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

diff --git a/.github/workflows/checks-quick.yml b/.github/workflows/checks-quick.yml index ee4bd62a558d..ee5ac31e9caa 100644 --- a/.github/workflows/checks-quick.yml +++ b/.github/workflows/checks-quick.yml @@ -100,6 +100,8 @@ jobs: --exclude "substrate/frame/contracts/fixtures/build" "substrate/frame/contracts/fixtures/contracts/common" + "substrate/frame/revive/fixtures/build" + "substrate/frame/revive/fixtures/contracts/common" - name: deny git deps run: python3 .github/scripts/deny-git-deps.py . check-markdown: @@ -154,3 +156,28 @@ jobs: git diff exit 1 fi + check-fail-ci: + runs-on: ubuntu-latest + container: + # there's no "rg" in ci-unified, and tools is a smaller image anyway + image: "paritytech/tools:latest" + # paritytech/tools uses "nonroot" user by default, which doesn't have enough + # permissions to create GHA context + options: --user root + steps: + - name: Fetch latest code + uses: actions/checkout@v4 + - name: Check + run: | + set +e + rg --line-number --hidden --type rust --glob '!{.git,target}' "$ASSERT_REGEX" .; exit_status=$? + if [ $exit_status -eq 0 ]; then + echo "$ASSERT_REGEX was found, exiting with 1"; + exit 1; + else + echo "No $ASSERT_REGEX was found, exiting with 0"; + exit 0; + fi + env: + ASSERT_REGEX: "FAIL-CI" + GIT_DEPTH: 1 diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml new file mode 100644 index 000000000000..dac46cf435a6 --- /dev/null +++ b/.github/workflows/cmd.yml @@ -0,0 +1,411 @@ +name: Command + +on: + issue_comment: # listen for comments on issues + types: [ created ] + +permissions: # allow the action to comment on the PR + contents: write + issues: write + pull-requests: write + actions: read + +jobs: + is-org-member: + if: startsWith(github.event.comment.body, '/cmd') + runs-on: ubuntu-latest + outputs: + member: ${{ steps.is-member.outputs.result }} + steps: + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@v2.1.0 + with: + app_id: ${{ secrets.CMD_BOT_APP_ID }} + private_key: ${{ secrets.CMD_BOT_APP_KEY }} + + - name: Check if user is a member of the organization + id: is-member + uses: actions/github-script@v7 + with: + github-token: ${{ steps.generate_token.outputs.token }} + result-encoding: string + script: | + const fs = require("fs"); + try { + const org = '${{ github.event.repository.owner.login }}'; + const username = '${{ github.event.comment.user.login }}'; + + const membership = await github.rest.orgs.checkMembershipForUser({ + org: org, + username: username + }); + + console.log(membership, membership.status, membership.status === 204); + + if (membership.status === 204) { + return 'true'; + } else { + console.log(membership); + fs.appendFileSync(process.env["GITHUB_STEP_SUMMARY"], `${membership.data && membership.data.message || 'Unknown error happened, please check logs'}`); + } + } catch (error) { + console.log(error) + } + + return 'false'; + + reject-non-members: + needs: is-org-member + if: ${{ startsWith(github.event.comment.body, '/cmd') && needs.is-org-member.outputs.member != 'true' }} + runs-on: ubuntu-latest + steps: + - name: Add reaction to rejected comment + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.reactions.createForIssueComment({ + comment_id: ${{ github.event.comment.id }}, + owner: context.repo.owner, + repo: context.repo.repo, + content: 'confused' + }) + + - name: Comment PR (Rejected) + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `Sorry, only members of the organization ${{ github.event.repository.owner.login }} members can run commands.` + }) + + acknowledge: + needs: is-org-member + if: ${{ startsWith(github.event.comment.body, '/cmd') && needs.is-org-member.outputs.member == 'true' }} + runs-on: ubuntu-latest + steps: + - name: Add reaction to triggered comment + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.reactions.createForIssueComment({ + comment_id: ${{ github.event.comment.id }}, + owner: context.repo.owner, + repo: context.repo.repo, + content: 'eyes' + }) + + clean: + needs: is-org-member + runs-on: ubuntu-latest + steps: + - name: Clean previous comments + if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--clean') && needs.is-org-member.outputs.member == 'true' }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.issues.listComments({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo + }).then(comments => { + for (let comment of comments.data) { + console.log(comment) + if ( + ${{ github.event.comment.id }} !== comment.id && + ( + ( + ( + comment.body.startsWith('Command') || + comment.body.startsWith('

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

Command failed ❌

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

Action completed 🎉🎉

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

Command failed ❌

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

Action completed 🎉🎉

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

Command failed ❌

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

Action completed 🎉🎉

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

Command failed ❌

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

Action completed 🎉🎉

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

Please, see the documentation on how to use it' - }) diff --git a/.github/workflows/command-prdoc.yml b/.github/workflows/command-prdoc.yml deleted file mode 100644 index 3a08b9a5fb28..000000000000 --- a/.github/workflows/command-prdoc.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: Command PrDoc - -on: - workflow_dispatch: - inputs: - pr: - type: number - description: Number of the Pull Request - required: true - bump: - type: choice - description: Default bump level for all crates - default: "TODO" - required: true - options: - - "TODO" - - "no change" - - "patch" - - "minor" - - "major" - audience: - type: choice - description: Audience of the PrDoc - default: "TODO" - required: true - options: - - "TODO" - - "Runtime Dev" - - "Runtime User" - - "Node Dev" - - "Node User" - overwrite: - type: choice - description: Overwrite existing PrDoc - default: "true" - required: true - options: - - "true" - - "false" - -concurrency: - group: command-prdoc - cancel-in-progress: true - -jobs: - set-image: - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - cmd-prdoc: - needs: [set-image] - runs-on: ubuntu-latest - timeout-minutes: 20 - container: - image: ${{ needs.set-image.outputs.IMAGE }} - permissions: - contents: write - pull-requests: write - steps: - - name: Download repo - uses: actions/checkout@v4 - - name: Install gh cli - id: gh - uses: ./.github/actions/set-up-gh - with: - pr-number: ${{ inputs.pr }} - GH_TOKEN: ${{ github.token }} - - name: Generate PrDoc - run: | - python3 -m pip install -q cargo-workspace PyGithub whatthepatch pyyaml toml - - python3 .github/scripts/generate-prdoc.py --pr "${{ inputs.pr }}" --bump "${{ inputs.bump }}" --audience "${{ inputs.audience }}" --force "${{ inputs.overwrite }}" - - - name: Report failure - if: ${{ failure() }} - run: gh pr comment ${{ inputs.pr }} --body "

Command failed ❌

Run by @${{ github.actor }} for ${{ github.workflow }} failed. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} - - name: Push Commit - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: Add PrDoc (auto generated) - branch: ${{ steps.gh.outputs.branch }} - file_pattern: 'prdoc/*.prdoc' diff --git a/.github/workflows/command-sync.yml b/.github/workflows/command-sync.yml deleted file mode 100644 index c610f4066a87..000000000000 --- a/.github/workflows/command-sync.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Command Sync - -on: - workflow_dispatch: - inputs: - pr: - description: Number of the Pull Request - required: true - chain: - description: Chain - type: choice - required: true - options: - - westend - - rococo - sync-type: - description: Sync type - type: choice - required: true - options: - - warp - - full - - fast - - fast-unsafe - -jobs: - set-image: - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - cmd-sync: - needs: [set-image] - runs-on: arc-runners-polkadot-sdk-warpsync - container: - image: ${{ needs.set-image.outputs.IMAGE }} - permissions: - contents: write - pull-requests: write - steps: - - name: Download repo - uses: actions/checkout@v4 - - name: Install gh cli - id: gh - uses: ./.github/actions/set-up-gh - with: - pr-number: ${{ inputs.pr }} - GH_TOKEN: ${{ github.token }} - - name: Run sync - run: | - "./scripts/sync.sh" --chain "${{ inputs.chain }}" --type "${{ inputs.sync-type }}" - - name: Report failure - if: ${{ failure() }} - run: gh pr comment ${{ inputs.pr }} --body "

Command failed ❌

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

Action completed 🎉🎉

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

Command failed ❌

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

Action completed 🎉🎉

Run by @${{ github.actor }} for ${{ github.workflow }} completed 🎉. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/release-30_publish_release_draft.yml b/.github/workflows/release-30_publish_release_draft.yml index 6d31ca7a7365..4343dbf915a9 100644 --- a/.github/workflows/release-30_publish_release_draft.yml +++ b/.github/workflows/release-30_publish_release_draft.yml @@ -26,6 +26,7 @@ jobs: uses: "./.github/workflows/release-srtool.yml" with: excluded_runtimes: "substrate-test bp cumulus-test kitchensink minimal-template parachain-template penpal polkadot-test seedling shell frame-try sp solochain-template" + build_opts: "--features on-chain-release-build" build-binaries: runs-on: ubuntu-latest diff --git a/.github/workflows/release-50_publish-docker.yml b/.github/workflows/release-50_publish-docker.yml index a749c86faa0c..c5d214ec68ab 100644 --- a/.github/workflows/release-50_publish-docker.yml +++ b/.github/workflows/release-50_publish-docker.yml @@ -298,7 +298,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@aa33708b10e362ff993539393ff100fa93ed6a27 # v3.5.0 + uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - name: Cache Docker layers uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 @@ -322,7 +322,7 @@ jobs: - name: Build and push id: docker_build - uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 # v6.5.0 + uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0 with: push: true file: docker/dockerfiles/polkadot/polkadot_injected_debian.Dockerfile diff --git a/.github/workflows/release-srtool.yml b/.github/workflows/release-srtool.yml index e1dc42afc6e9..262203f05005 100644 --- a/.github/workflows/release-srtool.yml +++ b/.github/workflows/release-srtool.yml @@ -9,6 +9,8 @@ on: inputs: excluded_runtimes: type: string + build_opts: + type: string outputs: published_runtimes: value: ${{ jobs.find-runtimes.outputs.runtime }} @@ -74,6 +76,8 @@ jobs: - name: Srtool build id: srtool_build uses: chevdor/srtool-actions@v0.9.2 + env: + BUILD_OPTS: ${{ inputs.build_opts }} with: chain: ${{ matrix.chain }} runtime_dir: ${{ matrix.runtime_dir }} diff --git a/.github/workflows/review-bot.yml b/.github/workflows/review-bot.yml index 80c96b0ef537..3dd5b1114813 100644 --- a/.github/workflows/review-bot.yml +++ b/.github/workflows/review-bot.yml @@ -15,7 +15,6 @@ on: jobs: review-approvals: runs-on: ubuntu-latest - environment: master steps: - name: Generate token id: app_token diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json new file mode 100644 index 000000000000..45a3acd3f166 --- /dev/null +++ b/.github/workflows/runtimes-matrix.json @@ -0,0 +1,98 @@ +[ + { + "name": "dev", + "package": "kitchensink-runtime", + "path": "substrate/frame", + "uri": null, + "is_relay": false + }, + { + "name": "westend", + "package": "westend-runtime", + "path": "polkadot/runtime/westend", + "uri": "wss://try-runtime-westend.polkadot.io:443", + "is_relay": true + }, + { + "name": "rococo", + "package": "rococo-runtime", + "path": "polkadot/runtime/rococo", + "uri": "wss://try-runtime-rococo.polkadot.io:443", + "is_relay": true + }, + { + "name": "asset-hub-westend", + "package": "asset-hub-westend-runtime", + "path": "cumulus/parachains/runtimes/assets/asset-hub-westend", + "uri": "wss://westend-asset-hub-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "asset-hub-rococo", + "package": "asset-hub-rococo-runtime", + "path": "cumulus/parachains/runtimes/assets/asset-hub-rococo", + "uri": "wss://rococo-asset-hub-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "bridge-hub-rococo", + "package": "bridge-hub-rococo-runtime", + "path": "cumulus/parachains/runtimes/bridges/bridge-hub-rococo", + "uri": "wss://rococo-bridge-hub-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "bridge-hub-westend", + "package": "bridge-hub-rococo-runtime", + "path": "cumulus/parachains/runtimes/bridges/bridge-hub-westend", + "uri": "wss://westend-bridge-hub-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "collectives-westend", + "package": "collectives-westend-runtime", + "path": "cumulus/parachains/runtimes/collectives/collectives-westend", + "uri": "wss://westend-collectives-rpc.polkadot.io:443" + }, + { + "name": "contracts-rococo", + "package": "contracts-rococo-runtime", + "path": "cumulus/parachains/runtimes/contracts/contracts-rococo", + "uri": "wss://rococo-contracts-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "coretime-rococo", + "package": "coretime-rococo-runtime", + "path": "cumulus/parachains/runtimes/coretime/coretime-rococo", + "uri": "wss://rococo-coretime-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "coretime-westend", + "package": "coretime-westend-runtime", + "path": "cumulus/parachains/runtimes/coretime/coretime-westend", + "uri": "wss://westend-coretime-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "glutton-westend", + "package": "glutton-westend-runtime", + "path": "cumulus/parachains/runtimes/gluttons/glutton-westend", + "is_relay": false + }, + { + "name": "people-rococo", + "package": "people-rococo-runtime", + "path": "cumulus/parachains/runtimes/people/people-rococo", + "uri": "wss://rococo-people-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "people-westend", + "package": "people-westend-runtime", + "path": "cumulus/parachains/runtimes/people/people-westend", + "uri": "wss://westend-people-rpc.polkadot.io:443", + "is_relay": false + } +] diff --git a/.gitignore b/.gitignore index e3e382af6195..0263626d832d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ **/node_modules **/target/ **/wip/*.stderr +**/__pycache__/ /.cargo/config /.envrc artifacts diff --git a/.gitlab/pipeline/check.yml b/.gitlab/pipeline/check.yml index 2b8b90ef19a4..3e94eb77c7b4 100644 --- a/.gitlab/pipeline/check.yml +++ b/.gitlab/pipeline/check.yml @@ -106,7 +106,7 @@ check-runtime-migration-westend: NETWORK: "westend" PACKAGE: "westend-runtime" WASM: "westend_runtime.compact.compressed.wasm" - URI: "wss://westend-try-runtime-node.parity-chains.parity.io:443" + URI: "wss://try-runtime-westend.polkadot.io:443" SUBCOMMAND_EXTRA_ARGS: "--no-weight-warnings" check-runtime-migration-rococo: @@ -119,29 +119,9 @@ check-runtime-migration-rococo: NETWORK: "rococo" PACKAGE: "rococo-runtime" WASM: "rococo_runtime.compact.compressed.wasm" - URI: "wss://rococo-try-runtime-node.parity-chains.parity.io:443" + URI: "wss://try-runtime-rococo.polkadot.io:443" SUBCOMMAND_EXTRA_ARGS: "--no-weight-warnings" -find-fail-ci-phrase: - stage: check - variables: - CI_IMAGE: "paritytech/tools:latest" - ASSERT_REGEX: "FAIL-CI" - GIT_DEPTH: 1 - extends: - - .kubernetes-env - - .test-pr-refs - script: - - set +e - - rg --line-number --hidden --type rust --glob '!{.git,target}' "$ASSERT_REGEX" .; exit_status=$? - - if [ $exit_status -eq 0 ]; then - echo "$ASSERT_REGEX was found, exiting with 1"; - exit 1; - else - echo "No $ASSERT_REGEX was found, exiting with 0"; - exit 0; - fi - check-core-crypto-features: stage: check extends: diff --git a/.gitlab/pipeline/zombienet/bridges.yml b/.gitlab/pipeline/zombienet/bridges.yml index 9d7a8b931193..070bfc8472d5 100644 --- a/.gitlab/pipeline/zombienet/bridges.yml +++ b/.gitlab/pipeline/zombienet/bridges.yml @@ -52,12 +52,12 @@ zombienet-bridges-0001-asset-transfer-works: extends: - .zombienet-bridges-common script: - - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0001-asset-transfer --docker + - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-test.sh 0001-asset-transfer --docker - echo "Done" zombienet-bridges-0002-free-headers-synced-while-idle: extends: - .zombienet-bridges-common script: - - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0002-free-headers-synced-while-idle --docker + - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-test.sh 0002-free-headers-synced-while-idle --docker - echo "Done" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 120000 index 000000000000..63b2a0dc1abc --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +docs/contributor/CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 120000 index 000000000000..0f645512e8e4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +docs/contributor/CONTRIBUTING.md \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a67f9fdee31b..bb0f01542d31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1465,15 +1465,6 @@ dependencies = [ "serde", ] -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" -dependencies = [ - "serde", -] - [[package]] name = "binary-merkle-tree" version = "13.0.0" @@ -1511,7 +1502,7 @@ dependencies = [ "proc-macro2 1.0.82", "quote 1.0.36", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.61", ] @@ -1622,13 +1613,13 @@ dependencies = [ [[package]] name = "blake2b_simd" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", "arrayvec 0.7.4", - "constant_time_eq 0.2.6", + "constant_time_eq 0.3.0", ] [[package]] @@ -5991,7 +5982,7 @@ dependencies = [ "sc-cli", "sp-runtime", "sp-statement-store", - "sp-tracing 16.0.0", + "tracing-subscriber 0.3.18", ] [[package]] @@ -7416,9 +7407,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b089779ad7f80768693755a031cc14a7766aba707cbe886674e3f79e9b7e47" +checksum = "5ec465b607a36dc5dd45d48b7689bc83f679f66a3ac6b6b21cc787a11e0f8685" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-client", @@ -7432,9 +7423,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08163edd8bcc466c33d79e10f695cdc98c00d1e6ddfb95cec41b6b0279dd5432" +checksum = "90f0977f9c15694371b8024c35ab58ca043dbbf4b51ccb03db8858a021241df1" dependencies = [ "base64 0.22.1", "futures-util", @@ -7455,13 +7446,11 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79712302e737d23ca0daa178e752c9334846b08321d439fd89af9a384f8c830b" +checksum = "e942c55635fbf5dc421938b8558a8141c7e773720640f4f1dbe1f4164ca4e221" dependencies = [ - "anyhow", "async-trait", - "beef", "bytes", "futures-timer", "futures-util", @@ -7472,7 +7461,7 @@ dependencies = [ "parking_lot 0.12.3", "pin-project", "rand", - "rustc-hash", + "rustc-hash 2.0.0", "serde", "serde_json", "thiserror", @@ -7483,9 +7472,9 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d90064e04fb9d7282b1c71044ea94d0bbc6eff5621c66f1a0bce9e9de7cf3ac" +checksum = "e33774602df12b68a2310b38a535733c477ca4a498751739f89fe8dbbb62ec4c" dependencies = [ "async-trait", "base64 0.22.1", @@ -7508,9 +7497,9 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7895f186d5921065d96e16bd795e5ca89ac8356ec423fafc6e3d7cf8ec11aee4" +checksum = "6b07a2daf52077ab1b197aea69a5c990c060143835bf04c77070e98903791715" dependencies = [ "heck 0.5.0", "proc-macro-crate 3.1.0", @@ -7521,11 +7510,10 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "654afab2e92e5d88ebd8a39d6074483f3f2bfdf91c5ac57fe285e7127cdd4f51" +checksum = "038fb697a709bec7134e9ccbdbecfea0e2d15183f7140254afef7c5610a3f488" dependencies = [ - "anyhow", "futures-util", "http 1.1.0", "http-body 1.0.0", @@ -7549,11 +7537,10 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c465fbe385238e861fdc4d1c85e04ada6c1fd246161d26385c1b311724d2af" +checksum = "23b67d6e008164f027afbc2e7bb79662650158d26df200040282d2aa1cbb093b" dependencies = [ - "beef", "http 1.1.0", "serde", "serde_json", @@ -7562,9 +7549,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c28759775f5cb2f1ea9667672d3fe2b0e701d1f4b7b67954e60afe7fd058b5e" +checksum = "992bf67d1132f88edf4a4f8cff474cf01abb2be203004a2b8e11c2b20795b99e" dependencies = [ "http 1.1.0", "jsonrpsee-client-transport", @@ -8739,48 +8726,18 @@ dependencies = [ "futures-timer", "jsonrpsee", "minimal-template-runtime", - "polkadot-sdk-frame", - "sc-basic-authorship", - "sc-cli", - "sc-client-api", - "sc-consensus", - "sc-consensus-manual-seal", - "sc-executor", - "sc-network", - "sc-offchain", - "sc-rpc-api", - "sc-service", - "sc-telemetry", - "sc-transaction-pool", - "sc-transaction-pool-api", + "polkadot-sdk", "serde_json", - "sp-api", - "sp-block-builder", - "sp-blockchain", - "sp-io", - "sp-keyring", - "sp-runtime", - "sp-timestamp", - "substrate-build-script-utils", - "substrate-frame-rpc-system", ] [[package]] name = "minimal-template-runtime" version = "0.0.0" dependencies = [ - "pallet-balances", "pallet-minimal-template", - "pallet-sudo", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", - "polkadot-sdk-frame", + "polkadot-sdk", "scale-info", - "sp-genesis-builder", - "sp-runtime", - "substrate-wasm-builder", ] [[package]] @@ -9283,7 +9240,6 @@ dependencies = [ "sc-consensus-grandpa-rpc", "sc-mixnet", "sc-rpc", - "sc-rpc-api", "sc-sync-state-rpc", "sc-transaction-pool-api", "sp-api", @@ -9571,6 +9527,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +dependencies = [ + "memchr", +] + [[package]] name = "oid-registry" version = "0.6.1" @@ -10357,7 +10322,7 @@ dependencies = [ "anyhow", "frame-system", "parity-wasm", - "polkavm-linker", + "polkavm-linker 0.9.2", "sp-runtime", "tempfile", "toml 0.8.8", @@ -10417,7 +10382,7 @@ dependencies = [ "bitflags 1.3.2", "parity-scale-codec", "paste", - "polkavm-derive", + "polkavm-derive 0.9.1", "scale-info", ] @@ -10963,7 +10928,7 @@ name = "pallet-minimal-template" version = "0.0.0" dependencies = [ "parity-scale-codec", - "polkadot-sdk-frame", + "polkadot-sdk", "scale-info", ] @@ -11412,6 +11377,115 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-revive" +version = "0.1.0" +dependencies = [ + "array-bytes", + "assert_matches", + "bitflags 1.3.2", + "environmental", + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-assets", + "pallet-balances", + "pallet-message-queue", + "pallet-proxy", + "pallet-revive-fixtures", + "pallet-revive-proc-macro", + "pallet-revive-uapi", + "pallet-timestamp", + "pallet-utility", + "parity-scale-codec", + "paste", + "polkavm 0.10.0", + "pretty_assertions", + "scale-info", + "serde", + "sp-api", + "sp-core", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-std 14.0.0", + "sp-tracing 16.0.0", + "staging-xcm", + "staging-xcm-builder", + "wat", +] + +[[package]] +name = "pallet-revive-fixtures" +version = "0.1.0" +dependencies = [ + "anyhow", + "frame-system", + "parity-wasm", + "polkavm-linker 0.10.0", + "sp-runtime", + "tempfile", + "toml 0.8.8", +] + +[[package]] +name = "pallet-revive-mock-network" +version = "0.1.0" +dependencies = [ + "assert_matches", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "pallet-message-queue", + "pallet-proxy", + "pallet-revive", + "pallet-revive-fixtures", + "pallet-revive-proc-macro", + "pallet-revive-uapi", + "pallet-timestamp", + "pallet-utility", + "pallet-xcm", + "parity-scale-codec", + "polkadot-parachain-primitives", + "polkadot-primitives", + "polkadot-runtime-parachains", + "pretty_assertions", + "scale-info", + "sp-api", + "sp-core", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-tracing 16.0.0", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "xcm-simulator", +] + +[[package]] +name = "pallet-revive-proc-macro" +version = "0.1.0" +dependencies = [ + "proc-macro2 1.0.82", + "quote 1.0.36", + "syn 2.0.61", +] + +[[package]] +name = "pallet-revive-uapi" +version = "0.1.0" +dependencies = [ + "bitflags 1.3.2", + "parity-scale-codec", + "paste", + "polkavm-derive 0.10.0", + "scale-info", +] + [[package]] name = "pallet-root-offences" version = "25.0.0" @@ -13918,6 +13992,7 @@ dependencies = [ "sc-chain-spec", "sc-cli", "sc-client-api", + "sc-client-db", "sc-consensus", "sc-executor", "sc-network", @@ -14304,6 +14379,11 @@ dependencies = [ "pallet-recovery", "pallet-referenda", "pallet-remark", + "pallet-revive", + "pallet-revive-fixtures", + "pallet-revive-mock-network", + "pallet-revive-proc-macro", + "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", @@ -15093,9 +15173,22 @@ checksum = "8a3693e5efdb2bf74e449cd25fd777a28bd7ed87e41f5d5da75eb31b4de48b94" dependencies = [ "libc", "log", - "polkavm-assembler", - "polkavm-common", - "polkavm-linux-raw", + "polkavm-assembler 0.9.0", + "polkavm-common 0.9.0", + "polkavm-linux-raw 0.9.0", +] + +[[package]] +name = "polkavm" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ec0c5935f2eff23cfc4653002f4f8d12b37f87a720e0631282d188c32089d6" +dependencies = [ + "libc", + "log", + "polkavm-assembler 0.10.0", + "polkavm-common 0.10.0", + "polkavm-linux-raw 0.10.0", ] [[package]] @@ -15107,6 +15200,15 @@ dependencies = [ "log", ] +[[package]] +name = "polkavm-assembler" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e4fd5a43100bf1afe9727b8130d01f966f5cfc9144d5604b21e795c2bcd80e" +dependencies = [ + "log", +] + [[package]] name = "polkavm-common" version = "0.9.0" @@ -15116,13 +15218,32 @@ dependencies = [ "log", ] +[[package]] +name = "polkavm-common" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0097b48bc0bedf9f3f537ce8f37e8f1202d8d83f9b621bdb21ff2c59b9097c50" +dependencies = [ + "log", + "polkavm-assembler 0.10.0", +] + [[package]] name = "polkavm-derive" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae8c4bea6f3e11cd89bb18bcdddac10bd9a24015399bd1c485ad68a985a19606" dependencies = [ - "polkavm-derive-impl-macro", + "polkavm-derive-impl-macro 0.9.0", +] + +[[package]] +name = "polkavm-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dcc701385c08c31bdb0569f0c51a290c580d892fa77f1dd88a7352a62679ecf" +dependencies = [ + "polkavm-derive-impl-macro 0.10.0", ] [[package]] @@ -15131,7 +15252,19 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" dependencies = [ - "polkavm-common", + "polkavm-common 0.9.0", + "proc-macro2 1.0.82", + "quote 1.0.36", + "syn 2.0.61", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7855353a5a783dd5d09e3b915474bddf66575f5a3cf45dec8d1c5e051ba320dc" +dependencies = [ + "polkavm-common 0.10.0", "proc-macro2 1.0.82", "quote 1.0.36", "syn 2.0.61", @@ -15143,7 +15276,17 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ - "polkavm-derive-impl", + "polkavm-derive-impl 0.9.0", + "syn 2.0.61", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9324fe036de37c17829af233b46ef6b5562d4a0c09bb7fdb9f8378856dee30cf" +dependencies = [ + "polkavm-derive-impl 0.10.0", "syn 2.0.61", ] @@ -15157,7 +15300,22 @@ dependencies = [ "hashbrown 0.14.3", "log", "object 0.32.2", - "polkavm-common", + "polkavm-common 0.9.0", + "regalloc2 0.9.3", + "rustc-demangle", +] + +[[package]] +name = "polkavm-linker" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d704edfe7bdcc876784f19436d53d515b65eb07bc9a0fae77085d552c2dbbb5" +dependencies = [ + "gimli 0.28.0", + "hashbrown 0.14.3", + "log", + "object 0.36.1", + "polkavm-common 0.10.0", "regalloc2 0.9.3", "rustc-demangle", ] @@ -15168,6 +15326,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26e85d3456948e650dff0cfc85603915847faf893ed1e66b020bb82ef4557120" +[[package]] +name = "polkavm-linux-raw" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26e45fa59c7e1bb12ef5289080601e9ec9b31435f6e32800a5c90c132453d126" + [[package]] name = "polling" version = "2.8.0" @@ -15777,7 +15941,7 @@ dependencies = [ "pin-project-lite", "quinn-proto 0.9.6", "quinn-udp 0.3.2", - "rustc-hash", + "rustc-hash 1.1.0", "rustls 0.20.9", "thiserror", "tokio", @@ -15796,7 +15960,7 @@ dependencies = [ "pin-project-lite", "quinn-proto 0.10.6", "quinn-udp 0.4.1", - "rustc-hash", + "rustc-hash 1.1.0", "rustls 0.21.7", "thiserror", "tokio", @@ -15812,7 +15976,7 @@ dependencies = [ "bytes", "rand", "ring 0.16.20", - "rustc-hash", + "rustc-hash 1.1.0", "rustls 0.20.9", "slab", "thiserror", @@ -15830,7 +15994,7 @@ dependencies = [ "bytes", "rand", "ring 0.16.20", - "rustc-hash", + "rustc-hash 1.1.0", "rustls 0.21.7", "slab", "thiserror", @@ -16103,7 +16267,7 @@ checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" dependencies = [ "hashbrown 0.13.2", "log", - "rustc-hash", + "rustc-hash 1.1.0", "slice-group-by", "smallvec", ] @@ -16704,6 +16868,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -16925,9 +17095,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rusty-fork" @@ -17638,7 +17808,7 @@ dependencies = [ name = "sc-executor-common" version = "0.29.0" dependencies = [ - "polkavm", + "polkavm 0.9.3", "sc-allocator", "sp-maybe-compressed-blob", "sp-wasm-interface 20.0.0", @@ -17651,7 +17821,7 @@ name = "sc-executor-polkavm" version = "0.29.0" dependencies = [ "log", - "polkavm", + "polkavm 0.9.3", "sc-executor-common", "sp-wasm-interface 20.0.0", ] @@ -18074,11 +18244,9 @@ dependencies = [ "sp-runtime", "sp-session", "sp-statement-store", - "sp-tracing 16.0.0", "sp-version", "substrate-test-runtime-client", "tokio", - "tracing-subscriber 0.3.18", ] [[package]] @@ -18104,6 +18272,7 @@ dependencies = [ name = "sc-rpc-server" version = "11.0.0" dependencies = [ + "dyn-clone", "forwarded-header-value", "futures", "governor", @@ -18113,6 +18282,7 @@ dependencies = [ "ip_network", "jsonrpsee", "log", + "sc-rpc-api", "serde", "serde_json", "substrate-prometheus-endpoint", @@ -18390,7 +18560,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "sc-client-api", "sc-tracing-proc-macro", "serde", @@ -19750,7 +19920,6 @@ dependencies = [ "sc-executor", "sc-network", "sc-offchain", - "sc-rpc-api", "sc-service", "sc-telemetry", "sc-transaction-pool", @@ -20312,7 +20481,7 @@ dependencies = [ "libsecp256k1", "log", "parity-scale-codec", - "polkavm-derive", + "polkavm-derive 0.9.1", "rustversion", "secp256k1", "sp-core", @@ -20439,7 +20608,7 @@ dependencies = [ name = "sp-rpc" version = "26.0.0" dependencies = [ - "rustc-hash", + "rustc-hash 1.1.0", "serde", "serde_json", "sp-core", @@ -20501,7 +20670,7 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "polkavm-derive", + "polkavm-derive 0.9.1", "primitive-types", "rustversion", "sp-core", @@ -21546,7 +21715,7 @@ dependencies = [ "merkleized-metadata", "parity-scale-codec", "parity-wasm", - "polkavm-linker", + "polkavm-linker 0.9.2", "sc-executor", "sp-core", "sp-io", diff --git a/Cargo.toml b/Cargo.toml index 397163b3cce1..f26a894960a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -395,6 +395,11 @@ members = [ "substrate/frame/recovery", "substrate/frame/referenda", "substrate/frame/remark", + "substrate/frame/revive", + "substrate/frame/revive/fixtures", + "substrate/frame/revive/mock-network", + "substrate/frame/revive/proc-macro", + "substrate/frame/revive/uapi", "substrate/frame/root-offences", "substrate/frame/root-testing", "substrate/frame/safe-mode", @@ -579,7 +584,7 @@ ahash = { version = "0.8.2" } alloy-primitives = { version = "0.4.2", default-features = false } alloy-sol-types = { version = "0.4.2", default-features = false } always-assert = { version = "0.1" } -anyhow = { version = "1.0.81" } +anyhow = { version = "1.0.81", default-features = false } aquamarine = { version = "0.5.0" } arbitrary = { version = "1.3.2" } ark-bls12-377 = { version = "0.4.0", default-features = false } @@ -616,7 +621,7 @@ bip39 = { version = "2.0.0" } bitflags = { version = "1.3.2" } bitvec = { version = "1.0.1", default-features = false } blake2 = { version = "0.10.4", default-features = false } -blake2b_simd = { version = "1.0.1", default-features = false } +blake2b_simd = { version = "1.0.2", default-features = false } blake3 = { version = "1.5" } bounded-collections = { version = "0.2.0", default-features = false } bounded-vec = { version = "0.7" } @@ -807,8 +812,8 @@ isahc = { version = "1.2" } itertools = { version = "0.11" } jobserver = { version = "0.1.26" } jsonpath_lib = { version = "0.3" } -jsonrpsee = { version = "0.23.2" } -jsonrpsee-core = { version = "0.23.2" } +jsonrpsee = { version = "0.24.3" } +jsonrpsee-core = { version = "0.24.3" } k256 = { version = "0.13.3", default-features = false } kitchensink-runtime = { path = "substrate/bin/node/runtime" } kvdb = { version = "0.13.0" } @@ -894,7 +899,7 @@ pallet-collator-selection = { path = "cumulus/pallets/collator-selection", defau pallet-collective = { path = "substrate/frame/collective", default-features = false } pallet-collective-content = { path = "cumulus/parachains/pallets/collective-content", default-features = false } pallet-contracts = { path = "substrate/frame/contracts", default-features = false } -pallet-contracts-fixtures = { path = "substrate/frame/contracts/fixtures" } +pallet-contracts-fixtures = { path = "substrate/frame/contracts/fixtures", default-features = false } pallet-contracts-mock-network = { default-features = false, path = "substrate/frame/contracts/mock-network" } pallet-contracts-proc-macro = { path = "substrate/frame/contracts/proc-macro", default-features = false } pallet-contracts-uapi = { path = "substrate/frame/contracts/uapi", default-features = false } @@ -950,6 +955,11 @@ pallet-ranked-collective = { path = "substrate/frame/ranked-collective", default pallet-recovery = { path = "substrate/frame/recovery", default-features = false } pallet-referenda = { path = "substrate/frame/referenda", default-features = false } pallet-remark = { default-features = false, path = "substrate/frame/remark" } +pallet-revive = { path = "substrate/frame/revive", default-features = false } +pallet-revive-fixtures = { path = "substrate/frame/revive/fixtures", default-features = false } +pallet-revive-mock-network = { default-features = false, path = "substrate/frame/revive/mock-network" } +pallet-revive-proc-macro = { path = "substrate/frame/revive/proc-macro", default-features = false } +pallet-revive-uapi = { path = "substrate/frame/revive/uapi", default-features = false } pallet-root-offences = { default-features = false, path = "substrate/frame/root-offences" } pallet-root-testing = { path = "substrate/frame/root-testing", default-features = false } pallet-safe-mode = { default-features = false, path = "substrate/frame/safe-mode" } @@ -1060,7 +1070,7 @@ polkadot-subsystem-bench = { path = "polkadot/node/subsystem-bench" } polkadot-test-client = { path = "polkadot/node/test/client" } polkadot-test-runtime = { path = "polkadot/runtime/test-runtime" } polkadot-test-service = { path = "polkadot/node/test/service" } -polkavm = "0.9.3" +polkavm = { version = "0.9.3", default-features = false } polkavm-derive = "0.9.1" polkavm-linker = "0.9.2" portpicker = { version = "0.1.1" } @@ -1105,7 +1115,7 @@ rstest = { version = "0.18.2" } rustc-hash = { version = "1.1.0" } rustc-hex = { version = "2.1.0", default-features = false } rustix = { version = "0.36.7", default-features = false } -rustversion = { version = "1.0.6" } +rustversion = { version = "1.0.17" } rusty-fork = { version = "0.3.0", default-features = false } safe-mix = { version = "1.0", default-features = false } sc-allocator = { path = "substrate/client/allocator", default-features = false } diff --git a/bridges/modules/messages/src/lib.rs b/bridges/modules/messages/src/lib.rs index bf105b140401..c36313a14764 100644 --- a/bridges/modules/messages/src/lib.rs +++ b/bridges/modules/messages/src/lib.rs @@ -70,7 +70,6 @@ use bp_runtime::{ }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get, DefaultNoBound}; -use sp_runtime::traits::UniqueSaturatedFrom; use sp_std::{marker::PhantomData, prelude::*}; mod inbound_lane; @@ -153,40 +152,6 @@ pub mod pallet { type OperatingModeStorage = PalletOperatingMode; } - #[pallet::hooks] - impl, I: 'static> Hooks> for Pallet - where - u32: TryFrom>, - { - fn on_idle(_block: BlockNumberFor, remaining_weight: Weight) -> Weight { - // we'll need at least to read outbound lane state, kill a message and update lane state - let db_weight = T::DbWeight::get(); - if !remaining_weight.all_gte(db_weight.reads_writes(1, 2)) { - return Weight::zero() - } - - // messages from lane with index `i` in `ActiveOutboundLanes` are pruned when - // `System::block_number() % lanes.len() == i`. Otherwise we need to read lane states on - // every block, wasting the whole `remaining_weight` for nothing and causing starvation - // of the last lane pruning - let active_lanes = T::ActiveOutboundLanes::get(); - let active_lanes_len = (active_lanes.len() as u32).into(); - let active_lane_index = u32::unique_saturated_from( - frame_system::Pallet::::block_number() % active_lanes_len, - ); - let active_lane_id = active_lanes[active_lane_index as usize]; - - // first db read - outbound lane state - let mut active_lane = outbound_lane::(active_lane_id); - let mut used_weight = db_weight.reads(1); - // and here we'll have writes - used_weight += active_lane.prune_messages(db_weight, remaining_weight - used_weight); - - // we already checked we have enough `remaining_weight` to cover this `used_weight` - used_weight - } - } - #[pallet::call] impl, I: 'static> Pallet { /// Change `PalletOwner`. @@ -610,6 +575,14 @@ pub mod pallet { } } + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + Self::do_try_state() + } + } + impl, I: 'static> Pallet { /// Get stored data of the outbound message with given nonce. pub fn outbound_message_data(lane: LaneId, nonce: MessageNonce) -> Option { @@ -644,6 +617,58 @@ pub mod pallet { } } + #[cfg(any(feature = "try-runtime", test))] + impl, I: 'static> Pallet { + /// Ensure the correctness of the state of this pallet. + pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + Self::do_try_state_for_outbound_lanes() + } + + /// Ensure the correctness of the state of outbound lanes. + pub fn do_try_state_for_outbound_lanes() -> Result<(), sp_runtime::TryRuntimeError> { + use sp_runtime::traits::One; + use sp_std::vec::Vec; + + // collect unpruned lanes + let mut unpruned_lanes = Vec::new(); + for (lane_id, lane_data) in OutboundLanes::::iter() { + let Some(expected_last_prunned_nonce) = + lane_data.oldest_unpruned_nonce.checked_sub(One::one()) + else { + continue; + }; + + // collect message_nonces that were supposed to be pruned + let mut unpruned_message_nonces = Vec::new(); + const MAX_MESSAGES_ITERATION: u64 = 16; + let start_nonce = + expected_last_prunned_nonce.checked_sub(MAX_MESSAGES_ITERATION).unwrap_or(0); + for current_nonce in start_nonce..=expected_last_prunned_nonce { + // check a message for current_nonce + if OutboundMessages::::contains_key(MessageKey { + lane_id, + nonce: current_nonce, + }) { + unpruned_message_nonces.push(current_nonce); + } + } + + if !unpruned_message_nonces.is_empty() { + log::warn!( + target: LOG_TARGET, + "do_try_state_for_outbound_lanes for lane_id: {lane_id:?} with lane_data: {lane_data:?} found unpruned_message_nonces: {unpruned_message_nonces:?}", + ); + unpruned_lanes.push((lane_id, lane_data, unpruned_message_nonces)); + } + } + + // ensure messages before `oldest_unpruned_nonce` are really pruned. + ensure!(unpruned_lanes.is_empty(), "Found unpruned lanes!"); + + Ok(()) + } + } + /// Get-parameter that returns number of active outbound lanes that the pallet maintains. pub struct MaybeOutboundLanesCount(PhantomData<(T, I)>); diff --git a/bridges/modules/messages/src/outbound_lane.rs b/bridges/modules/messages/src/outbound_lane.rs index fcdddf199dc6..788a13e82b1b 100644 --- a/bridges/modules/messages/src/outbound_lane.rs +++ b/bridges/modules/messages/src/outbound_lane.rs @@ -22,13 +22,9 @@ use bp_messages::{ ChainWithMessages, DeliveredMessages, LaneId, MessageNonce, OutboundLaneData, UnrewardedRelayer, }; use codec::{Decode, Encode}; -use frame_support::{ - traits::Get, - weights::{RuntimeDbWeight, Weight}, - BoundedVec, PalletError, -}; +use frame_support::{traits::Get, BoundedVec, PalletError}; use scale_info::TypeInfo; -use sp_runtime::{traits::Zero, RuntimeDebug}; +use sp_runtime::RuntimeDebug; use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData}; /// Outbound lane storage. @@ -143,41 +139,17 @@ impl OutboundLane { ensure_unrewarded_relayers_are_correct(confirmed_messages.end, relayers)?; + // prune all confirmed messages + for nonce in confirmed_messages.begin..=confirmed_messages.end { + self.storage.remove_message(&nonce); + } + data.latest_received_nonce = confirmed_messages.end; + data.oldest_unpruned_nonce = data.latest_received_nonce.saturating_add(1); self.storage.set_data(data); Ok(Some(confirmed_messages)) } - - /// Prune at most `max_messages_to_prune` already received messages. - /// - /// Returns weight, consumed by messages pruning and lane state update. - pub fn prune_messages( - &mut self, - db_weight: RuntimeDbWeight, - mut remaining_weight: Weight, - ) -> Weight { - let write_weight = db_weight.writes(1); - let two_writes_weight = write_weight + write_weight; - let mut spent_weight = Weight::zero(); - let mut data = self.storage.data(); - while remaining_weight.all_gte(two_writes_weight) && - data.oldest_unpruned_nonce <= data.latest_received_nonce - { - self.storage.remove_message(&data.oldest_unpruned_nonce); - - spent_weight += write_weight; - remaining_weight -= write_weight; - data.oldest_unpruned_nonce += 1; - } - - if !spent_weight.is_zero() { - spent_weight += write_weight; - self.storage.set_data(data); - } - - spent_weight - } } /// Verifies unrewarded relayers vec. @@ -221,7 +193,6 @@ mod tests { REGULAR_PAYLOAD, TEST_LANE_ID, }, }; - use frame_support::weights::constants::RocksDbWeight; use sp_std::ops::RangeInclusive; fn unrewarded_relayers( @@ -281,7 +252,7 @@ mod tests { ); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 3); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4); }); } @@ -302,7 +273,7 @@ mod tests { ); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 2); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 3); assert_eq!( lane.confirm_delivery(3, 3, &unrewarded_relayers(3..=3)), @@ -310,7 +281,7 @@ mod tests { ); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 3); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4); }); } @@ -331,12 +302,12 @@ mod tests { assert_eq!(lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)), Ok(None),); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 3); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4); assert_eq!(lane.confirm_delivery(1, 2, &unrewarded_relayers(1..=1)), Ok(None),); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 3); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4); }); } @@ -394,57 +365,6 @@ mod tests { ); } - #[test] - fn prune_messages_works() { - run_test(|| { - let mut lane = outbound_lane::(TEST_LANE_ID); - // when lane is empty, nothing is pruned - assert_eq!( - lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)), - Weight::zero() - ); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); - // when nothing is confirmed, nothing is pruned - lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); - lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); - lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); - assert!(lane.storage.message(&1).is_some()); - assert!(lane.storage.message(&2).is_some()); - assert!(lane.storage.message(&3).is_some()); - assert_eq!( - lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)), - Weight::zero() - ); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); - // after confirmation, some messages are received - assert_eq!( - lane.confirm_delivery(2, 2, &unrewarded_relayers(1..=2)), - Ok(Some(delivered_messages(1..=2))), - ); - assert_eq!( - lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)), - RocksDbWeight::get().writes(3), - ); - assert!(lane.storage.message(&1).is_none()); - assert!(lane.storage.message(&2).is_none()); - assert!(lane.storage.message(&3).is_some()); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 3); - // after last message is confirmed, everything is pruned - assert_eq!( - lane.confirm_delivery(1, 3, &unrewarded_relayers(3..=3)), - Ok(Some(delivered_messages(3..=3))), - ); - assert_eq!( - lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)), - RocksDbWeight::get().writes(2), - ); - assert!(lane.storage.message(&1).is_none()); - assert!(lane.storage.message(&2).is_none()); - assert!(lane.storage.message(&3).is_none()); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4); - }); - } - #[test] fn confirm_delivery_detects_when_more_than_expected_messages_are_confirmed() { run_test(|| { diff --git a/bridges/modules/messages/src/tests/pallet_tests.rs b/bridges/modules/messages/src/tests/pallet_tests.rs index 42e1042717de..f7a288d649a9 100644 --- a/bridges/modules/messages/src/tests/pallet_tests.rs +++ b/bridges/modules/messages/src/tests/pallet_tests.rs @@ -38,15 +38,14 @@ use bp_runtime::{BasicOperatingMode, PreComputedSize, RangeInclusiveExt, Size}; use bp_test_utils::generate_owned_bridge_module_tests; use codec::Encode; use frame_support::{ - assert_noop, assert_ok, + assert_err, assert_noop, assert_ok, dispatch::Pays, storage::generator::{StorageMap, StorageValue}, - traits::Hooks, weights::Weight, }; use frame_system::{EventRecord, Pallet as System, Phase}; use sp_core::Get; -use sp_runtime::DispatchError; +use sp_runtime::{BoundedVec, DispatchError}; fn get_ready_for_events() { System::::set_block_number(1); @@ -99,6 +98,7 @@ fn receive_messages_delivery_proof() { last_delivered_nonce: 1, }, )); + assert_ok!(Pallet::::do_try_state()); assert_eq!( System::::events(), @@ -160,6 +160,7 @@ fn pallet_rejects_transactions_if_halted() { ), Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), ); + assert_ok!(Pallet::::do_try_state()); }); } @@ -220,6 +221,7 @@ fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() { last_delivered_nonce: 1, }, )); + assert_ok!(Pallet::::do_try_state()); }); } @@ -395,10 +397,14 @@ fn receive_messages_proof_rejects_proof_with_too_many_messages() { #[test] fn receive_messages_delivery_proof_works() { run_test(|| { + assert_eq!(OutboundLanes::::get(TEST_LANE_ID).latest_received_nonce, 0); + assert_eq!(OutboundLanes::::get(TEST_LANE_ID).oldest_unpruned_nonce, 1); + send_regular_message(TEST_LANE_ID); receive_messages_delivery_proof(); - assert_eq!(OutboundLanes::::get(TEST_LANE_ID).latest_received_nonce, 1,); + assert_eq!(OutboundLanes::::get(TEST_LANE_ID).latest_received_nonce, 1); + assert_eq!(OutboundLanes::::get(TEST_LANE_ID).oldest_unpruned_nonce, 2); }); } @@ -428,6 +434,7 @@ fn receive_messages_delivery_proof_rewards_relayers() { }, ); assert_ok!(result); + assert_ok!(Pallet::::do_try_state()); assert_eq!( result.unwrap().actual_weight.unwrap(), TestWeightInfo::receive_messages_delivery_proof_weight( @@ -467,6 +474,7 @@ fn receive_messages_delivery_proof_rewards_relayers() { }, ); assert_ok!(result); + assert_ok!(Pallet::::do_try_state()); // even though the pre-dispatch weight was for two messages, the actual weight is // for single message only assert_eq!( @@ -852,129 +860,6 @@ fn inbound_message_details_works() { }); } -#[test] -fn on_idle_callback_respects_remaining_weight() { - run_test(|| { - send_regular_message(TEST_LANE_ID); - send_regular_message(TEST_LANE_ID); - send_regular_message(TEST_LANE_ID); - send_regular_message(TEST_LANE_ID); - - assert_ok!(Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - prepare_messages_delivery_proof( - TEST_LANE_ID, - InboundLaneData { - last_confirmed_nonce: 4, - relayers: vec![unrewarded_relayer(1, 4, TEST_RELAYER_A)].into(), - }, - ), - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - messages_in_oldest_entry: 4, - total_messages: 4, - last_delivered_nonce: 4, - }, - )); - - // all 4 messages may be pruned now - assert_eq!(outbound_lane::(TEST_LANE_ID).data().latest_received_nonce, 4); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 1); - System::::set_block_number(2); - - // if passed wight is too low to do anything - let dbw = DbWeight::get(); - assert_eq!(Pallet::::on_idle(0, dbw.reads_writes(1, 1)), Weight::zero(),); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 1); - - // if passed wight is enough to prune single message - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(1, 2)), - dbw.reads_writes(1, 2), - ); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 2); - - // if passed wight is enough to prune two more messages - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(1, 3)), - dbw.reads_writes(1, 3), - ); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 4); - - // if passed wight is enough to prune many messages - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(100, 100)), - dbw.reads_writes(1, 2), - ); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 5); - }); -} - -#[test] -fn on_idle_callback_is_rotating_lanes_to_prune() { - run_test(|| { - // send + receive confirmation for lane 1 - send_regular_message(TEST_LANE_ID); - receive_messages_delivery_proof(); - // send + receive confirmation for lane 2 - send_regular_message(TEST_LANE_ID_2); - assert_ok!(Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - prepare_messages_delivery_proof( - TEST_LANE_ID_2, - InboundLaneData { - last_confirmed_nonce: 1, - relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into(), - }, - ), - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - messages_in_oldest_entry: 1, - total_messages: 1, - last_delivered_nonce: 1, - }, - )); - - // nothing is pruned yet - assert_eq!(outbound_lane::(TEST_LANE_ID).data().latest_received_nonce, 1); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 1); - assert_eq!( - outbound_lane::(TEST_LANE_ID_2).data().latest_received_nonce, - 1 - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, - 1 - ); - - // in block#2.on_idle lane messages of lane 1 are pruned - let dbw = DbWeight::get(); - System::::set_block_number(2); - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(100, 100)), - dbw.reads_writes(1, 2), - ); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 2); - assert_eq!( - outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, - 1 - ); - - // in block#3.on_idle lane messages of lane 2 are pruned - System::::set_block_number(3); - - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(100, 100)), - dbw.reads_writes(1, 2), - ); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 2); - assert_eq!( - outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, - 2 - ); - }); -} - #[test] fn outbound_message_from_unconfigured_lane_is_rejected() { run_test(|| { @@ -1098,3 +983,33 @@ fn maybe_outbound_lanes_count_returns_correct_value() { Some(mock::ActiveOutboundLanes::get().len() as u32) ); } + +#[test] +fn do_try_state_for_outbound_lanes_works() { + run_test(|| { + let lane_id = TEST_LANE_ID; + + // setup delivered nonce 1 + OutboundLanes::::insert( + lane_id, + OutboundLaneData { + oldest_unpruned_nonce: 2, + latest_received_nonce: 1, + latest_generated_nonce: 0, + }, + ); + // store message for nonce 1 + OutboundMessages::::insert( + MessageKey { lane_id, nonce: 1 }, + BoundedVec::default(), + ); + assert_err!( + Pallet::::do_try_state(), + sp_runtime::TryRuntimeError::Other("Found unpruned lanes!") + ); + + // remove message for nonce 1 + OutboundMessages::::remove(MessageKey { lane_id, nonce: 1 }); + assert_ok!(Pallet::::do_try_state()); + }) +} diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/utils/Cargo.toml index beb03b9381d4..4c25566607dc 100644 --- a/bridges/relays/utils/Cargo.toml +++ b/bridges/relays/utils/Cargo.toml @@ -11,7 +11,7 @@ publish = false workspace = true [dependencies] -anyhow = { workspace = true } +anyhow = { workspace = true, default-features = true } async-std = { workspace = true } async-trait = { workspace = true } backoff = { workspace = true } diff --git a/bridges/testing/README.md b/bridges/testing/README.md index bd467a410d01..158dfd73b1ad 100644 --- a/bridges/testing/README.md +++ b/bridges/testing/README.md @@ -1,31 +1,29 @@ # Bridges Tests for Local Rococo <> Westend Bridge This folder contains [zombienet](https://github.com/paritytech/zombienet/) based integration tests for both -onchain and offchain bridges code. Due to some -[technical difficulties](https://github.com/paritytech/parity-bridges-common/pull/2649#issue-1965339051), we -are using native zombienet provider, which means that you need to build some binaries locally. +onchain and offchain bridges code. -To start those tests, you need to: +Prerequisites for running the tests locally: - download latest [zombienet release](https://github.com/paritytech/zombienet/releases); - build Polkadot binary by running `cargo build -p polkadot --release --features fast-runtime` command in the -[`polkadot-sdk`](https://github.com/paritytech/polkadot-sdk) repository clone; + [`polkadot-sdk`](https://github.com/paritytech/polkadot-sdk) repository clone; - build Polkadot Parachain binary by running `cargo build -p polkadot-parachain-bin --release` command in the -[`polkadot-sdk`](https://github.com/paritytech/polkadot-sdk) repository clone; + [`polkadot-sdk`](https://github.com/paritytech/polkadot-sdk) repository clone; - ensure that you have [`node`](https://nodejs.org/en) installed. Additionally, we'll need globally installed -`polkadot/api-cli` package (use `npm install -g @polkadot/api-cli@beta` to install it); + `polkadot/api-cli` package (use `npm install -g @polkadot/api-cli@beta` to install it); - build Substrate relay by running `cargo build -p substrate-relay --release` command in the -[`parity-bridges-common`](https://github.com/paritytech/parity-bridges-common) repository clone. + [`parity-bridges-common`](https://github.com/paritytech/parity-bridges-common) repository clone; -- copy fresh `substrate-relay` binary, built in previous point, to the `~/local_bridge_testing/bin/substrate-relay`; +- copy the `substrate-relay` binary, built in the previous step, to `~/local_bridge_testing/bin/substrate-relay`; -- change the `POLKADOT_SDK_PATH` and `ZOMBIENET_BINARY_PATH` (and ensure that the nearby variables -have correct values) in the `./run-tests.sh`. +After that, any test can be run using the `run-test.sh` command. +Example: `./run-new-test.sh 0001-asset-transfer` -After that, you could run tests with the `./run-tests.sh` command. Hopefully, it'll show the +Hopefully, it'll show the "All tests have completed successfully" message in the end. Otherwise, it'll print paths to zombienet process logs, which, in turn, may be used to track locations of all spinned relay and parachain nodes. diff --git a/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js b/bridges/testing/framework/js-helpers/only-required-headers-synced-when-active.js similarity index 94% rename from bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js rename to bridges/testing/framework/js-helpers/only-required-headers-synced-when-active.js index 8c3130e4fd96..61738a21e38e 100644 --- a/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js +++ b/bridges/testing/framework/js-helpers/only-required-headers-synced-when-active.js @@ -65,8 +65,12 @@ async function run(nodeName, networkInfo, args) { // wait until we have received + delivered messages OR until timeout await utils.pollUntil( exitAfterSeconds, - () => { return atLeastOneMessageReceived && atLeastOneMessageDelivered; }, - () => { unsubscribe(); }, + () => { + return atLeastOneMessageReceived && atLeastOneMessageDelivered; + }, + () => { + unsubscribe(); + }, () => { if (!atLeastOneMessageReceived) { throw new Error("No messages received from bridged chain"); @@ -78,4 +82,4 @@ async function run(nodeName, networkInfo, args) { ); } -module.exports = { run } +module.exports = {run} diff --git a/bridges/testing/run-new-test.sh b/bridges/testing/run-test.sh similarity index 100% rename from bridges/testing/run-new-test.sh rename to bridges/testing/run-test.sh diff --git a/bridges/testing/run-tests.sh b/bridges/testing/run-tests.sh deleted file mode 100755 index fd12b57f5334..000000000000 --- a/bridges/testing/run-tests.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/bin/bash -set -x -shopt -s nullglob - -trap "trap - SIGINT SIGTERM EXIT && killall -q -9 substrate-relay && kill -- -$$" SIGINT SIGTERM EXIT - -# run tests in range [TESTS_BEGIN; TESTS_END) -TESTS_BEGIN=1 -TESTS_END=1000 -# whether to use paths for zombienet+bridges tests container or for local testing -ZOMBIENET_DOCKER_PATHS=0 -while [ $# -ne 0 ] -do - arg="$1" - case "$arg" in - --docker) - ZOMBIENET_DOCKER_PATHS=1 - ;; - --test) - shift - TESTS_BEGIN="$1" - TESTS_END="$1" - ;; - esac - shift -done - -# assuming that we'll be using native provide && all processes will be executing locally -# (we need absolute paths here, because they're used when scripts are called by zombienet from tmp folders) -export POLKADOT_SDK_PATH=`realpath $(dirname "$0")/../..` -export BRIDGE_TESTS_FOLDER=$POLKADOT_SDK_PATH/bridges/testing/tests - -# set path to binaries -if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then - export POLKADOT_BINARY=/usr/local/bin/polkadot - export POLKADOT_PARACHAIN_BINARY=/usr/local/bin/polkadot-parachain - - export SUBSTRATE_RELAY_BINARY=/usr/local/bin/substrate-relay - export ZOMBIENET_BINARY_PATH=/usr/local/bin/zombie -else - export POLKADOT_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot - export POLKADOT_PARACHAIN_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot-parachain - - export SUBSTRATE_RELAY_BINARY=~/local_bridge_testing/bin/substrate-relay - export ZOMBIENET_BINARY_PATH=~/local_bridge_testing/bin/zombienet-linux -fi - -# check if `wait` supports -p flag -if [ `printf "$BASH_VERSION\n5.1" | sort -V | head -n 1` = "5.1" ]; then IS_BASH_5_1=1; else IS_BASH_5_1=0; fi - -# bridge configuration -export LANE_ID="00000002" - -# tests configuration -ALL_TESTS_FOLDER=`mktemp -d /tmp/bridges-zombienet-tests.XXXXX` - -function start_coproc() { - local command=$1 - local name=$2 - local logname=`basename $name` - local coproc_log=`mktemp -p $TEST_FOLDER $logname.XXXXX` - coproc COPROC { - # otherwise zombienet uses some hardcoded paths - unset RUN_IN_CONTAINER - unset ZOMBIENET_IMAGE - - $command >$coproc_log 2>&1 - } - TEST_COPROCS[$COPROC_PID, 0]=$name - TEST_COPROCS[$COPROC_PID, 1]=$coproc_log - echo "Spawned $name coprocess. StdOut + StdErr: $coproc_log" - - return $COPROC_PID -} - -# execute every test from tests folder -TEST_INDEX=$TESTS_BEGIN -while true -do - declare -A TEST_COPROCS - TEST_COPROCS_COUNT=0 - TEST_PREFIX=$(printf "%04d" $TEST_INDEX) - - # it'll be used by the `sync-exit.sh` script - export TEST_FOLDER=`mktemp -d -p $ALL_TESTS_FOLDER test-$TEST_PREFIX.XXXXX` - - # check if there are no more tests - zndsl_files=($BRIDGE_TESTS_FOLDER/$TEST_PREFIX-*.zndsl) - if [ ${#zndsl_files[@]} -eq 0 ]; then - break - fi - - # start tests - for zndsl_file in "${zndsl_files[@]}"; do - start_coproc "$ZOMBIENET_BINARY_PATH --provider native test $zndsl_file" "$zndsl_file" - echo -n "1">>$TEST_FOLDER/exit-sync - ((TEST_COPROCS_COUNT++)) - done - # wait until all tests are completed - for n in `seq 1 $TEST_COPROCS_COUNT`; do - if [ "$IS_BASH_5_1" -eq 1 ]; then - wait -n -p COPROC_PID - exit_code=$? - coproc_name=${TEST_COPROCS[$COPROC_PID, 0]} - coproc_log=${TEST_COPROCS[$COPROC_PID, 1]} - coproc_stdout=$(cat $coproc_log) - else - wait -n - exit_code=$? - coproc_name="" - coproc_stdout="" - fi - echo "Process $coproc_name has finished with exit code: $exit_code" - - # if exit code is not zero, exit - if [ $exit_code -ne 0 ]; then - echo "=====================================================================" - echo "=== Shutting down. Log of failed process below ===" - echo "=====================================================================" - echo "$coproc_stdout" - - exit 1 - fi - done - - # proceed to next index - ((TEST_INDEX++)) - if [ "$TEST_INDEX" -ge "$TESTS_END" ]; then - break - fi - - # kill relay here - it is started manually by tests - killall substrate-relay -done - -echo "=====================================================================" -echo "=== All tests have completed successfully ===" -echo "=====================================================================" diff --git a/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl deleted file mode 100644 index 07b91481dc7c..000000000000 --- a/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl +++ /dev/null @@ -1,26 +0,0 @@ -Description: While relayer is active, we only sync mandatory and required Rococo (and Rococo BH) headers to Westend BH. -Network: ../environments/rococo-westend/bridge_hub_westend_local_network.toml -Creds: config - -# step 1: initialize Westend AH -asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-westend-local" within 60 seconds - -# step 2: initialize Westend bridge hub -bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds - -# step 3: ensure that initialization has completed -asset-hub-westend-collator1: js-script ../js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds - -# step 4: send message from Westend to Rococo -asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 60 seconds - -# step 5: start relayer -# (we are starting it after sending the message to be sure that relayer won't relay messages before our js script -# will be started at step 6) -# (it is started by sibling 0003-required-headers-synced-while-active-westend-to-rococo.zndsl) - -# step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations -bridge-hub-westend-collator1: js-script ../js-helpers/only-required-headers-synced-when-active.js with "500,rococo-at-westend" within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl deleted file mode 100644 index a6b11fc24052..000000000000 --- a/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl +++ /dev/null @@ -1,26 +0,0 @@ -Description: While relayer is active, we only sync mandatory and required Westend (and Westend BH) headers to Rococo BH. -Network: ../environments/rococo-westend/bridge_hub_rococo_local_network.toml -Creds: config - -# step 1: initialize Rococo AH -asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-rococo-local" within 60 seconds - -# step 2: initialize Rococo bridge hub -bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds - -# step 3: ensure that initialization has completed -asset-hub-rococo-collator1: js-script ../js-helpers/wait-hrmp-channel-opened.js with "1013" within 600 seconds - -# step 4: send message from Rococo to Westend -asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 60 seconds - -# step 5: start relayer -# (we are starting it after sending the message to be sure that relayer won't relay messages before our js script -# will be started at step 6) -bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds - -# step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations -bridge-hub-rococo-collator1: js-script ../js-helpers/only-required-headers-synced-when-active.js with "500,westend-at-rococo" within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/bridges/testing/tests/0003-required-headers-synced-while-active/rococo-to-westend.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active/rococo-to-westend.zndsl new file mode 100644 index 000000000000..897b79eeff23 --- /dev/null +++ b/bridges/testing/tests/0003-required-headers-synced-while-active/rococo-to-westend.zndsl @@ -0,0 +1,7 @@ +Description: While relayer is active, we only sync mandatory and required Rococo (and Rococo BH) headers to Westend BH. +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# ensure that relayer won't sync any extra headers while delivering messages and confirmations +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-required-headers-synced-when-active.js with "500,rococo-at-westend" within 600 seconds + diff --git a/bridges/testing/tests/0003-required-headers-synced-while-active/run.sh b/bridges/testing/tests/0003-required-headers-synced-while-active/run.sh new file mode 100755 index 000000000000..8fad38f22052 --- /dev/null +++ b/bridges/testing/tests/0003-required-headers-synced-while-active/run.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +set -e + +# TODO: This test doesn't work. It was added at a time when we couldn't run it because we didn't have the scafolding. +# It needs to be fixed. For the moment we keep it in the repo as it is since the idea has value. +# But we don't run it in the CI. + +source "${BASH_SOURCE%/*}/../../framework/utils/common.sh" +source "${BASH_SOURCE%/*}/../../framework/utils/zombienet.sh" + +export ENV_PATH=`realpath ${BASH_SOURCE%/*}/../../environments/rococo-westend` + +logs_dir=$TEST_DIR/logs + +$ENV_PATH/spawn.sh --init & +env_pid=$! + +ensure_process_file $env_pid $TEST_DIR/rococo.env 600 +rococo_dir=`cat $TEST_DIR/rococo.env` +echo + +ensure_process_file $env_pid $TEST_DIR/westend.env 300 +westend_dir=`cat $TEST_DIR/westend.env` +echo + +echo "Sending message from Rococo to Westend" +$ENV_PATH/helper.sh auto-log reserve-transfer-assets-from-asset-hub-rococo-local 5000000000000 +echo + +echo "Sending message from Westend to Rococo" +$ENV_PATH/helper.sh auto-log reserve-transfer-assets-from-asset-hub-westend-local 5000000000000 +echo + + +# Start the relayer with a 30s delay +# We want to be sure that the messages won't be relayed before starting the js script in `rococo-to-westend.zndsl` +start_relayer_log=$logs_dir/start_relayer.log +echo -e "The rococo-westend relayer will be started in 30s. Logs will be available at: $start_relayer_log\n" +(sleep 30 && $ENV_PATH/start_relayer.sh \ + $rococo_dir $westend_dir finality_relayer_pid parachains_relayer_pid messages_relayer_pid > $start_relayer_log)& + +run_zndsl ${BASH_SOURCE%/*}/rococo-to-westend.zndsl $westend_dir + diff --git a/cumulus/client/cli/src/lib.rs b/cumulus/client/cli/src/lib.rs index a7b2eb19de88..564d7b58c94d 100644 --- a/cumulus/client/cli/src/lib.rs +++ b/cumulus/client/cli/src/lib.rs @@ -21,13 +21,13 @@ use std::{ fs, io::{self, Write}, - net::SocketAddr, path::PathBuf, sync::Arc, }; use codec::Encode; use sc_chain_spec::ChainSpec; +use sc_cli::RpcEndpoint; use sc_client_api::HeaderBackend; use sc_service::{ config::{PrometheusConfig, RpcBatchRequestConfig, TelemetryEndpoints}, @@ -423,7 +423,7 @@ impl sc_cli::CliConfiguration for NormalizedRunCmd { self.base.rpc_cors(is_dev) } - fn rpc_addr(&self, default_listen_port: u16) -> sc_cli::Result> { + fn rpc_addr(&self, default_listen_port: u16) -> sc_cli::Result>> { self.base.rpc_addr(default_listen_port) } diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index 01e07cb395a9..47e2d8572c3f 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -54,3 +54,7 @@ polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } + +[features] +# Allows collator to use full PoV size for block building +full-pov-size = [] diff --git a/cumulus/client/consensus/aura/src/collators/basic.rs b/cumulus/client/consensus/aura/src/collators/basic.rs index 0f9583cd0eb0..d843483b79fa 100644 --- a/cumulus/client/consensus/aura/src/collators/basic.rs +++ b/cumulus/client/consensus/aura/src/collators/basic.rs @@ -237,6 +237,16 @@ where .await ); + let allowed_pov_size = if cfg!(feature = "full-pov-size") { + validation_data.max_pov_size + } else { + // Set the block limit to 50% of the maximum PoV size. + // + // TODO: If we got benchmarking that includes the proof size, + // we should be able to use the maximum pov size. + validation_data.max_pov_size / 2 + } as usize; + let maybe_collation = try_request!( collator .collate( @@ -245,11 +255,7 @@ where None, (parachain_inherent_data, other_inherent_data), params.authoring_duration, - // Set the block limit to 50% of the maximum PoV size. - // - // TODO: If we got benchmarking that includes the proof size, - // we should be able to use the maximum pov size. - (validation_data.max_pov_size / 2) as usize, + allowed_pov_size, ) .await ); diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 02d60538a732..0be1e0a23ca5 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -412,6 +412,16 @@ where ) .await; + let allowed_pov_size = if cfg!(feature = "full-pov-size") { + validation_data.max_pov_size + } else { + // Set the block limit to 50% of the maximum PoV size. + // + // TODO: If we got benchmarking that includes the proof size, + // we should be able to use the maximum pov size. + validation_data.max_pov_size / 2 + } as usize; + match collator .collate( &parent_header, @@ -419,11 +429,7 @@ where None, (parachain_inherent_data, other_inherent_data), params.authoring_duration, - // Set the block limit to 50% of the maximum PoV size. - // - // TODO: If we got benchmarking that includes the proof size, - // we should be able to use the maximum pov size. - (validation_data.max_pov_size / 2) as usize, + allowed_pov_size, ) .await { diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs index 1fbc0689da86..b70cfe3841b7 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs @@ -350,6 +350,16 @@ where ) .await; + let allowed_pov_size = if cfg!(feature = "full-pov-size") { + validation_data.max_pov_size + } else { + // Set the block limit to 50% of the maximum PoV size. + // + // TODO: If we got benchmarking that includes the proof size, + // we should be able to use the maximum pov size. + validation_data.max_pov_size / 2 + } as usize; + let Ok(Some(candidate)) = collator .build_block_and_import( &parent_header, @@ -357,11 +367,7 @@ where None, (parachain_inherent_data, other_inherent_data), authoring_duration, - // Set the block limit to 50% of the maximum PoV size. - // - // TODO: If we got benchmarking that includes the proof size, - // we should be able to use the maximum pov size. - (validation_data.max_pov_size / 2) as usize, + allowed_pov_size, ) .await else { diff --git a/cumulus/client/consensus/proposer/Cargo.toml b/cumulus/client/consensus/proposer/Cargo.toml index ce91d48bf589..bb760ae03f4d 100644 --- a/cumulus/client/consensus/proposer/Cargo.toml +++ b/cumulus/client/consensus/proposer/Cargo.toml @@ -10,7 +10,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" workspace = true [dependencies] -anyhow = { workspace = true } +anyhow = { workspace = true, default-features = true } async-trait = { workspace = true } thiserror = { workspace = true } diff --git a/cumulus/pallets/collator-selection/src/lib.rs b/cumulus/pallets/collator-selection/src/lib.rs index 17dc1a552c2d..9d7e62af3c68 100644 --- a/cumulus/pallets/collator-selection/src/lib.rs +++ b/cumulus/pallets/collator-selection/src/lib.rs @@ -972,7 +972,7 @@ pub mod pallet { let result = Self::assemble_collators(); frame_system::Pallet::::register_extra_weight_unchecked( - T::WeightInfo::new_session(candidates_len_before, removed), + T::WeightInfo::new_session(removed, candidates_len_before), DispatchClass::Mandatory, ); Some(result) diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 9e0a68d09a14..bf136dc0644c 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -53,9 +53,6 @@ use polkadot_runtime_parachains::FeeTracker; use scale_info::TypeInfo; use sp_runtime::{ traits::{Block as BlockT, BlockNumberProvider, Hash}, - transaction_validity::{ - InvalidTransaction, TransactionSource, TransactionValidity, ValidTransaction, - }, BoundedSlice, FixedU128, RuntimeDebug, Saturating, }; use xcm::{latest::XcmHash, VersionedLocation, VersionedXcm}; @@ -193,7 +190,7 @@ pub mod ump_constants { pub mod pallet { use super::*; use frame_support::pallet_prelude::*; - use frame_system::{pallet_prelude::*, WeightInfo as SystemWeightInfo}; + use frame_system::pallet_prelude::*; #[pallet::pallet] #[pallet::storage_version(migration::STORAGE_VERSION)] @@ -653,52 +650,8 @@ pub mod pallet { Ok(()) } - /// Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be supplied - /// later. - /// - /// The `check_version` parameter sets a boolean flag for whether or not the runtime's spec - /// version and name should be verified on upgrade. Since the authorization only has a hash, - /// it cannot actually perform the verification. - /// - /// This call requires Root origin. - #[pallet::call_index(2)] - #[pallet::weight(::SystemWeightInfo::authorize_upgrade())] - #[allow(deprecated)] - #[deprecated( - note = "To be removed after June 2024. Migrate to `frame_system::authorize_upgrade`." - )] - pub fn authorize_upgrade( - origin: OriginFor, - code_hash: T::Hash, - check_version: bool, - ) -> DispatchResult { - ensure_root(origin)?; - frame_system::Pallet::::do_authorize_upgrade(code_hash, check_version); - Ok(()) - } - - /// Provide the preimage (runtime binary) `code` for an upgrade that has been authorized. - /// - /// If the authorization required a version check, this call will ensure the spec name - /// remains unchanged and that the spec version has increased. - /// - /// Note that this function will not apply the new `code`, but only attempt to schedule the - /// upgrade with the Relay Chain. - /// - /// All origins are allowed. - #[pallet::call_index(3)] - #[pallet::weight(::SystemWeightInfo::apply_authorized_upgrade())] - #[allow(deprecated)] - #[deprecated( - note = "To be removed after June 2024. Migrate to `frame_system::apply_authorized_upgrade`." - )] - pub fn enact_authorized_upgrade( - _: OriginFor, - code: Vec, - ) -> DispatchResultWithPostInfo { - let post = frame_system::Pallet::::do_apply_authorize_upgrade(code)?; - Ok(post) - } + // WARNING: call indices 2 and 3 were used in a former version of this pallet. Using them + // again will require to bump the transaction version of runtimes using this pallet. } #[pallet::event] @@ -951,30 +904,6 @@ pub mod pallet { sp_io::storage::set(b":c", &[]); } } - - #[pallet::validate_unsigned] - impl sp_runtime::traits::ValidateUnsigned for Pallet { - type Call = Call; - - fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { - if let Call::enact_authorized_upgrade { ref code } = call { - if let Ok(hash) = frame_system::Pallet::::validate_authorized_upgrade(&code[..]) - { - return Ok(ValidTransaction { - priority: 100, - requires: Vec::new(), - provides: vec![hash.as_ref().to_vec()], - longevity: TransactionLongevity::max_value(), - propagate: true, - }) - } - } - if let Call::set_validation_data { .. } = call { - return Ok(Default::default()) - } - Err(InvalidTransaction::Call.into()) - } - } } impl Pallet { diff --git a/cumulus/pallets/parachain-system/src/mock.rs b/cumulus/pallets/parachain-system/src/mock.rs index 7bea72224b8b..b4d118aadf04 100644 --- a/cumulus/pallets/parachain-system/src/mock.rs +++ b/cumulus/pallets/parachain-system/src/mock.rs @@ -49,9 +49,9 @@ type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - ParachainSystem: parachain_system::{Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned}, - MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + System: frame_system, + ParachainSystem: parachain_system, + MessageQueue: pallet_message_queue, } ); diff --git a/cumulus/pallets/parachain-system/src/tests.rs b/cumulus/pallets/parachain-system/src/tests.rs index 51c6e83c1131..548231966e42 100755 --- a/cumulus/pallets/parachain-system/src/tests.rs +++ b/cumulus/pallets/parachain-system/src/tests.rs @@ -1127,10 +1127,8 @@ fn upgrade_version_checks_should_work() { let new_code = vec![1, 2, 3, 4]; let new_code_hash = H256(sp_crypto_hashing::blake2_256(&new_code)); - #[allow(deprecated)] - let _authorize = ParachainSystem::authorize_upgrade(RawOrigin::Root.into(), new_code_hash, true); - #[allow(deprecated)] - let res = ParachainSystem::enact_authorized_upgrade(RawOrigin::None.into(), new_code); + let _authorize = System::authorize_upgrade(RawOrigin::Root.into(), new_code_hash); + let res = System::apply_authorized_upgrade(RawOrigin::None.into(), new_code); assert_eq!(expected.map_err(DispatchErrorWithPostInfo::from), res); }); diff --git a/cumulus/pallets/xcmp-queue/src/mock.rs b/cumulus/pallets/xcmp-queue/src/mock.rs index 7fb96de7a4ea..348939de1f14 100644 --- a/cumulus/pallets/xcmp-queue/src/mock.rs +++ b/cumulus/pallets/xcmp-queue/src/mock.rs @@ -45,7 +45,7 @@ frame_support::construct_runtime!( System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, ParachainSystem: cumulus_pallet_parachain_system::{ - Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned, + Pallet, Call, Config, Storage, Inherent, Event, }, XcmpQueue: xcmp_queue::{Pallet, Call, Storage, Event}, } diff --git a/cumulus/parachains/chain-specs/coretime-polkadot.json b/cumulus/parachains/chain-specs/coretime-polkadot.json index 73e104b38290..806231db7646 100644 --- a/cumulus/parachains/chain-specs/coretime-polkadot.json +++ b/cumulus/parachains/chain-specs/coretime-polkadot.json @@ -8,7 +8,9 @@ "/dns/polkadot-coretime-connect-a-0.polkadot.io/tcp/443/wss/p2p/12D3KooWKjnixAHbKMsPTJwGx8SrBeGEJLHA8KmKcEDYMp3YmWgR", "/dns/polkadot-coretime-connect-a-1.polkadot.io/tcp/443/wss/p2p/12D3KooWQ7B7p4DFv1jWqaKfhrZBcMmi5g8bWFnmskguLaGEmT6n", "/dns4/coretime-polkadot.boot.stake.plus/tcp/30332/wss/p2p/12D3KooWFJ2yBTKFKYwgKUjfY3F7XfaxHV8hY6fbJu5oMkpP7wZ9", - "/dns4/coretime-polkadot.boot.stake.plus/tcp/31332/wss/p2p/12D3KooWCy5pToLafcQzPHn5kadxAftmF6Eh8ZJGPXhSeXSUDfjv" + "/dns4/coretime-polkadot.boot.stake.plus/tcp/31332/wss/p2p/12D3KooWCy5pToLafcQzPHn5kadxAftmF6Eh8ZJGPXhSeXSUDfjv", + "/dns/coretime-polkadot-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWGpmytHjdthrkKgkXDZyKm9ABtJ2PtGk9NStJDG4pChy9", + "/dns/coretime-polkadot-boot-ng.dwellir.com/tcp/30361/p2p/12D3KooWGpmytHjdthrkKgkXDZyKm9ABtJ2PtGk9NStJDG4pChy9" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index 98df41090a40..0143c09036d2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -258,4 +258,4 @@ metadata-hash = ["substrate-wasm-builder/metadata-hash"] # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["metadata-hash", "sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash"] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 2f244a07e8f1..77130ff846b5 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -262,4 +262,4 @@ metadata-hash = ["substrate-wasm-builder/metadata-hash"] # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["metadata-hash", "sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash"] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index f85861bdaa54..6d0fbd7d5c66 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -305,4 +305,4 @@ fast-runtime = [] # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["sp-api/disable-logging"] +on-chain-release-build = [] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_rococo_bulletin.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_rococo_bulletin.rs index 5522a325f192..d0a7ed25363d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_rococo_bulletin.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_rococo_bulletin.rs @@ -17,7 +17,7 @@ //! Autogenerated weights for `pallet_bridge_messages` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-07-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-07-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `runner-7wrmsoux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 @@ -60,8 +60,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `654` // Estimated: `52645` - // Minimum execution time: 37_206_000 picoseconds. - Weight::from_parts(38_545_000, 0) + // Minimum execution time: 36_836_000 picoseconds. + Weight::from_parts(37_858_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) @@ -80,11 +80,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `654` // Estimated: `52645` - // Minimum execution time: 37_075_000 picoseconds. - Weight::from_parts(37_757_000, 0) + // Minimum execution time: 36_587_000 picoseconds. + Weight::from_parts(37_516_000, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 5_776 - .saturating_add(Weight::from_parts(11_586_768, 0).saturating_mul(n.into())) + // Standard Error: 8_655 + .saturating_add(Weight::from_parts(11_649_169, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -100,8 +100,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `654` // Estimated: `52645` - // Minimum execution time: 42_087_000 picoseconds. - Weight::from_parts(42_970_000, 0) + // Minimum execution time: 42_157_000 picoseconds. + Weight::from_parts(43_105_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) @@ -120,11 +120,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `654` // Estimated: `52645` - // Minimum execution time: 35_055_000 picoseconds. - Weight::from_parts(36_987_740, 0) + // Minimum execution time: 35_536_000 picoseconds. + Weight::from_parts(37_452_828, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(2_316, 0).saturating_mul(n.into())) + // Standard Error: 3 + .saturating_add(Weight::from_parts(2_269, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -134,15 +134,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) /// Storage: `BridgePolkadotBulletinMessages::OutboundLanes` (r:1 w:1) /// Proof: `BridgePolkadotBulletinMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinMessages::OutboundMessages` (r:0 w:1) + /// Proof: `BridgePolkadotBulletinMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_single_message() -> Weight { // Proof Size summary in bytes: // Measured: `621` // Estimated: `2543` - // Minimum execution time: 24_326_000 picoseconds. - Weight::from_parts(25_169_000, 0) + // Minimum execution time: 25_800_000 picoseconds. + Weight::from_parts(26_666_000, 0) .saturating_add(Weight::from_parts(0, 2543)) .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -150,15 +152,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) /// Storage: `BridgePolkadotBulletinMessages::OutboundLanes` (r:1 w:1) /// Proof: `BridgePolkadotBulletinMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinMessages::OutboundMessages` (r:0 w:2) + /// Proof: `BridgePolkadotBulletinMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight { // Proof Size summary in bytes: // Measured: `621` // Estimated: `2543` - // Minimum execution time: 24_484_000 picoseconds. - Weight::from_parts(25_130_000, 0) + // Minimum execution time: 27_262_000 picoseconds. + Weight::from_parts(27_997_000, 0) .saturating_add(Weight::from_parts(0, 2543)) .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -166,15 +170,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) /// Storage: `BridgePolkadotBulletinMessages::OutboundLanes` (r:1 w:1) /// Proof: `BridgePolkadotBulletinMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinMessages::OutboundMessages` (r:0 w:2) + /// Proof: `BridgePolkadotBulletinMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight { // Proof Size summary in bytes: // Measured: `621` // Estimated: `2543` - // Minimum execution time: 24_450_000 picoseconds. - Weight::from_parts(25_164_000, 0) + // Minimum execution time: 26_992_000 picoseconds. + Weight::from_parts(27_921_000, 0) .saturating_add(Weight::from_parts(0, 2543)) .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -204,11 +210,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `813` // Estimated: `52645` - // Minimum execution time: 54_317_000 picoseconds. - Weight::from_parts(59_171_547, 0) + // Minimum execution time: 55_509_000 picoseconds. + Weight::from_parts(59_826_763, 0) .saturating_add(Weight::from_parts(0, 52645)) // Standard Error: 7 - .saturating_add(Weight::from_parts(7_566, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(7_565, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_westend.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_westend.rs index 9c05dae979da..dc6c917c6d00 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_westend.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_westend.rs @@ -17,7 +17,7 @@ //! Autogenerated weights for `pallet_bridge_messages` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-07-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-07-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `runner-7wrmsoux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 @@ -62,8 +62,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `658` // Estimated: `52645` - // Minimum execution time: 41_396_000 picoseconds. - Weight::from_parts(43_141_000, 0) + // Minimum execution time: 40_198_000 picoseconds. + Weight::from_parts(42_079_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -84,11 +84,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `658` // Estimated: `52645` - // Minimum execution time: 41_095_000 picoseconds. - Weight::from_parts(42_030_000, 0) + // Minimum execution time: 39_990_000 picoseconds. + Weight::from_parts(41_381_000, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 5_702 - .saturating_add(Weight::from_parts(11_627_951, 0).saturating_mul(n.into())) + // Standard Error: 8_459 + .saturating_add(Weight::from_parts(11_710_167, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -106,8 +106,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `658` // Estimated: `52645` - // Minimum execution time: 45_912_000 picoseconds. - Weight::from_parts(47_564_000, 0) + // Minimum execution time: 45_940_000 picoseconds. + Weight::from_parts(47_753_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -128,11 +128,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `658` // Estimated: `52645` - // Minimum execution time: 39_175_000 picoseconds. - Weight::from_parts(41_674_095, 0) + // Minimum execution time: 39_067_000 picoseconds. + Weight::from_parts(41_787_019, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(2_305, 0).saturating_mul(n.into())) + // Standard Error: 5 + .saturating_add(Weight::from_parts(2_295, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -146,15 +146,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: UNKNOWN KEY `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::OutboundMessages` (r:0 w:1) + /// Proof: `BridgeWestendMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_single_message() -> Weight { // Proof Size summary in bytes: // Measured: `501` // Estimated: `3966` - // Minimum execution time: 32_033_000 picoseconds. - Weight::from_parts(33_131_000, 0) + // Minimum execution time: 33_107_000 picoseconds. + Weight::from_parts(34_364_000, 0) .saturating_add(Weight::from_parts(0, 3966)) .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeWestendMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -166,15 +168,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: UNKNOWN KEY `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::OutboundMessages` (r:0 w:2) + /// Proof: `BridgeWestendMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight { // Proof Size summary in bytes: // Measured: `501` // Estimated: `3966` - // Minimum execution time: 32_153_000 picoseconds. - Weight::from_parts(33_126_000, 0) + // Minimum execution time: 34_826_000 picoseconds. + Weight::from_parts(35_563_000, 0) .saturating_add(Weight::from_parts(0, 3966)) .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeWestendMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -186,15 +190,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: UNKNOWN KEY `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) /// Storage: `BridgeRelayers::RelayerRewards` (r:2 w:2) /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::OutboundMessages` (r:0 w:2) + /// Proof: `BridgeWestendMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight { // Proof Size summary in bytes: // Measured: `501` // Estimated: `6086` - // Minimum execution time: 36_387_000 picoseconds. - Weight::from_parts(37_396_000, 0) + // Minimum execution time: 38_725_000 picoseconds. + Weight::from_parts(39_727_000, 0) .saturating_add(Weight::from_parts(0, 6086)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeWestendMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -224,11 +230,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `789` // Estimated: `52645` - // Minimum execution time: 56_562_000 picoseconds. - Weight::from_parts(61_452_871, 0) + // Minimum execution time: 56_892_000 picoseconds. + Weight::from_parts(61_941_659, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 9 - .saturating_add(Weight::from_parts(7_587, 0).saturating_mul(n.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(7_580, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index a9381501359e..1c9d8c0207b9 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -292,6 +292,6 @@ try-runtime = [ # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["sp-api/disable-logging"] +on-chain-release-build = [] fast-runtime = [] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs index 386342d7ea5d..1033387b527e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs @@ -17,7 +17,7 @@ //! Autogenerated weights for `pallet_bridge_messages` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-07-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-07-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `runner-7wrmsoux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 @@ -62,8 +62,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `522` // Estimated: `52645` - // Minimum execution time: 40_748_000 picoseconds. - Weight::from_parts(41_836_000, 0) + // Minimum execution time: 40_289_000 picoseconds. + Weight::from_parts(42_150_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -83,11 +83,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `522` // Estimated: `52645` - // Minimum execution time: 40_923_000 picoseconds. - Weight::from_parts(41_287_000, 0) + // Minimum execution time: 40_572_000 picoseconds. + Weight::from_parts(41_033_000, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 9_774 - .saturating_add(Weight::from_parts(11_469_207, 0).saturating_mul(n.into())) + // Standard Error: 12_000 + .saturating_add(Weight::from_parts(11_710_588, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -105,8 +105,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `522` // Estimated: `52645` - // Minimum execution time: 45_946_000 picoseconds. - Weight::from_parts(47_547_000, 0) + // Minimum execution time: 46_655_000 picoseconds. + Weight::from_parts(49_576_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -126,11 +126,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `522` // Estimated: `52645` - // Minimum execution time: 39_668_000 picoseconds. - Weight::from_parts(41_908_980, 0) + // Minimum execution time: 40_245_000 picoseconds. + Weight::from_parts(43_461_320, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 11 - .saturating_add(Weight::from_parts(2_209, 0).saturating_mul(n.into())) + // Standard Error: 21 + .saturating_add(Weight::from_parts(2_246, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -144,15 +144,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: UNKNOWN KEY `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::OutboundMessages` (r:0 w:1) + /// Proof: `BridgeRococoMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_single_message() -> Weight { // Proof Size summary in bytes: // Measured: `357` // Estimated: `3822` - // Minimum execution time: 30_544_000 picoseconds. - Weight::from_parts(31_171_000, 0) + // Minimum execution time: 32_001_000 picoseconds. + Weight::from_parts(32_842_000, 0) .saturating_add(Weight::from_parts(0, 3822)) .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -164,15 +166,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: UNKNOWN KEY `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::OutboundMessages` (r:0 w:2) + /// Proof: `BridgeRococoMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight { // Proof Size summary in bytes: // Measured: `357` // Estimated: `3822` - // Minimum execution time: 30_593_000 picoseconds. - Weight::from_parts(31_261_000, 0) + // Minimum execution time: 33_287_000 picoseconds. + Weight::from_parts(33_769_000, 0) .saturating_add(Weight::from_parts(0, 3822)) .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -184,15 +188,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: UNKNOWN KEY `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) /// Storage: `BridgeRelayers::RelayerRewards` (r:2 w:2) /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::OutboundMessages` (r:0 w:2) + /// Proof: `BridgeRococoMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight { // Proof Size summary in bytes: // Measured: `357` // Estimated: `6086` - // Minimum execution time: 34_682_000 picoseconds. - Weight::from_parts(35_277_000, 0) + // Minimum execution time: 37_136_000 picoseconds. + Weight::from_parts(38_294_000, 0) .saturating_add(Weight::from_parts(0, 6086)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -221,11 +227,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `653` // Estimated: `52645` - // Minimum execution time: 56_465_000 picoseconds. - Weight::from_parts(61_575_775, 0) + // Minimum execution time: 55_942_000 picoseconds. + Weight::from_parts(60_615_769, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 15 - .saturating_add(Weight::from_parts(7_197, 0).saturating_mul(n.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(7_225, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index 43fc9083937c..e98508ea02e6 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -243,4 +243,4 @@ std = [ # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["sp-api/disable-logging"] +on-chain-release-build = [] diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml index 1fcebb3f16a9..dfa75b8d3cf3 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml @@ -203,4 +203,4 @@ try-runtime = [ # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["sp-api/disable-logging"] +on-chain-release-build = [] diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index 2920bc428d90..07d133c80be7 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -209,4 +209,4 @@ metadata-hash = ["substrate-wasm-builder/metadata-hash"] # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["metadata-hash", "sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash"] diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index 07a4332800d7..5029c82f971d 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -206,4 +206,4 @@ metadata-hash = ["substrate-wasm-builder/metadata-hash"] # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["metadata-hash", "sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash"] diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml index d20b62a557b9..09b4ef679d24 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml @@ -136,4 +136,4 @@ try-runtime = [ # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["sp-api/disable-logging"] +on-chain-release-build = [] diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index a732bec2352d..c676587b1de4 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -191,3 +191,8 @@ try-runtime = [ "polkadot-runtime-common/try-runtime", "sp-runtime/try-runtime", ] + +# A feature that should be enabled when the runtime should be built for on-chain +# deployment. This will disable stuff that shouldn't be part of the on-chain wasm +# to make it smaller, like logging for example. +on-chain-release-build = [] diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index 20c7e691ebc8..ab7dd04bb78a 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -191,3 +191,8 @@ try-runtime = [ "polkadot-runtime-common/try-runtime", "sp-runtime/try-runtime", ] + +# A feature that should be enabled when the runtime should be built for on-chain +# deployment. This will disable stuff that shouldn't be part of the on-chain wasm +# to make it smaller, like logging for example. +on-chain-release-build = [] diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml index a0ad248bb704..9c905c876277 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml @@ -134,3 +134,8 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", ] + +# A feature that should be enabled when the runtime should be built for on-chain +# deployment. This will disable stuff that shouldn't be part of the on-chain wasm +# to make it smaller, like logging for example. +on-chain-release-build = [] diff --git a/cumulus/polkadot-parachain/chain-specs/contracts-rococo.json b/cumulus/polkadot-parachain/chain-specs/contracts-rococo.json deleted file mode 120000 index b9f8e8f31e84..000000000000 --- a/cumulus/polkadot-parachain/chain-specs/contracts-rococo.json +++ /dev/null @@ -1 +0,0 @@ -../../parachains/chain-specs/contracts-rococo.json \ No newline at end of file diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml b/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml index 09bde034cf26..066cbfae53ae 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml @@ -38,6 +38,7 @@ sc-consensus = { workspace = true, default-features = true } frame-support = { optional = true, workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } +sc-client-db = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } @@ -105,6 +106,7 @@ runtime-benchmarks = [ "parachains-common/runtime-benchmarks", "polkadot-cli/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", + "sc-client-db/runtime-benchmarks", "sc-service/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs index 2aa2b10fbb67..15d21235d1a1 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs @@ -25,10 +25,10 @@ use clap::{Command, CommandFactory, FromArgMatches}; use sc_chain_spec::ChainSpec; use sc_cli::{ CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, NetworkParams, - SharedParams, SubstrateCli, + RpcEndpoint, SharedParams, SubstrateCli, }; use sc_service::{config::PrometheusConfig, BasePath}; -use std::{fmt::Debug, marker::PhantomData, net::SocketAddr, path::PathBuf}; +use std::{fmt::Debug, marker::PhantomData, path::PathBuf}; /// Trait that can be used to customize some of the customer-facing info related to the node binary /// that is being built using this library. @@ -36,8 +36,10 @@ use std::{fmt::Debug, marker::PhantomData, net::SocketAddr, path::PathBuf}; /// The related info is shown to the customer as part of logs or help messages. /// It does not impact functionality. pub trait CliConfig { + /// The version of the resulting node binary. fn impl_version() -> String; + /// The description of the resulting node binary. fn description(executable_name: String) -> String { format!( "The command-line arguments provided first will be passed to the parachain node, \n\ @@ -50,10 +52,13 @@ pub trait CliConfig { ) } + /// The author of the resulting node binary. fn author() -> String; + /// The support URL for the resulting node binary. fn support_url() -> String; + /// The starting copyright year of the resulting node binary. fn copyright_start_year() -> u16; } @@ -300,7 +305,7 @@ impl CliConfiguration for RelayChainCli { .or_else(|| self.base_path.clone().map(Into::into))) } - fn rpc_addr(&self, default_listen_port: u16) -> sc_cli::Result> { + fn rpc_addr(&self, default_listen_port: u16) -> sc_cli::Result>> { self.base.base.rpc_addr(default_listen_port) } diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs index 7f915b729e0a..320511ece5e5 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs @@ -14,8 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -#[cfg(feature = "runtime-benchmarks")] -use crate::service::Block; use crate::{ cli::{Cli, RelayChainCli, Subcommand}, common::{ @@ -24,32 +22,56 @@ use crate::{ AuraConsensusId, Consensus, Runtime, RuntimeResolver as RuntimeResolverT, RuntimeResolver, }, - NodeExtraArgs, - }, - fake_runtime_api::{ - asset_hub_polkadot_aura::RuntimeApi as AssetHubPolkadotRuntimeApi, - aura::RuntimeApi as AuraRuntimeApi, + spec::DynNodeSpec, + types::Block, + NodeBlock, NodeExtraArgs, }, - service::{new_aura_node_spec, DynNodeSpec, ShellNode}, + fake_runtime_api, + runtime::BlockNumber, + service::ShellNode, }; #[cfg(feature = "runtime-benchmarks")] use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions; use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; use log::info; -use parachains_common::{AssetHubPolkadotAuraId, AuraId}; use sc_cli::{Result, SubstrateCli}; use sp_runtime::traits::AccountIdConversion; #[cfg(feature = "runtime-benchmarks")] use sp_runtime::traits::HashingFor; +use std::panic::{RefUnwindSafe, UnwindSafe}; /// Structure that can be used in order to provide customizers for different functionalities of the /// node binary that is being built using this library. pub struct RunConfig { + /// A custom chain spec loader. pub chain_spec_loader: Box, + /// A custom runtime resolver. pub runtime_resolver: Box, } +pub fn new_aura_node_spec( + aura_id: AuraConsensusId, + extra_args: &NodeExtraArgs, +) -> Box +where + Block: NodeBlock + UnwindSafe + RefUnwindSafe, + Block::BoundedHeader: UnwindSafe + RefUnwindSafe, +{ + match aura_id { + AuraConsensusId::Sr25519 => crate::service::new_aura_node_spec::< + Block, + fake_runtime_api::aura_sr25519::RuntimeApi, + sp_consensus_aura::sr25519::AuthorityId, + >(extra_args), + AuraConsensusId::Ed25519 => crate::service::new_aura_node_spec::< + Block, + fake_runtime_api::aura_ed25519::RuntimeApi, + sp_consensus_aura::ed25519::AuthorityId, + >(extra_args), + } +} + fn new_node_spec( config: &sc_service::Configuration, runtime_resolver: &Box, @@ -59,11 +81,11 @@ fn new_node_spec( Ok(match runtime { Runtime::Shell => Box::new(ShellNode), - Runtime::Omni(consensus) => match consensus { - Consensus::Aura(AuraConsensusId::Sr25519) => - new_aura_node_spec::(extra_args), - Consensus::Aura(AuraConsensusId::Ed25519) => - new_aura_node_spec::(extra_args), + Runtime::Omni(block_number, consensus) => match (block_number, consensus) { + (BlockNumber::U32, Consensus::Aura(aura_id)) => + new_aura_node_spec::>(aura_id, extra_args), + (BlockNumber::U64, Consensus::Aura(aura_id)) => + new_aura_node_spec::>(aura_id, extra_args), }, }) } @@ -156,7 +178,7 @@ pub fn run(cmd_config: RunConfig) -> Result<() match cmd { #[cfg(feature = "runtime-benchmarks")] BenchmarkCmd::Pallet(cmd) => runner.sync_run(|config| { - cmd.run_with_spec::, ReclaimHostFunctions>(Some( + cmd.run_with_spec::>, ReclaimHostFunctions>(Some( config.chain_spec, )) }), diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/command.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/command.rs new file mode 100644 index 000000000000..e2826826d40e --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/command.rs @@ -0,0 +1,161 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use crate::common::spec::NodeSpec; +use cumulus_client_cli::ExportGenesisHeadCommand; +use frame_benchmarking_cli::BlockCmd; +#[cfg(any(feature = "runtime-benchmarks"))] +use frame_benchmarking_cli::StorageCmd; +use sc_cli::{CheckBlockCmd, ExportBlocksCmd, ExportStateCmd, ImportBlocksCmd, RevertCmd}; +use sc_service::{Configuration, TaskManager}; +use std::{future::Future, pin::Pin}; + +type SyncCmdResult = sc_cli::Result<()>; + +type AsyncCmdResult<'a> = + sc_cli::Result<(Pin + 'a>>, TaskManager)>; + +pub trait NodeCommandRunner { + fn prepare_check_block_cmd( + self: Box, + config: Configuration, + cmd: &CheckBlockCmd, + ) -> AsyncCmdResult<'_>; + + fn prepare_export_blocks_cmd( + self: Box, + config: Configuration, + cmd: &ExportBlocksCmd, + ) -> AsyncCmdResult<'_>; + + fn prepare_export_state_cmd( + self: Box, + config: Configuration, + cmd: &ExportStateCmd, + ) -> AsyncCmdResult<'_>; + + fn prepare_import_blocks_cmd( + self: Box, + config: Configuration, + cmd: &ImportBlocksCmd, + ) -> AsyncCmdResult<'_>; + + fn prepare_revert_cmd( + self: Box, + config: Configuration, + cmd: &RevertCmd, + ) -> AsyncCmdResult<'_>; + + fn run_export_genesis_head_cmd( + self: Box, + config: Configuration, + cmd: &ExportGenesisHeadCommand, + ) -> SyncCmdResult; + + fn run_benchmark_block_cmd( + self: Box, + config: Configuration, + cmd: &BlockCmd, + ) -> SyncCmdResult; + + #[cfg(any(feature = "runtime-benchmarks"))] + fn run_benchmark_storage_cmd( + self: Box, + config: Configuration, + cmd: &StorageCmd, + ) -> SyncCmdResult; +} + +impl NodeCommandRunner for T +where + T: NodeSpec, +{ + fn prepare_check_block_cmd( + self: Box, + config: Configuration, + cmd: &CheckBlockCmd, + ) -> AsyncCmdResult<'_> { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + Ok((Box::pin(cmd.run(partial.client, partial.import_queue)), partial.task_manager)) + } + + fn prepare_export_blocks_cmd( + self: Box, + config: Configuration, + cmd: &ExportBlocksCmd, + ) -> AsyncCmdResult<'_> { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + Ok((Box::pin(cmd.run(partial.client, config.database)), partial.task_manager)) + } + + fn prepare_export_state_cmd( + self: Box, + config: Configuration, + cmd: &ExportStateCmd, + ) -> AsyncCmdResult<'_> { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + Ok((Box::pin(cmd.run(partial.client, config.chain_spec)), partial.task_manager)) + } + + fn prepare_import_blocks_cmd( + self: Box, + config: Configuration, + cmd: &ImportBlocksCmd, + ) -> AsyncCmdResult<'_> { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + Ok((Box::pin(cmd.run(partial.client, partial.import_queue)), partial.task_manager)) + } + + fn prepare_revert_cmd( + self: Box, + config: Configuration, + cmd: &RevertCmd, + ) -> AsyncCmdResult<'_> { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + Ok((Box::pin(cmd.run(partial.client, partial.backend, None)), partial.task_manager)) + } + + fn run_export_genesis_head_cmd( + self: Box, + config: Configuration, + cmd: &ExportGenesisHeadCommand, + ) -> SyncCmdResult { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + cmd.run(partial.client) + } + + fn run_benchmark_block_cmd( + self: Box, + config: Configuration, + cmd: &BlockCmd, + ) -> SyncCmdResult { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + cmd.run(partial.client) + } + + #[cfg(any(feature = "runtime-benchmarks"))] + fn run_benchmark_storage_cmd( + self: Box, + config: Configuration, + cmd: &StorageCmd, + ) -> SyncCmdResult { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + let db = partial.backend.expose_db(); + let storage = partial.backend.expose_storage(); + + cmd.run(config, partial.client, db, storage) + } +} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs index 89bc7511dac3..907f09263fc1 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs @@ -20,15 +20,43 @@ pub(crate) mod aura; pub mod chain_spec; +pub mod command; +pub mod rpc; pub mod runtime; +pub mod spec; +pub mod types; use cumulus_primitives_core::CollectCollationInfo; +use sc_client_db::DbHash; use sp_api::{ApiExt, CallApiAt, ConstructRuntimeApi, Metadata}; use sp_block_builder::BlockBuilder; -use sp_runtime::traits::Block as BlockT; +use sp_runtime::{ + traits::{Block as BlockT, BlockNumber, Header as HeaderT, NumberFor}, + OpaqueExtrinsic, +}; use sp_session::SessionKeys; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; -use std::path::PathBuf; +use std::{fmt::Debug, path::PathBuf, str::FromStr}; + +pub trait NodeBlock: + BlockT + + for<'de> serde::Deserialize<'de> +{ + type BoundedFromStrErr: Debug; + type BoundedNumber: FromStr + BlockNumber; + type BoundedHeader: HeaderT + Unpin; +} + +impl NodeBlock for T +where + T: BlockT + for<'de> serde::Deserialize<'de>, + ::Header: Unpin, + as FromStr>::Err: Debug, +{ + type BoundedFromStrErr = as FromStr>::Err; + type BoundedNumber = NumberFor; + type BoundedHeader = ::Header; +} /// Convenience trait that defines the basic bounds for the `RuntimeApi` of a parachain node. pub trait NodeRuntimeApi: diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/rpc.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/rpc.rs similarity index 59% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/rpc.rs rename to cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/rpc.rs index 283a73d931d7..a4e157e87216 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/rpc.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/rpc.rs @@ -18,16 +18,13 @@ #![warn(missing_docs)] -use crate::{ - common::ConstructNodeRuntimeApi, - service::{ParachainBackend, ParachainClient}, +use crate::common::{ + types::{AccountId, Balance, Nonce, ParachainBackend, ParachainClient}, + ConstructNodeRuntimeApi, }; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; -use parachains_common::{AccountId, Balance, Block, Nonce}; -use sc_rpc::{ - dev::{Dev, DevApiServer}, - DenyUnsafe, -}; +use sc_rpc::dev::{Dev, DevApiServer}; +use sp_runtime::traits::Block as BlockT; use std::{marker::PhantomData, sync::Arc}; use substrate_frame_rpc_system::{System, SystemApiServer}; use substrate_state_trie_migration_rpc::{StateMigration, StateMigrationApiServer}; @@ -37,60 +34,59 @@ pub type RpcExtension = jsonrpsee::RpcModule<()>; pub(crate) trait BuildRpcExtensions { fn build_rpc_extensions( - deny_unsafe: DenyUnsafe, client: Arc, backend: Arc, pool: Arc, ) -> sc_service::error::Result; } -pub(crate) struct BuildEmptyRpcExtensions(PhantomData); +pub(crate) struct BuildEmptyRpcExtensions(PhantomData<(Block, RuntimeApi)>); -impl +impl BuildRpcExtensions< - ParachainClient, - ParachainBackend, - sc_transaction_pool::FullPool>, - > for BuildEmptyRpcExtensions + ParachainClient, + ParachainBackend, + sc_transaction_pool::FullPool>, + > for BuildEmptyRpcExtensions where - RuntimeApi: ConstructNodeRuntimeApi> + Send + Sync + 'static, + RuntimeApi: + ConstructNodeRuntimeApi> + Send + Sync + 'static, { fn build_rpc_extensions( - _deny_unsafe: DenyUnsafe, - _client: Arc>, - _backend: Arc, - _pool: Arc>>, + _client: Arc>, + _backend: Arc>, + _pool: Arc>>, ) -> sc_service::error::Result { Ok(RpcExtension::new(())) } } -pub(crate) struct BuildParachainRpcExtensions(PhantomData); +pub(crate) struct BuildParachainRpcExtensions(PhantomData<(Block, RuntimeApi)>); -impl +impl BuildRpcExtensions< - ParachainClient, - ParachainBackend, - sc_transaction_pool::FullPool>, - > for BuildParachainRpcExtensions + ParachainClient, + ParachainBackend, + sc_transaction_pool::FullPool>, + > for BuildParachainRpcExtensions where - RuntimeApi: ConstructNodeRuntimeApi> + Send + Sync + 'static, + RuntimeApi: + ConstructNodeRuntimeApi> + Send + Sync + 'static, RuntimeApi::RuntimeApi: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + substrate_frame_rpc_system::AccountNonceApi, { fn build_rpc_extensions( - deny_unsafe: DenyUnsafe, - client: Arc>, - backend: Arc, - pool: Arc>>, + client: Arc>, + backend: Arc>, + pool: Arc>>, ) -> sc_service::error::Result { let build = || -> Result> { let mut module = RpcExtension::new(()); - module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; + module.merge(System::new(client.clone(), pool).into_rpc())?; module.merge(TransactionPayment::new(client.clone()).into_rpc())?; - module.merge(StateMigration::new(client.clone(), backend, deny_unsafe).into_rpc())?; - module.merge(Dev::new(client, deny_unsafe).into_rpc())?; + module.merge(StateMigration::new(client.clone(), backend).into_rpc())?; + module.merge(Dev::new(client).into_rpc())?; Ok(module) }; diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs index c64eda12d5ef..bddbb0a85d03 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs @@ -34,12 +34,21 @@ pub enum Consensus { Aura(AuraConsensusId), } +/// The choice of block number for the parachain omni-node. +#[derive(PartialEq)] +pub enum BlockNumber { + /// u32 + U32, + /// u64 + U64, +} + /// Helper enum listing the supported Runtime types #[derive(PartialEq)] pub enum Runtime { /// None of the system-chain runtimes, rather the node will act agnostic to the runtime ie. be /// an omni-node, and simply run a node with the given consensus algorithm. - Omni(Consensus), + Omni(BlockNumber, Consensus), /// Shell Shell, } @@ -51,11 +60,11 @@ pub trait RuntimeResolver { } /// Default implementation for `RuntimeResolver` that just returns -/// `Runtime::Omni(Consensus::Aura(AuraConsensusId::Sr25519))`. +/// `Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519))`. pub struct DefaultRuntimeResolver; impl RuntimeResolver for DefaultRuntimeResolver { fn runtime(&self, _chain_spec: &dyn ChainSpec) -> sc_cli::Result { - Ok(Runtime::Omni(Consensus::Aura(AuraConsensusId::Sr25519))) + Ok(Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519))) } } diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs new file mode 100644 index 000000000000..55e042aed87e --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs @@ -0,0 +1,392 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use crate::common::{ + command::NodeCommandRunner, + rpc::BuildRpcExtensions, + types::{ + ParachainBackend, ParachainBlockImport, ParachainClient, ParachainHostFunctions, + ParachainService, + }, + ConstructNodeRuntimeApi, NodeBlock, NodeExtraArgs, +}; +use cumulus_client_cli::CollatorOptions; +use cumulus_client_service::{ + build_network, build_relay_chain_interface, prepare_node_config, start_relay_chain_tasks, + BuildNetworkParams, CollatorSybilResistance, DARecoveryProfile, StartRelayChainTasksParams, +}; +use cumulus_primitives_core::{BlockT, ParaId}; +use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface}; +use parachains_common::Hash; +use polkadot_primitives::CollatorPair; +use prometheus_endpoint::Registry; +use sc_consensus::DefaultImportQueue; +use sc_executor::{HeapAllocStrategy, DEFAULT_HEAP_ALLOC_STRATEGY}; +use sc_network::{config::FullNetworkConfiguration, NetworkBackend, NetworkBlock}; +use sc_service::{Configuration, ImportQueue, PartialComponents, TaskManager}; +use sc_sysinfo::HwBench; +use sc_telemetry::{TelemetryHandle, TelemetryWorker}; +use sc_transaction_pool::FullPool; +use sp_keystore::KeystorePtr; +use std::{future::Future, pin::Pin, sync::Arc, time::Duration}; + +pub(crate) trait BuildImportQueue { + fn build_import_queue( + client: Arc>, + block_import: ParachainBlockImport, + config: &Configuration, + telemetry_handle: Option, + task_manager: &TaskManager, + ) -> sc_service::error::Result>; +} + +pub(crate) trait StartConsensus +where + RuntimeApi: ConstructNodeRuntimeApi>, +{ + fn start_consensus( + client: Arc>, + block_import: ParachainBlockImport, + prometheus_registry: Option<&Registry>, + telemetry: Option, + task_manager: &TaskManager, + relay_chain_interface: Arc, + transaction_pool: Arc>>, + keystore: KeystorePtr, + relay_chain_slot_duration: Duration, + para_id: ParaId, + collator_key: CollatorPair, + overseer_handle: OverseerHandle, + announce_block: Arc>) + Send + Sync>, + backend: Arc>, + node_extra_args: NodeExtraArgs, + ) -> Result<(), sc_service::Error>; +} + +/// Checks that the hardware meets the requirements and print a warning otherwise. +fn warn_if_slow_hardware(hwbench: &sc_sysinfo::HwBench) { + // Polkadot para-chains should generally use these requirements to ensure that the relay-chain + // will not take longer than expected to import its blocks. + if let Err(err) = frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE.check_hardware(hwbench) { + log::warn!( + "⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\ + https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware", + err + ); + } +} + +pub(crate) trait NodeSpec { + type Block: NodeBlock; + + type RuntimeApi: ConstructNodeRuntimeApi< + Self::Block, + ParachainClient, + >; + + type BuildImportQueue: BuildImportQueue; + + type BuildRpcExtensions: BuildRpcExtensions< + ParachainClient, + ParachainBackend, + FullPool>, + >; + + type StartConsensus: StartConsensus; + + const SYBIL_RESISTANCE: CollatorSybilResistance; + + /// Starts a `ServiceBuilder` for a full service. + /// + /// Use this macro if you don't actually need the full service, but just the builder in order to + /// be able to perform chain operations. + fn new_partial( + config: &Configuration, + ) -> sc_service::error::Result> { + let telemetry = config + .telemetry_endpoints + .clone() + .filter(|x| !x.is_empty()) + .map(|endpoints| -> Result<_, sc_telemetry::Error> { + let worker = TelemetryWorker::new(16)?; + let telemetry = worker.handle().new_telemetry(endpoints); + Ok((worker, telemetry)) + }) + .transpose()?; + + let heap_pages = config.default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| { + HeapAllocStrategy::Static { extra_pages: h as _ } + }); + + let executor = sc_executor::WasmExecutor::::builder() + .with_execution_method(config.wasm_method) + .with_max_runtime_instances(config.max_runtime_instances) + .with_runtime_cache_size(config.runtime_cache_size) + .with_onchain_heap_alloc_strategy(heap_pages) + .with_offchain_heap_alloc_strategy(heap_pages) + .build(); + + let (client, backend, keystore_container, task_manager) = + sc_service::new_full_parts_record_import::( + config, + telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), + executor, + true, + )?; + let client = Arc::new(client); + + let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle()); + + let telemetry = telemetry.map(|(worker, telemetry)| { + task_manager.spawn_handle().spawn("telemetry", None, worker.run()); + telemetry + }); + + let transaction_pool = sc_transaction_pool::BasicPool::new_full( + config.transaction_pool.clone(), + config.role.is_authority().into(), + config.prometheus_registry(), + task_manager.spawn_essential_handle(), + client.clone(), + ); + + let block_import = ParachainBlockImport::new(client.clone(), backend.clone()); + + let import_queue = Self::BuildImportQueue::build_import_queue( + client.clone(), + block_import.clone(), + config, + telemetry.as_ref().map(|telemetry| telemetry.handle()), + &task_manager, + )?; + + Ok(PartialComponents { + backend, + client, + import_queue, + keystore_container, + task_manager, + transaction_pool, + select_chain: (), + other: (block_import, telemetry, telemetry_worker_handle), + }) + } + + /// Start a node with the given parachain spec. + /// + /// This is the actual implementation that is abstract over the executor and the runtime api. + fn start_node( + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + para_id: ParaId, + hwbench: Option, + node_extra_args: NodeExtraArgs, + ) -> Pin>>> + where + Net: NetworkBackend, + { + Box::pin(async move { + let parachain_config = prepare_node_config(parachain_config); + + let params = Self::new_partial(¶chain_config)?; + let (block_import, mut telemetry, telemetry_worker_handle) = params.other; + + let client = params.client.clone(); + let backend = params.backend.clone(); + + let mut task_manager = params.task_manager; + let (relay_chain_interface, collator_key) = build_relay_chain_interface( + polkadot_config, + ¶chain_config, + telemetry_worker_handle, + &mut task_manager, + collator_options.clone(), + hwbench.clone(), + ) + .await + .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?; + + let validator = parachain_config.role.is_authority(); + let prometheus_registry = parachain_config.prometheus_registry().cloned(); + let transaction_pool = params.transaction_pool.clone(); + let import_queue_service = params.import_queue.service(); + let net_config = FullNetworkConfiguration::<_, _, Net>::new( + ¶chain_config.network, + prometheus_registry.clone(), + ); + + let (network, system_rpc_tx, tx_handler_controller, start_network, sync_service) = + build_network(BuildNetworkParams { + parachain_config: ¶chain_config, + net_config, + client: client.clone(), + transaction_pool: transaction_pool.clone(), + para_id, + spawn_handle: task_manager.spawn_handle(), + relay_chain_interface: relay_chain_interface.clone(), + import_queue: params.import_queue, + sybil_resistance_level: Self::SYBIL_RESISTANCE, + }) + .await?; + + let rpc_builder = { + let client = client.clone(); + let transaction_pool = transaction_pool.clone(); + let backend_for_rpc = backend.clone(); + + Box::new(move |_| { + Self::BuildRpcExtensions::build_rpc_extensions( + client.clone(), + backend_for_rpc.clone(), + transaction_pool.clone(), + ) + }) + }; + + sc_service::spawn_tasks(sc_service::SpawnTasksParams { + rpc_builder, + client: client.clone(), + transaction_pool: transaction_pool.clone(), + task_manager: &mut task_manager, + config: parachain_config, + keystore: params.keystore_container.keystore(), + backend: backend.clone(), + network: network.clone(), + sync_service: sync_service.clone(), + system_rpc_tx, + tx_handler_controller, + telemetry: telemetry.as_mut(), + })?; + + if let Some(hwbench) = hwbench { + sc_sysinfo::print_hwbench(&hwbench); + if validator { + warn_if_slow_hardware(&hwbench); + } + + if let Some(ref mut telemetry) = telemetry { + let telemetry_handle = telemetry.handle(); + task_manager.spawn_handle().spawn( + "telemetry_hwbench", + None, + sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench), + ); + } + } + + let announce_block = { + let sync_service = sync_service.clone(); + Arc::new(move |hash, data| sync_service.announce_block(hash, data)) + }; + + let relay_chain_slot_duration = Duration::from_secs(6); + + let overseer_handle = relay_chain_interface + .overseer_handle() + .map_err(|e| sc_service::Error::Application(Box::new(e)))?; + + start_relay_chain_tasks(StartRelayChainTasksParams { + client: client.clone(), + announce_block: announce_block.clone(), + para_id, + relay_chain_interface: relay_chain_interface.clone(), + task_manager: &mut task_manager, + da_recovery_profile: if validator { + DARecoveryProfile::Collator + } else { + DARecoveryProfile::FullNode + }, + import_queue: import_queue_service, + relay_chain_slot_duration, + recovery_handle: Box::new(overseer_handle.clone()), + sync_service, + })?; + + if validator { + Self::StartConsensus::start_consensus( + client.clone(), + block_import, + prometheus_registry.as_ref(), + telemetry.as_ref().map(|t| t.handle()), + &task_manager, + relay_chain_interface.clone(), + transaction_pool, + params.keystore_container.keystore(), + relay_chain_slot_duration, + para_id, + collator_key.expect("Command line arguments do not allow this. qed"), + overseer_handle, + announce_block, + backend.clone(), + node_extra_args, + )?; + } + + start_network.start_network(); + + Ok(task_manager) + }) + } +} + +pub(crate) trait DynNodeSpec: NodeCommandRunner { + fn start_node( + self: Box, + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + para_id: ParaId, + hwbench: Option, + node_extra_args: NodeExtraArgs, + ) -> Pin>>>; +} + +impl DynNodeSpec for T +where + T: NodeSpec + NodeCommandRunner, +{ + fn start_node( + self: Box, + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + para_id: ParaId, + hwbench: Option, + node_extra_args: NodeExtraArgs, + ) -> Pin>>> { + match parachain_config.network.network_backend { + sc_network::config::NetworkBackendType::Libp2p => + ::start_node::>( + parachain_config, + polkadot_config, + collator_options, + para_id, + hwbench, + node_extra_args, + ), + sc_network::config::NetworkBackendType::Litep2p => + ::start_node::( + parachain_config, + polkadot_config, + collator_options, + para_id, + hwbench, + node_extra_args, + ), + } + } +} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/types.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/types.rs new file mode 100644 index 000000000000..9cfdcb22451c --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/types.rs @@ -0,0 +1,56 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use cumulus_client_consensus_common::ParachainBlockImport as TParachainBlockImport; +use cumulus_primitives_core::relay_chain::UncheckedExtrinsic; +use sc_consensus::DefaultImportQueue; +use sc_executor::WasmExecutor; +use sc_service::{PartialComponents, TFullBackend, TFullClient}; +use sc_telemetry::{Telemetry, TelemetryWorkerHandle}; +use sc_transaction_pool::FullPool; +use sp_runtime::{generic, traits::BlakeTwo256}; +use std::sync::Arc; + +pub use parachains_common::{AccountId, Balance, Hash, Nonce}; + +type Header = generic::Header; +pub type Block = generic::Block, UncheckedExtrinsic>; + +#[cfg(not(feature = "runtime-benchmarks"))] +pub type ParachainHostFunctions = cumulus_client_service::ParachainHostFunctions; +#[cfg(feature = "runtime-benchmarks")] +pub type ParachainHostFunctions = ( + cumulus_client_service::ParachainHostFunctions, + frame_benchmarking::benchmarking::HostFunctions, +); + +pub type ParachainClient = + TFullClient>; + +pub type ParachainBackend = TFullBackend; + +pub type ParachainBlockImport = + TParachainBlockImport>, ParachainBackend>; + +/// Assembly of PartialComponents (enough to run chain ops subcommands) +pub type ParachainService = PartialComponents< + ParachainClient, + ParachainBackend, + (), + DefaultImportQueue, + FullPool>, + (ParachainBlockImport, Option, Option), +>; diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/asset_hub_polkadot_aura.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/asset_hub_polkadot_aura.rs deleted file mode 100644 index f2b8b4d562b9..000000000000 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/asset_hub_polkadot_aura.rs +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. - -// Cumulus is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Cumulus is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Cumulus. If not, see . - -//! These are used to provide a type that implements these runtime APIs without requiring to import -//! the native runtimes. - -use parachains_common::{AccountId, AssetHubPolkadotAuraId, Balance, Block, Nonce}; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; -use sp_runtime::{ - traits::Block as BlockT, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, -}; -use sp_weights::Weight; - -pub struct Runtime; - -sp_api::impl_runtime_apis! { - impl sp_api::Core for Runtime { - fn version() -> sp_version::RuntimeVersion { - unimplemented!() - } - - fn execute_block(_: Block) { - unimplemented!() - } - - fn initialize_block(_: &::Header) -> sp_runtime::ExtrinsicInclusionMode { - unimplemented!() - } - } - - impl sp_api::Metadata for Runtime { - fn metadata() -> OpaqueMetadata { - unimplemented!() - } - - fn metadata_at_version(_: u32) -> Option { - unimplemented!() - } - - fn metadata_versions() -> Vec { - unimplemented!() - } - } - - impl sp_consensus_aura::AuraApi for Runtime { - fn slot_duration() -> sp_consensus_aura::SlotDuration { - unimplemented!() - } - - fn authorities() -> Vec { - unimplemented!() - } - } - - impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { - fn can_build_upon( - _: ::Hash, - _: cumulus_primitives_aura::Slot, - ) -> bool { - unimplemented!() - } - } - - impl sp_block_builder::BlockBuilder for Runtime { - fn apply_extrinsic(_: ::Extrinsic) -> ApplyExtrinsicResult { - unimplemented!() - } - - fn finalize_block() -> ::Header { - unimplemented!() - } - - fn inherent_extrinsics(_: sp_inherents::InherentData) -> Vec<::Extrinsic> { - unimplemented!() - } - - fn check_inherents(_: Block, _: sp_inherents::InherentData) -> sp_inherents::CheckInherentsResult { - unimplemented!() - } - } - - impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction( - _: TransactionSource, - _: ::Extrinsic, - _: ::Hash, - ) -> TransactionValidity { - unimplemented!() - } - } - - impl sp_session::SessionKeys for Runtime { - fn generate_session_keys(_: Option>) -> Vec { - unimplemented!() - } - - fn decode_session_keys( - _: Vec, - ) -> Option, KeyTypeId)>> { - unimplemented!() - } - } - - impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { - fn query_info( - _: ::Extrinsic, - _: u32, - ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { - unimplemented!() - } - fn query_fee_details( - _: ::Extrinsic, - _: u32, - ) -> pallet_transaction_payment::FeeDetails { - unimplemented!() - } - fn query_weight_to_fee(_: Weight) -> Balance { - unimplemented!() - } - fn query_length_to_fee(_: u32) -> Balance { - unimplemented!() - } - } - - impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info(_: &::Header) -> cumulus_primitives_core::CollationInfo { - unimplemented!() - } - } - - #[cfg(feature = "try-runtime")] - impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade(_: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { - unimplemented!() - } - - fn execute_block( - _: Block, - _: bool, - _: bool, - _: frame_try_runtime::TryStateSelect, - ) -> Weight { - unimplemented!() - } - } - - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { - fn account_nonce(_: AccountId) -> Nonce { - unimplemented!() - } - } - - #[cfg(feature = "runtime-benchmarks")] - impl frame_benchmarking::Benchmark for Runtime { - fn benchmark_metadata(_: bool) -> ( - Vec, - Vec, - ) { - unimplemented!() - } - - fn dispatch_benchmark( - _: frame_benchmarking::BenchmarkConfig - ) -> Result, sp_runtime::RuntimeString> { - unimplemented!() - } - } - - impl sp_genesis_builder::GenesisBuilder for Runtime { - fn build_state(_: Vec) -> sp_genesis_builder::Result { - unimplemented!() - } - - fn get_preset(_id: &Option) -> Option> { - unimplemented!() - } - - fn preset_names() -> Vec { - unimplemented!() - } - } -} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/aura.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/aura.rs deleted file mode 100644 index eb6d3fafa3d6..000000000000 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/aura.rs +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. - -// Cumulus is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Cumulus is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Cumulus. If not, see . - -//! These are used to provide a type that implements these runtime APIs without requiring to import -//! the native runtimes. - -use parachains_common::{AccountId, AuraId, Balance, Block, Nonce}; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; -use sp_runtime::{ - traits::Block as BlockT, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, -}; -use sp_weights::Weight; - -pub struct Runtime; - -sp_api::impl_runtime_apis! { - impl sp_api::Core for Runtime { - fn version() -> sp_version::RuntimeVersion { - unimplemented!() - } - - fn execute_block(_: Block) { - unimplemented!() - } - - fn initialize_block(_: &::Header) -> sp_runtime::ExtrinsicInclusionMode { - unimplemented!() - } - } - - impl sp_api::Metadata for Runtime { - fn metadata() -> OpaqueMetadata { - unimplemented!() - } - - fn metadata_at_version(_: u32) -> Option { - unimplemented!() - } - - fn metadata_versions() -> Vec { - unimplemented!() - } - } - - impl sp_consensus_aura::AuraApi for Runtime { - fn slot_duration() -> sp_consensus_aura::SlotDuration { - unimplemented!() - } - - fn authorities() -> Vec { - unimplemented!() - } - } - - impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { - fn can_build_upon( - _: ::Hash, - _: cumulus_primitives_aura::Slot, - ) -> bool { - unimplemented!() - } - } - - impl sp_block_builder::BlockBuilder for Runtime { - fn apply_extrinsic(_: ::Extrinsic) -> ApplyExtrinsicResult { - unimplemented!() - } - - fn finalize_block() -> ::Header { - unimplemented!() - } - - fn inherent_extrinsics(_: sp_inherents::InherentData) -> Vec<::Extrinsic> { - unimplemented!() - } - - fn check_inherents(_: Block, _: sp_inherents::InherentData) -> sp_inherents::CheckInherentsResult { - unimplemented!() - } - } - - impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction( - _: TransactionSource, - _: ::Extrinsic, - _: ::Hash, - ) -> TransactionValidity { - unimplemented!() - } - } - - impl sp_session::SessionKeys for Runtime { - fn generate_session_keys(_: Option>) -> Vec { - unimplemented!() - } - - fn decode_session_keys( - _: Vec, - ) -> Option, KeyTypeId)>> { - unimplemented!() - } - } - - impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { - fn query_info( - _: ::Extrinsic, - _: u32, - ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { - unimplemented!() - } - fn query_fee_details( - _: ::Extrinsic, - _: u32, - ) -> pallet_transaction_payment::FeeDetails { - unimplemented!() - } - fn query_weight_to_fee(_: Weight) -> Balance { - unimplemented!() - } - fn query_length_to_fee(_: u32) -> Balance { - unimplemented!() - } - } - - impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info(_: &::Header) -> cumulus_primitives_core::CollationInfo { - unimplemented!() - } - } - - #[cfg(feature = "try-runtime")] - impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade(_: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { - unimplemented!() - } - - fn execute_block( - _: Block, - _: bool, - _: bool, - _: frame_try_runtime::TryStateSelect, - ) -> Weight { - unimplemented!() - } - } - - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { - fn account_nonce(_: AccountId) -> Nonce { - unimplemented!() - } - } - - #[cfg(feature = "runtime-benchmarks")] - impl frame_benchmarking::Benchmark for Runtime { - fn benchmark_metadata(_: bool) -> ( - Vec, - Vec, - ) { - unimplemented!() - } - - fn dispatch_benchmark( - _: frame_benchmarking::BenchmarkConfig - ) -> Result, sp_runtime::RuntimeString> { - unimplemented!() - } - } - - impl sp_genesis_builder::GenesisBuilder for Runtime { - fn build_state(_: Vec) -> sp_genesis_builder::Result { - unimplemented!() - } - - fn get_preset(_id: &Option) -> Option> { - unimplemented!() - } - - fn preset_names() -> Vec { - unimplemented!() - } - } -} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs index 29e2736b06ff..02aa867d70fe 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs @@ -17,5 +17,19 @@ //! In an ideal world this would be one runtime which would simplify the code massively. //! This is not an ideal world - Polkadot Asset Hub has a different key type. -pub mod asset_hub_polkadot_aura; -pub mod aura; +mod utils; + +use utils::{impl_node_runtime_apis, imports::*}; + +type CustomBlock = crate::common::types::Block; +pub mod aura_sr25519 { + use super::*; + struct FakeRuntime; + impl_node_runtime_apis!(FakeRuntime, CustomBlock, sp_consensus_aura::sr25519::AuthorityId); +} + +pub mod aura_ed25519 { + use super::*; + struct FakeRuntime; + impl_node_runtime_apis!(FakeRuntime, CustomBlock, sp_consensus_aura::ed25519::AuthorityId); +} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/utils.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/utils.rs new file mode 100644 index 000000000000..442b87b5d775 --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/utils.rs @@ -0,0 +1,220 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +pub(crate) mod imports { + pub use parachains_common::{AccountId, Balance, Nonce}; + pub use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; + pub use sp_runtime::{ + traits::Block as BlockT, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, + }; + pub use sp_weights::Weight; +} + +macro_rules! impl_node_runtime_apis { + ($runtime: ty, $block: tt, $aura_id: ty) => { + sp_api::impl_runtime_apis! { + impl sp_api::Core<$block> for $runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + + fn execute_block(_: $block) { + unimplemented!() + } + + fn initialize_block( + _: &<$block as BlockT>::Header + ) -> sp_runtime::ExtrinsicInclusionMode { + unimplemented!() + } + } + + impl sp_api::Metadata<$block> for $runtime { + fn metadata() -> OpaqueMetadata { + unimplemented!() + } + + fn metadata_at_version(_: u32) -> Option { + unimplemented!() + } + + fn metadata_versions() -> Vec { + unimplemented!() + } + } + + impl sp_consensus_aura::AuraApi<$block, $aura_id> for $runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + unimplemented!() + } + + fn authorities() -> Vec<$aura_id> { + unimplemented!() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi<$block> for $runtime { + fn can_build_upon( + _: <$block as BlockT>::Hash, + _: cumulus_primitives_aura::Slot, + ) -> bool { + unimplemented!() + } + } + + impl sp_block_builder::BlockBuilder<$block> for $runtime { + fn apply_extrinsic(_: <$block as BlockT>::Extrinsic) -> ApplyExtrinsicResult { + unimplemented!() + } + + fn finalize_block() -> <$block as BlockT>::Header { + unimplemented!() + } + + fn inherent_extrinsics( + _: sp_inherents::InherentData + ) -> Vec<<$block as BlockT>::Extrinsic> { + unimplemented!() + } + + fn check_inherents( + _: $block, + _: sp_inherents::InherentData + ) -> sp_inherents::CheckInherentsResult { + unimplemented!() + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<$block> for $runtime { + fn validate_transaction( + _: TransactionSource, + _: <$block as BlockT>::Extrinsic, + _: <$block as BlockT>::Hash, + ) -> TransactionValidity { + unimplemented!() + } + } + + impl sp_session::SessionKeys<$block> for $runtime { + fn generate_session_keys(_: Option>) -> Vec { + unimplemented!() + } + + fn decode_session_keys( + _: Vec, + ) -> Option, KeyTypeId)>> { + unimplemented!() + } + } + + impl + pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< + $block, + Balance, + > for $runtime + { + fn query_info( + _: <$block as BlockT>::Extrinsic, + _: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + unimplemented!() + } + fn query_fee_details( + _: <$block as BlockT>::Extrinsic, + _: u32, + ) -> pallet_transaction_payment::FeeDetails { + unimplemented!() + } + fn query_weight_to_fee(_: Weight) -> Balance { + unimplemented!() + } + fn query_length_to_fee(_: u32) -> Balance { + unimplemented!() + } + } + + impl cumulus_primitives_core::CollectCollationInfo<$block> for $runtime { + fn collect_collation_info( + _: &<$block as BlockT>::Header + ) -> cumulus_primitives_core::CollationInfo { + unimplemented!() + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime<$block> for $runtime { + fn on_runtime_upgrade( + _: frame_try_runtime::UpgradeCheckSelect + ) -> (Weight, Weight) { + unimplemented!() + } + + fn execute_block( + _: $block, + _: bool, + _: bool, + _: frame_try_runtime::TryStateSelect, + ) -> Weight { + unimplemented!() + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi< + $block, + AccountId, + Nonce + > for $runtime { + fn account_nonce(_: AccountId) -> Nonce { + unimplemented!() + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark<$block> for $runtime { + fn benchmark_metadata(_: bool) -> ( + Vec, + Vec, + ) { + unimplemented!() + } + + fn dispatch_benchmark( + _: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + unimplemented!() + } + } + + impl sp_genesis_builder::GenesisBuilder<$block> for $runtime { + fn build_state(_: Vec) -> sp_genesis_builder::Result { + unimplemented!() + } + + fn get_preset(_id: &Option) -> Option> { + unimplemented!() + } + + fn preset_names() -> Vec { + unimplemented!() + } + } + } + }; +} + +pub(crate) use impl_node_runtime_apis; diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs index fc164a9d8907..6aa2f656a48b 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs @@ -39,11 +39,12 @@ //! //! For an example, see the `polkadot-parachain-bin` crate. +#![deny(missing_docs)] + mod cli; mod command; mod common; mod fake_runtime_api; -mod rpc; mod service; pub use cli::CliConfig; diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs index 3b9ae6bd4457..303ec1e3b298 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs @@ -14,7 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use cumulus_client_cli::{CollatorOptions, ExportGenesisHeadCommand}; +use crate::{ + common::{ + aura::{AuraIdT, AuraRuntimeApi}, + rpc::{BuildEmptyRpcExtensions, BuildParachainRpcExtensions}, + spec::{BuildImportQueue, DynNodeSpec, NodeSpec, StartConsensus}, + types::{ + AccountId, Balance, Block, Hash, Nonce, ParachainBackend, ParachainBlockImport, + ParachainClient, + }, + ConstructNodeRuntimeApi, NodeBlock, NodeExtraArgs, + }, + fake_runtime_api::aura_sr25519::RuntimeApi as FakeRuntimeApi, +}; use cumulus_client_collator::service::{ CollatorService, ServiceInterface as CollatorServiceInterface, }; @@ -23,371 +35,45 @@ use cumulus_client_consensus_aura::collators::lookahead::{self as aura, Params a use cumulus_client_consensus_aura::collators::slot_based::{ self as slot_based, Params as SlotBasedParams, }; -use cumulus_client_consensus_common::ParachainBlockImport as TParachainBlockImport; use cumulus_client_consensus_proposer::{Proposer, ProposerInterface}; use cumulus_client_consensus_relay_chain::Verifier as RelayChainVerifier; #[allow(deprecated)] use cumulus_client_service::old_consensus; -use cumulus_client_service::{ - build_network, build_relay_chain_interface, prepare_node_config, start_relay_chain_tasks, - BuildNetworkParams, CollatorSybilResistance, DARecoveryProfile, StartRelayChainTasksParams, -}; +use cumulus_client_service::CollatorSybilResistance; use cumulus_primitives_core::{relay_chain::ValidationCode, ParaId}; use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface}; - -use crate::{ - common::{ - aura::{AuraIdT, AuraRuntimeApi}, - ConstructNodeRuntimeApi, NodeExtraArgs, - }, - fake_runtime_api::aura::RuntimeApi as FakeRuntimeApi, - rpc::BuildRpcExtensions, -}; -pub use parachains_common::{AccountId, Balance, Block, Hash, Nonce}; - -use crate::rpc::{BuildEmptyRpcExtensions, BuildParachainRpcExtensions}; -use frame_benchmarking_cli::BlockCmd; -#[cfg(any(feature = "runtime-benchmarks"))] -use frame_benchmarking_cli::StorageCmd; use futures::prelude::*; use polkadot_primitives::CollatorPair; use prometheus_endpoint::Registry; -use sc_cli::{CheckBlockCmd, ExportBlocksCmd, ExportStateCmd, ImportBlocksCmd, RevertCmd}; use sc_client_api::BlockchainEvents; +use sc_client_db::DbHash; use sc_consensus::{ import_queue::{BasicQueue, Verifier as VerifierT}, - BlockImportParams, DefaultImportQueue, ImportQueue, + BlockImportParams, DefaultImportQueue, }; -use sc_executor::{HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY}; -use sc_network::{config::FullNetworkConfiguration, service::traits::NetworkBackend, NetworkBlock}; -use sc_service::{Configuration, Error, PartialComponents, TFullBackend, TFullClient, TaskManager}; -use sc_sysinfo::HwBench; -use sc_telemetry::{Telemetry, TelemetryHandle, TelemetryWorker, TelemetryWorkerHandle}; +use sc_service::{Configuration, Error, TaskManager}; +use sc_telemetry::TelemetryHandle; use sc_transaction_pool::FullPool; use sp_api::ProvideRuntimeApi; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; -use sp_runtime::{app_crypto::AppCrypto, traits::Header as HeaderT}; -use std::{marker::PhantomData, pin::Pin, sync::Arc, time::Duration}; - -#[cfg(not(feature = "runtime-benchmarks"))] -type HostFunctions = cumulus_client_service::ParachainHostFunctions; - -#[cfg(feature = "runtime-benchmarks")] -type HostFunctions = ( - cumulus_client_service::ParachainHostFunctions, - frame_benchmarking::benchmarking::HostFunctions, -); - -pub type ParachainClient = TFullClient>; - -pub type ParachainBackend = TFullBackend; - -type ParachainBlockImport = - TParachainBlockImport>, ParachainBackend>; - -/// Assembly of PartialComponents (enough to run chain ops subcommands) -pub type Service = PartialComponents< - ParachainClient, - ParachainBackend, - (), - sc_consensus::DefaultImportQueue, - sc_transaction_pool::FullPool>, - (ParachainBlockImport, Option, Option), ->; - -pub(crate) trait BuildImportQueue { - fn build_import_queue( - client: Arc>, - block_import: ParachainBlockImport, - config: &Configuration, - telemetry_handle: Option, - task_manager: &TaskManager, - ) -> sc_service::error::Result>; -} - -pub(crate) trait StartConsensus -where - RuntimeApi: ConstructNodeRuntimeApi>, -{ - fn start_consensus( - client: Arc>, - block_import: ParachainBlockImport, - prometheus_registry: Option<&Registry>, - telemetry: Option, - task_manager: &TaskManager, - relay_chain_interface: Arc, - transaction_pool: Arc>>, - keystore: KeystorePtr, - relay_chain_slot_duration: Duration, - para_id: ParaId, - collator_key: CollatorPair, - overseer_handle: OverseerHandle, - announce_block: Arc>) + Send + Sync>, - backend: Arc, - node_extra_args: NodeExtraArgs, - ) -> Result<(), sc_service::Error>; -} - -pub(crate) trait NodeSpec { - type RuntimeApi: ConstructNodeRuntimeApi>; - - type BuildImportQueue: BuildImportQueue + 'static; - - type BuildRpcExtensions: BuildRpcExtensions< - ParachainClient, - ParachainBackend, - sc_transaction_pool::FullPool>, - > + 'static; - - type StartConsensus: StartConsensus + 'static; - - const SYBIL_RESISTANCE: CollatorSybilResistance; - - /// Starts a `ServiceBuilder` for a full service. - /// - /// Use this macro if you don't actually need the full service, but just the builder in order to - /// be able to perform chain operations. - fn new_partial(config: &Configuration) -> sc_service::error::Result> { - let telemetry = config - .telemetry_endpoints - .clone() - .filter(|x| !x.is_empty()) - .map(|endpoints| -> Result<_, sc_telemetry::Error> { - let worker = TelemetryWorker::new(16)?; - let telemetry = worker.handle().new_telemetry(endpoints); - Ok((worker, telemetry)) - }) - .transpose()?; - - let heap_pages = config.default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| { - HeapAllocStrategy::Static { extra_pages: h as _ } - }); - - let executor = sc_executor::WasmExecutor::::builder() - .with_execution_method(config.wasm_method) - .with_max_runtime_instances(config.max_runtime_instances) - .with_runtime_cache_size(config.runtime_cache_size) - .with_onchain_heap_alloc_strategy(heap_pages) - .with_offchain_heap_alloc_strategy(heap_pages) - .build(); - - let (client, backend, keystore_container, task_manager) = - sc_service::new_full_parts_record_import::( - config, - telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), - executor, - true, - )?; - let client = Arc::new(client); - - let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle()); - - let telemetry = telemetry.map(|(worker, telemetry)| { - task_manager.spawn_handle().spawn("telemetry", None, worker.run()); - telemetry - }); - - let transaction_pool = sc_transaction_pool::BasicPool::new_full( - config.transaction_pool.clone(), - config.role.is_authority().into(), - config.prometheus_registry(), - task_manager.spawn_essential_handle(), - client.clone(), - ); - - let block_import = ParachainBlockImport::new(client.clone(), backend.clone()); - - let import_queue = Self::BuildImportQueue::build_import_queue( - client.clone(), - block_import.clone(), - config, - telemetry.as_ref().map(|telemetry| telemetry.handle()), - &task_manager, - )?; - - Ok(PartialComponents { - backend, - client, - import_queue, - keystore_container, - task_manager, - transaction_pool, - select_chain: (), - other: (block_import, telemetry, telemetry_worker_handle), - }) - } - - /// Start a node with the given parachain spec. - /// - /// This is the actual implementation that is abstract over the executor and the runtime api. - fn start_node( - parachain_config: Configuration, - polkadot_config: Configuration, - collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, - node_extra_args: NodeExtraArgs, - ) -> Pin>>> - where - Net: NetworkBackend, - { - Box::pin(async move { - let parachain_config = prepare_node_config(parachain_config); - - let params = Self::new_partial(¶chain_config)?; - let (block_import, mut telemetry, telemetry_worker_handle) = params.other; - - let client = params.client.clone(); - let backend = params.backend.clone(); - - let mut task_manager = params.task_manager; - let (relay_chain_interface, collator_key) = build_relay_chain_interface( - polkadot_config, - ¶chain_config, - telemetry_worker_handle, - &mut task_manager, - collator_options.clone(), - hwbench.clone(), - ) - .await - .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?; - - let validator = parachain_config.role.is_authority(); - let prometheus_registry = parachain_config.prometheus_registry().cloned(); - let transaction_pool = params.transaction_pool.clone(); - let import_queue_service = params.import_queue.service(); - let net_config = FullNetworkConfiguration::<_, _, Net>::new( - ¶chain_config.network, - prometheus_registry.clone(), - ); - - let (network, system_rpc_tx, tx_handler_controller, start_network, sync_service) = - build_network(BuildNetworkParams { - parachain_config: ¶chain_config, - net_config, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - para_id, - spawn_handle: task_manager.spawn_handle(), - relay_chain_interface: relay_chain_interface.clone(), - import_queue: params.import_queue, - sybil_resistance_level: Self::SYBIL_RESISTANCE, - }) - .await?; - - let rpc_builder = { - let client = client.clone(); - let transaction_pool = transaction_pool.clone(); - let backend_for_rpc = backend.clone(); - - Box::new(move |deny_unsafe, _| { - Self::BuildRpcExtensions::build_rpc_extensions( - deny_unsafe, - client.clone(), - backend_for_rpc.clone(), - transaction_pool.clone(), - ) - }) - }; - - sc_service::spawn_tasks(sc_service::SpawnTasksParams { - rpc_builder, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - task_manager: &mut task_manager, - config: parachain_config, - keystore: params.keystore_container.keystore(), - backend: backend.clone(), - network: network.clone(), - sync_service: sync_service.clone(), - system_rpc_tx, - tx_handler_controller, - telemetry: telemetry.as_mut(), - })?; - - if let Some(hwbench) = hwbench { - sc_sysinfo::print_hwbench(&hwbench); - if validator { - warn_if_slow_hardware(&hwbench); - } - - if let Some(ref mut telemetry) = telemetry { - let telemetry_handle = telemetry.handle(); - task_manager.spawn_handle().spawn( - "telemetry_hwbench", - None, - sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench), - ); - } - } - - let announce_block = { - let sync_service = sync_service.clone(); - Arc::new(move |hash, data| sync_service.announce_block(hash, data)) - }; - - let relay_chain_slot_duration = Duration::from_secs(6); - - let overseer_handle = relay_chain_interface - .overseer_handle() - .map_err(|e| sc_service::Error::Application(Box::new(e)))?; - - start_relay_chain_tasks(StartRelayChainTasksParams { - client: client.clone(), - announce_block: announce_block.clone(), - para_id, - relay_chain_interface: relay_chain_interface.clone(), - task_manager: &mut task_manager, - da_recovery_profile: if validator { - DARecoveryProfile::Collator - } else { - DARecoveryProfile::FullNode - }, - import_queue: import_queue_service, - relay_chain_slot_duration, - recovery_handle: Box::new(overseer_handle.clone()), - sync_service, - })?; - - if validator { - Self::StartConsensus::start_consensus( - client.clone(), - block_import, - prometheus_registry.as_ref(), - telemetry.as_ref().map(|t| t.handle()), - &task_manager, - relay_chain_interface.clone(), - transaction_pool, - params.keystore_container.keystore(), - relay_chain_slot_duration, - para_id, - collator_key.expect("Command line arguments do not allow this. qed"), - overseer_handle, - announce_block, - backend.clone(), - node_extra_args, - )?; - } - - start_network.start_network(); - - Ok(task_manager) - }) - } -} +use sp_runtime::{ + app_crypto::AppCrypto, + traits::{Block as BlockT, Header as HeaderT}, +}; +use std::{marker::PhantomData, sync::Arc, time::Duration}; /// Build the import queue for the shell runtime. -pub(crate) struct BuildShellImportQueue(PhantomData); +pub(crate) struct BuildShellImportQueue; -impl BuildImportQueue for BuildShellImportQueue { +impl BuildImportQueue, FakeRuntimeApi> for BuildShellImportQueue { fn build_import_queue( - client: Arc>, - block_import: ParachainBlockImport, + client: Arc, FakeRuntimeApi>>, + block_import: ParachainBlockImport, FakeRuntimeApi>, config: &Configuration, _telemetry_handle: Option, task_manager: &TaskManager, - ) -> sc_service::error::Result> { + ) -> sc_service::error::Result>> { cumulus_client_consensus_relay_chain::import_queue( client, block_import, @@ -402,15 +88,16 @@ impl BuildImportQueue for BuildShellImportQueue pub(crate) struct ShellNode; impl NodeSpec for ShellNode { + type Block = Block; type RuntimeApi = FakeRuntimeApi; - type BuildImportQueue = BuildShellImportQueue; - type BuildRpcExtensions = BuildEmptyRpcExtensions; + type BuildImportQueue = BuildShellImportQueue; + type BuildRpcExtensions = BuildEmptyRpcExtensions, Self::RuntimeApi>; type StartConsensus = StartRelayChainConsensus; const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Unresistant; } -struct Verifier { +struct Verifier { client: Arc, aura_verifier: Box>, relay_chain_verifier: Box>, @@ -418,7 +105,7 @@ struct Verifier { } #[async_trait::async_trait] -impl VerifierT for Verifier +impl VerifierT for Verifier where Client: ProvideRuntimeApi + Send + Sync, Client::Api: AuraRuntimeApi, @@ -438,51 +125,53 @@ where /// Build the import queue for parachain runtimes that started with relay chain consensus and /// switched to aura. -pub(crate) struct BuildRelayToAuraImportQueue( - PhantomData<(RuntimeApi, AuraId)>, +pub(crate) struct BuildRelayToAuraImportQueue( + PhantomData<(Block, RuntimeApi, AuraId)>, ); -impl BuildImportQueue - for BuildRelayToAuraImportQueue +impl BuildImportQueue + for BuildRelayToAuraImportQueue where - RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi: ConstructNodeRuntimeApi>, RuntimeApi::RuntimeApi: AuraRuntimeApi, AuraId: AuraIdT + Sync, { fn build_import_queue( - client: Arc>, - block_import: ParachainBlockImport, + client: Arc>, + block_import: ParachainBlockImport, config: &Configuration, telemetry_handle: Option, task_manager: &TaskManager, ) -> sc_service::error::Result> { let verifier_client = client.clone(); - let aura_verifier = - cumulus_client_consensus_aura::build_verifier::<::Pair, _, _, _>( - cumulus_client_consensus_aura::BuildVerifierParams { - client: verifier_client.clone(), - create_inherent_data_providers: move |parent_hash, _| { - let cidp_client = verifier_client.clone(); - async move { - let slot_duration = cumulus_client_consensus_aura::slot_duration_at( - &*cidp_client, - parent_hash, - )?; - let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); - - let slot = - sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( - *timestamp, - slot_duration, - ); - - Ok((slot, timestamp)) - } - }, - telemetry: telemetry_handle, - }, - ); + let aura_verifier = cumulus_client_consensus_aura::build_verifier::< + ::Pair, + _, + _, + _, + >(cumulus_client_consensus_aura::BuildVerifierParams { + client: verifier_client.clone(), + create_inherent_data_providers: move |parent_hash, _| { + let cidp_client = verifier_client.clone(); + async move { + let slot_duration = cumulus_client_consensus_aura::slot_duration_at( + &*cidp_client, + parent_hash, + )?; + let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + + let slot = + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + *timestamp, + slot_duration, + ); + + Ok((slot, timestamp)) + } + }, + telemetry: telemetry_handle, + }); let relay_chain_verifier = Box::new(RelayChainVerifier::new(client.clone(), |_, _| async { Ok(()) })); @@ -504,35 +193,43 @@ where /// Uses the lookahead collator to support async backing. /// /// Start an aura powered parachain node. Some system chains use this. -pub(crate) struct AuraNode( - pub PhantomData<(RuntimeApi, AuraId, StartConsensus)>, +pub(crate) struct AuraNode( + pub PhantomData<(Block, RuntimeApi, AuraId, StartConsensus)>, ); -impl Default for AuraNode { +impl Default + for AuraNode +{ fn default() -> Self { Self(Default::default()) } } -impl NodeSpec for AuraNode +impl NodeSpec + for AuraNode where - RuntimeApi: ConstructNodeRuntimeApi>, + Block: NodeBlock, + RuntimeApi: ConstructNodeRuntimeApi>, RuntimeApi::RuntimeApi: AuraRuntimeApi + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + substrate_frame_rpc_system::AccountNonceApi, AuraId: AuraIdT + Sync, - StartConsensus: self::StartConsensus + 'static, + StartConsensus: self::StartConsensus + 'static, { + type Block = Block; type RuntimeApi = RuntimeApi; - type BuildImportQueue = BuildRelayToAuraImportQueue; - type BuildRpcExtensions = BuildParachainRpcExtensions; + type BuildImportQueue = BuildRelayToAuraImportQueue; + type BuildRpcExtensions = BuildParachainRpcExtensions; type StartConsensus = StartConsensus; const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Resistant; } -pub fn new_aura_node_spec(extra_args: &NodeExtraArgs) -> Box +pub fn new_aura_node_spec( + extra_args: &NodeExtraArgs, +) -> Box where - RuntimeApi: ConstructNodeRuntimeApi>, + Block: NodeBlock, + RuntimeApi: ConstructNodeRuntimeApi>, RuntimeApi::RuntimeApi: AuraRuntimeApi + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + substrate_frame_rpc_system::AccountNonceApi, @@ -540,15 +237,17 @@ where { if extra_args.use_slot_based_consensus { Box::new(AuraNode::< + Block, RuntimeApi, AuraId, - StartSlotBasedAuraConsensus, + StartSlotBasedAuraConsensus, >::default()) } else { Box::new(AuraNode::< + Block, RuntimeApi, AuraId, - StartLookaheadAuraConsensus, + StartLookaheadAuraConsensus, >::default()) } } @@ -557,22 +256,22 @@ where /// decides what is backed and included. pub(crate) struct StartRelayChainConsensus; -impl StartConsensus for StartRelayChainConsensus { +impl StartConsensus, FakeRuntimeApi> for StartRelayChainConsensus { fn start_consensus( - client: Arc>, - block_import: ParachainBlockImport, + client: Arc, FakeRuntimeApi>>, + block_import: ParachainBlockImport, FakeRuntimeApi>, prometheus_registry: Option<&Registry>, telemetry: Option, task_manager: &TaskManager, relay_chain_interface: Arc, - transaction_pool: Arc>>, + transaction_pool: Arc, ParachainClient, FakeRuntimeApi>>>, _keystore: KeystorePtr, _relay_chain_slot_duration: Duration, para_id: ParaId, collator_key: CollatorPair, overseer_handle: OverseerHandle, announce_block: Arc>) + Send + Sync>, - _backend: Arc, + _backend: Arc>>, _node_extra_args: NodeExtraArgs, ) -> Result<(), Error> { let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( @@ -593,12 +292,12 @@ impl StartConsensus for StartRelayChainConsensus { let relay_chain_interface = relay_chain_interface.clone(); async move { let parachain_inherent = - cumulus_client_parachain_inherent::ParachainInherentDataProvider::create_at( - relay_parent, - &relay_chain_interface, - &validation_data, - para_id, - ).await; + cumulus_client_parachain_inherent::ParachainInherentDataProvider::create_at( + relay_parent, + &relay_chain_interface, + &validation_data, + para_id, + ).await; let parachain_inherent = parachain_inherent.ok_or_else(|| { Box::::from( "Failed to create parachain inherent", @@ -630,23 +329,24 @@ impl StartConsensus for StartRelayChainConsensus { } /// Start consensus using the lookahead aura collator. -pub(crate) struct StartSlotBasedAuraConsensus( - PhantomData<(RuntimeApi, AuraId)>, +pub(crate) struct StartSlotBasedAuraConsensus( + PhantomData<(Block, RuntimeApi, AuraId)>, ); -impl StartSlotBasedAuraConsensus +impl, RuntimeApi, AuraId> + StartSlotBasedAuraConsensus where - RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi: ConstructNodeRuntimeApi>, RuntimeApi::RuntimeApi: AuraRuntimeApi, AuraId: AuraIdT + Sync, { #[docify::export_content] fn launch_slot_based_collator( params: SlotBasedParams< - ParachainBlockImport, + ParachainBlockImport, CIDP, - ParachainClient, - ParachainBackend, + ParachainClient, + ParachainBackend, Arc, CHP, Proposer, @@ -676,28 +376,28 @@ where } } -impl StartConsensus - for StartSlotBasedAuraConsensus +impl, RuntimeApi, AuraId> StartConsensus + for StartSlotBasedAuraConsensus where - RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi: ConstructNodeRuntimeApi>, RuntimeApi::RuntimeApi: AuraRuntimeApi, AuraId: AuraIdT + Sync, { fn start_consensus( - client: Arc>, - block_import: ParachainBlockImport, + client: Arc>, + block_import: ParachainBlockImport, prometheus_registry: Option<&Registry>, telemetry: Option, task_manager: &TaskManager, relay_chain_interface: Arc, - transaction_pool: Arc>>, + transaction_pool: Arc>>, keystore: KeystorePtr, relay_chain_slot_duration: Duration, para_id: ParaId, collator_key: CollatorPair, _overseer_handle: OverseerHandle, announce_block: Arc>) + Send + Sync>, - backend: Arc, + backend: Arc>, _node_extra_args: NodeExtraArgs, ) -> Result<(), Error> { let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( @@ -748,9 +448,10 @@ where /// Wait for the Aura runtime API to appear on chain. /// This is useful for chains that started out without Aura. Components that /// are depending on Aura functionality will wait until Aura appears in the runtime. -async fn wait_for_aura(client: Arc>) -where - RuntimeApi: ConstructNodeRuntimeApi>, +async fn wait_for_aura( + client: Arc>, +) where + RuntimeApi: ConstructNodeRuntimeApi>, RuntimeApi::RuntimeApi: AuraRuntimeApi, AuraId: AuraIdT + Sync, { @@ -768,32 +469,32 @@ where } /// Start consensus using the lookahead aura collator. -pub(crate) struct StartLookaheadAuraConsensus( - PhantomData<(RuntimeApi, AuraId)>, +pub(crate) struct StartLookaheadAuraConsensus( + PhantomData<(Block, RuntimeApi, AuraId)>, ); -impl StartConsensus - for StartLookaheadAuraConsensus +impl, RuntimeApi, AuraId> StartConsensus + for StartLookaheadAuraConsensus where - RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi: ConstructNodeRuntimeApi>, RuntimeApi::RuntimeApi: AuraRuntimeApi, AuraId: AuraIdT + Sync, { fn start_consensus( - client: Arc>, - block_import: ParachainBlockImport, + client: Arc>, + block_import: ParachainBlockImport, prometheus_registry: Option<&Registry>, telemetry: Option, task_manager: &TaskManager, relay_chain_interface: Arc, - transaction_pool: Arc>>, + transaction_pool: Arc>>, keystore: KeystorePtr, relay_chain_slot_duration: Duration, para_id: ParaId, collator_key: CollatorPair, overseer_handle: OverseerHandle, announce_block: Arc>) + Send + Sync>, - backend: Arc, + backend: Arc>, node_extra_args: NodeExtraArgs, ) -> Result<(), Error> { let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( @@ -837,204 +538,15 @@ where }, }; - let fut = - async move { - wait_for_aura(client).await; - aura::run_with_export::::Pair, _, _, _, _, _, _, _, _>(params).await; - }; + let fut = async move { + wait_for_aura(client).await; + aura::run_with_export::::Pair, _, _, _, _, _, _, _, _>( + params, + ) + .await; + }; task_manager.spawn_essential_handle().spawn("aura", None, fut); Ok(()) } } - -/// Checks that the hardware meets the requirements and print a warning otherwise. -fn warn_if_slow_hardware(hwbench: &sc_sysinfo::HwBench) { - // Polkadot para-chains should generally use these requirements to ensure that the relay-chain - // will not take longer than expected to import its blocks. - if let Err(err) = frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE.check_hardware(hwbench) { - log::warn!( - "⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\ - https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware", - err - ); - } -} - -type SyncCmdResult = sc_cli::Result<()>; - -type AsyncCmdResult<'a> = - sc_cli::Result<(Pin + 'a>>, TaskManager)>; - -pub(crate) trait DynNodeSpec { - fn prepare_check_block_cmd( - self: Box, - config: Configuration, - cmd: &CheckBlockCmd, - ) -> AsyncCmdResult<'_>; - - fn prepare_export_blocks_cmd( - self: Box, - config: Configuration, - cmd: &ExportBlocksCmd, - ) -> AsyncCmdResult<'_>; - - fn prepare_export_state_cmd( - self: Box, - config: Configuration, - cmd: &ExportStateCmd, - ) -> AsyncCmdResult<'_>; - - fn prepare_import_blocks_cmd( - self: Box, - config: Configuration, - cmd: &ImportBlocksCmd, - ) -> AsyncCmdResult<'_>; - - fn prepare_revert_cmd( - self: Box, - config: Configuration, - cmd: &RevertCmd, - ) -> AsyncCmdResult<'_>; - - fn run_export_genesis_head_cmd( - self: Box, - config: Configuration, - cmd: &ExportGenesisHeadCommand, - ) -> SyncCmdResult; - - fn run_benchmark_block_cmd( - self: Box, - config: Configuration, - cmd: &BlockCmd, - ) -> SyncCmdResult; - - #[cfg(any(feature = "runtime-benchmarks"))] - fn run_benchmark_storage_cmd( - self: Box, - config: Configuration, - cmd: &StorageCmd, - ) -> SyncCmdResult; - - fn start_node( - self: Box, - parachain_config: Configuration, - polkadot_config: Configuration, - collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, - node_extra_args: NodeExtraArgs, - ) -> Pin>>>; -} - -impl DynNodeSpec for T -where - T: NodeSpec, -{ - fn prepare_check_block_cmd( - self: Box, - config: Configuration, - cmd: &CheckBlockCmd, - ) -> AsyncCmdResult<'_> { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - Ok((Box::pin(cmd.run(partial.client, partial.import_queue)), partial.task_manager)) - } - - fn prepare_export_blocks_cmd( - self: Box, - config: Configuration, - cmd: &ExportBlocksCmd, - ) -> AsyncCmdResult<'_> { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - Ok((Box::pin(cmd.run(partial.client, config.database)), partial.task_manager)) - } - - fn prepare_export_state_cmd( - self: Box, - config: Configuration, - cmd: &ExportStateCmd, - ) -> AsyncCmdResult<'_> { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - Ok((Box::pin(cmd.run(partial.client, config.chain_spec)), partial.task_manager)) - } - - fn prepare_import_blocks_cmd( - self: Box, - config: Configuration, - cmd: &ImportBlocksCmd, - ) -> AsyncCmdResult<'_> { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - Ok((Box::pin(cmd.run(partial.client, partial.import_queue)), partial.task_manager)) - } - - fn prepare_revert_cmd( - self: Box, - config: Configuration, - cmd: &RevertCmd, - ) -> AsyncCmdResult<'_> { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - Ok((Box::pin(cmd.run(partial.client, partial.backend, None)), partial.task_manager)) - } - - fn run_export_genesis_head_cmd( - self: Box, - config: Configuration, - cmd: &ExportGenesisHeadCommand, - ) -> SyncCmdResult { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - cmd.run(partial.client) - } - - fn run_benchmark_block_cmd( - self: Box, - config: Configuration, - cmd: &BlockCmd, - ) -> SyncCmdResult { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - cmd.run(partial.client) - } - - #[cfg(any(feature = "runtime-benchmarks"))] - fn run_benchmark_storage_cmd( - self: Box, - config: Configuration, - cmd: &StorageCmd, - ) -> SyncCmdResult { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - let db = partial.backend.expose_db(); - let storage = partial.backend.expose_storage(); - - cmd.run(config, partial.client, db, storage) - } - - fn start_node( - self: Box, - parachain_config: Configuration, - polkadot_config: Configuration, - collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, - node_extra_args: NodeExtraArgs, - ) -> Pin>>> { - match parachain_config.network.network_backend { - sc_network::config::NetworkBackendType::Libp2p => - ::start_node::>( - parachain_config, - polkadot_config, - collator_options, - para_id, - hwbench, - node_extra_args, - ), - sc_network::config::NetworkBackendType::Litep2p => - ::start_node::( - parachain_config, - polkadot_config, - collator_options, - para_id, - hwbench, - node_extra_args, - ), - } - } -} diff --git a/cumulus/polkadot-parachain/src/chain_spec/contracts.rs b/cumulus/polkadot-parachain/src/chain_spec/contracts.rs deleted file mode 100644 index eb10a43ffbea..000000000000 --- a/cumulus/polkadot-parachain/src/chain_spec/contracts.rs +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. - -// Cumulus is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Cumulus is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Cumulus. If not, see . - -use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION}; -use cumulus_primitives_core::ParaId; -use hex_literal::hex; -use parachains_common::{AccountId, AuraId}; -use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; -use sc_service::ChainType; -use sp_core::{crypto::UncheckedInto, sr25519}; - -/// No relay chain suffix because the id is the same over all relay chains. -const CONTRACTS_PARACHAIN_ID: u32 = 1002; - -/// The existential deposit is determined by the runtime "contracts-rococo". -const CONTRACTS_ROCOCO_ED: contracts_rococo_runtime::Balance = - testnet_parachains_constants::rococo::currency::EXISTENTIAL_DEPOSIT; - -pub fn contracts_rococo_development_config() -> GenericChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("tokenSymbol".into(), "ROC".into()); - properties.insert("tokenDecimals".into(), 12.into()); - - GenericChainSpec::builder( - contracts_rococo_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), - Extensions { - relay_chain: "rococo-local".into(), // You MUST set this to the correct network! - para_id: CONTRACTS_PARACHAIN_ID, - }, - ) - .with_name("Contracts on Rococo Development") - .with_id("contracts-rococo-dev") - .with_chain_type(ChainType::Development) - .with_genesis_config_patch(contracts_rococo_genesis( - // initial collators. - vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ], - CONTRACTS_PARACHAIN_ID.into(), - )) - .with_boot_nodes(Vec::new()) - .build() -} - -pub fn contracts_rococo_local_config() -> GenericChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("tokenSymbol".into(), "ROC".into()); - properties.insert("tokenDecimals".into(), 12.into()); - - GenericChainSpec::builder( - contracts_rococo_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), - Extensions { - relay_chain: "rococo-local".into(), // You MUST set this to the correct network! - para_id: CONTRACTS_PARACHAIN_ID, - }, - ) - .with_name("Contracts on Rococo") - .with_id("contracts-rococo-local") - .with_chain_type(ChainType::Local) - .with_genesis_config_patch(contracts_rococo_genesis( - // initial collators. - vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ], - CONTRACTS_PARACHAIN_ID.into(), - )) - .with_properties(properties) - .build() -} - -pub fn contracts_rococo_config() -> GenericChainSpec { - // Give your base currency a unit name and decimal places - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("tokenSymbol".into(), "ROC".into()); - properties.insert("tokenDecimals".into(), 12.into()); - - GenericChainSpec::builder( - contracts_rococo_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), - Extensions { relay_chain: "rococo".into(), para_id: CONTRACTS_PARACHAIN_ID }, - ) - .with_name("Contracts on Rococo") - .with_id("contracts-rococo") - .with_chain_type(ChainType::Live) - .with_genesis_config_patch(contracts_rococo_genesis( - vec![ - // 5GKFbTTgrVS4Vz1UWWHPqMZQNFWZtqo7H2KpCDyYhEL3aS26 - ( - hex!["bc09354c12c054c8f6b3da208485eacec4ac648bad348895273b37bab5a0937c"] - .into(), - hex!["bc09354c12c054c8f6b3da208485eacec4ac648bad348895273b37bab5a0937c"] - .unchecked_into(), - ), - // 5EPRJHm2GpABVWcwnAujcrhnrjFZyDGd5TwKFzkBoGgdRyv2 - ( - hex!["66be63b7bcbfb91040e5248e2d1ceb822cf219c57848c5924ffa3a1f8e67ba72"] - .into(), - hex!["66be63b7bcbfb91040e5248e2d1ceb822cf219c57848c5924ffa3a1f8e67ba72"] - .unchecked_into(), - ), - // 5GH62vrJrVZxLREcHzm2PR5uTLAT5RQMJitoztCGyaP4o3uM - ( - hex!["ba62886472a0a9f66b5e39f1469ce1c5b3d8cad6be39078daf16f111e89d1e44"] - .into(), - hex!["ba62886472a0a9f66b5e39f1469ce1c5b3d8cad6be39078daf16f111e89d1e44"] - .unchecked_into(), - ), - // 5FHfoJDLdjRYX5KXLRqMDYBbWrwHLMtti21uK4QByUoUAbJF - ( - hex!["8e97f65cda001976311df9bed39e8d0c956089093e94a75ef76fe9347a0eda7b"] - .into(), - hex!["8e97f65cda001976311df9bed39e8d0c956089093e94a75ef76fe9347a0eda7b"] - .unchecked_into(), - ), - ], - // Warning: The configuration for a production chain should not contain - // any endowed accounts here, otherwise it'll be minting extra native tokens - // from the relay chain on the parachain. - vec![ - // NOTE: Remove endowed accounts if deployed on other relay chains. - // Endowed accounts - hex!["baa78c7154c7f82d6d377177e20bcab65d327eca0086513f9964f5a0f6bdad56"].into(), - // AccountId of an account which `ink-waterfall` uses for automated testing - hex!["0e47e2344d523c3cc5c34394b0d58b9a4200e813a038e6c5a6163cc07d70b069"].into(), - ], - CONTRACTS_PARACHAIN_ID.into(), - )) - .with_boot_nodes(vec![ - "/dns/contracts-collator-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWKg3Rpxcr9oJ8n6khoxpGKWztCZydtUZk2cojHqnfLrpj" - .parse() - .expect("MultiaddrWithPeerId"), - "/dns/contracts-collator-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWPEXYrz8tHU3nDtPoPw4V7ou5dzMEWSTuUj7vaWiYVAVh" - .parse() - .expect("MultiaddrWithPeerId"), - "/dns/contracts-collator-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWEVU8AFNary4nP4qEnEcwJaRuy59Wefekzdu9pKbnVEhk" - .parse() - .expect("MultiaddrWithPeerId"), - "/dns/contracts-collator-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWP6pV3ZmcXzGDjv8ZMgA6nZxfAKDxSz4VNiLx6vVCQgJX" - .parse() - .expect("MultiaddrWithPeerId"), - ]) - .with_properties(properties) - .build() -} - -fn contracts_rococo_genesis( - invulnerables: Vec<(AccountId, AuraId)>, - endowed_accounts: Vec, - id: ParaId, -) -> serde_json::Value { - serde_json::json!( { - "balances": { - "balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 60)).collect::>(), - }, - "parachainInfo": { - "parachainId": id, - }, - "collatorSelection": { - "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), - "candidacyBond": CONTRACTS_ROCOCO_ED * 16, - }, - "session": { - "keys": invulnerables - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - contracts_rococo_runtime::SessionKeys { aura }, // session keys - ) - }) - .collect::>(), - }, - // no need to pass anything to aura, in fact it will panic if we do. Session will take care - // of this. - "polkadotXcm": { - "safeXcmVersion": Some(SAFE_XCM_VERSION), - }, - "sudo": { - "key": Some(sp_runtime::AccountId32::from(hex![ - "2681a28014e7d3a5bfb32a003b3571f53c408acbc28d351d6bf58f5028c4ef14" - ])), - }, - }) -} diff --git a/cumulus/polkadot-parachain/src/chain_spec/mod.rs b/cumulus/polkadot-parachain/src/chain_spec/mod.rs index de9c6a889ed0..82aec951704f 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/mod.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/mod.rs @@ -18,7 +18,9 @@ use cumulus_primitives_core::ParaId; use parachains_common::{AccountId, Signature}; use polkadot_parachain_lib::{ chain_spec::{GenericChainSpec, LoadSpec}, - runtime::{AuraConsensusId, Consensus, Runtime, RuntimeResolver as RuntimeResolverT}, + runtime::{ + AuraConsensusId, BlockNumber, Consensus, Runtime, RuntimeResolver as RuntimeResolverT, + }, }; use sc_chain_spec::ChainSpec; use sp_core::{Pair, Public}; @@ -27,7 +29,6 @@ use sp_runtime::traits::{IdentifyAccount, Verify}; pub mod asset_hubs; pub mod bridge_hubs; pub mod collectives; -pub mod contracts; pub mod coretime; pub mod glutton; pub mod penpal; @@ -148,14 +149,6 @@ impl LoadSpec for ChainSpecLoader { &include_bytes!("../../chain-specs/collectives-westend.json")[..], )?), - // -- Contracts on Rococo - "contracts-rococo-dev" => Box::new(contracts::contracts_rococo_development_config()), - "contracts-rococo-local" => Box::new(contracts::contracts_rococo_local_config()), - "contracts-rococo-genesis" => Box::new(contracts::contracts_rococo_config()), - "contracts-rococo" => Box::new(GenericChainSpec::from_json_bytes( - &include_bytes!("../../chain-specs/contracts-rococo.json")[..], - )?), - // -- BridgeHub bridge_like_id if bridge_like_id.starts_with(bridge_hubs::BridgeHubRuntimeType::ID_PREFIX) => @@ -238,7 +231,6 @@ enum LegacyRuntime { AssetHubPolkadot, AssetHub, Penpal, - ContractsRococo, Collectives, Glutton, BridgeHub(bridge_hubs::BridgeHubRuntimeType), @@ -266,8 +258,6 @@ impl LegacyRuntime { LegacyRuntime::AssetHub } else if id.starts_with("penpal") { LegacyRuntime::Penpal - } else if id.starts_with("contracts-rococo") { - LegacyRuntime::ContractsRococo } else if id.starts_with("collectives-polkadot") || id.starts_with("collectives-westend") { LegacyRuntime::Collectives } else if id.starts_with(bridge_hubs::BridgeHubRuntimeType::ID_PREFIX) { @@ -301,16 +291,16 @@ impl RuntimeResolverT for RuntimeResolver { let legacy_runtime = LegacyRuntime::from_id(chain_spec.id()); Ok(match legacy_runtime { LegacyRuntime::AssetHubPolkadot => - Runtime::Omni(Consensus::Aura(AuraConsensusId::Ed25519)), + Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Ed25519)), LegacyRuntime::AssetHub | LegacyRuntime::BridgeHub(_) | LegacyRuntime::Collectives | LegacyRuntime::Coretime(_) | LegacyRuntime::People(_) | - LegacyRuntime::ContractsRococo | LegacyRuntime::Glutton | LegacyRuntime::Penpal | - LegacyRuntime::Omni => Runtime::Omni(Consensus::Aura(AuraConsensusId::Sr25519)), + LegacyRuntime::Omni => + Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519)), LegacyRuntime::Shell | LegacyRuntime::Seedling => Runtime::Shell, }) } @@ -383,9 +373,6 @@ mod tests { create_default_with_extensions("penpal-rococo-1000", Extensions2::default()); assert_eq!(LegacyRuntime::Penpal, LegacyRuntime::from_id(chain_spec.id())); - let chain_spec = crate::chain_spec::contracts::contracts_rococo_local_config(); - assert_eq!(LegacyRuntime::ContractsRococo, LegacyRuntime::from_id(chain_spec.id())); - let chain_spec = crate::chain_spec::rococo_parachain::rococo_parachain_local_config(); assert_eq!(LegacyRuntime::Omni, LegacyRuntime::from_id(chain_spec.id())); } diff --git a/cumulus/test/service/src/cli.rs b/cumulus/test/service/src/cli.rs index 37ca27542cbf..739c2d4bda16 100644 --- a/cumulus/test/service/src/cli.rs +++ b/cumulus/test/service/src/cli.rs @@ -14,13 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use std::{net::SocketAddr, path::PathBuf}; +use std::path::PathBuf; use cumulus_client_cli::{ExportGenesisHeadCommand, ExportGenesisWasmCommand}; use polkadot_service::{ChainSpec, ParaId, PrometheusConfig}; use sc_cli::{ CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, NetworkParams, - Result as CliResult, SharedParams, SubstrateCli, + Result as CliResult, RpcEndpoint, SharedParams, SubstrateCli, }; use sc_service::BasePath; @@ -122,7 +122,7 @@ impl CliConfiguration for RelayChainCli { .or_else(|| self.base_path.clone().map(Into::into))) } - fn rpc_addr(&self, default_listen_port: u16) -> CliResult> { + fn rpc_addr(&self, default_listen_port: u16) -> CliResult>> { self.base.base.rpc_addr(default_listen_port) } diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index 503c2f24fd54..bc0fe9090d38 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -38,7 +38,7 @@ use sp_consensus_aura::sr25519::AuthorityPair; use std::{ collections::HashSet, future::Future, - net::{IpAddr, Ipv4Addr, SocketAddr}, + net::{Ipv4Addr, SocketAddr, SocketAddrV4}, time::Duration, }; use url::Url; @@ -79,7 +79,7 @@ use sc_network::{ use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, NetworkConfiguration, - OffchainWorkerConfig, PruningMode, RpcBatchRequestConfig, WasmExecutionMethod, + OffchainWorkerConfig, PruningMode, RpcBatchRequestConfig, RpcEndpoint, WasmExecutionMethod, }, BasePath, ChainSpec as ChainSpecService, Configuration, Error as ServiceError, PartialComponents, Role, RpcHandlers, TFullBackend, TFullClient, TaskManager, @@ -379,7 +379,7 @@ where let keystore = params.keystore_container.keystore(); let rpc_builder = { let client = client.clone(); - Box::new(move |_, _| rpc_ext_builder(client.clone())) + Box::new(move |_| rpc_ext_builder(client.clone())) }; let rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { @@ -1006,7 +1006,22 @@ pub fn run_relay_chain_validator_node( ); if let Some(port) = port { - config.rpc_addr = Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port)); + config.rpc_addr = Some(vec![RpcEndpoint { + batch_config: config.rpc_batch_config, + cors: config.rpc_cors.clone(), + listen_addr: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)), + max_connections: config.rpc_max_connections, + max_payload_in_mb: config.rpc_max_request_size, + max_payload_out_mb: config.rpc_max_response_size, + max_subscriptions_per_connection: config.rpc_max_subs_per_conn, + max_buffer_capacity_per_connection: config.rpc_message_buffer_capacity, + rpc_methods: config.rpc_methods, + rate_limit: config.rpc_rate_limit, + rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(), + retry_random_port: true, + is_optional: false, + }]); } let mut workers_path = std::env::current_exe().unwrap(); diff --git a/docs/BACKPORT.md b/docs/BACKPORT.md new file mode 100644 index 000000000000..0b4a97e6f667 --- /dev/null +++ b/docs/BACKPORT.md @@ -0,0 +1,21 @@ +# Backporting + +This document explains how to backport a merged PR from `master` to one of the `stable*` branches. +Backports should only be used to fix bugs or security issues - never to introduce new features. + +## Steps + +1. Fix a bug through a PR that targets `master`. +2. Add label `A4-needs-backport` to the PR. +3. Merge the PR into `master`. +4. Wait for the bot to open the backport PR. +5. Ensure the change is audited or does not need audit. +6. Merge the backport PR. + +The label can also be added after the PR is merged. + +## Example + +For example here where the dev triggered the process by adding the label after merging: + +![backport](./images/backport-ex2.png) diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 653e6a2a3e92..bea367411359 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -55,9 +55,10 @@ The Westend testnet will be updated to a new runtime every two weeks with the la **From `master` to `stable`** -Backports in this direction can be anything that is audited and either a `minor` or a `patch` bump. [Security -fixes](#bug-and-security-fix) should be prioritized over additions or improvements. Crates that are declared as internal -API can also have `major` version bumps through backports. +Backports in this direction can be anything that is audited and either a `minor` or a `patch` bump. +See [BACKPORT.md](./BACKPORT.md) for more explanation. [Security fixes](#bug-and-security-fix) +should be prioritized over additions or improvements. Crates that are declared as internal API can +also have `major` version bumps through backports. **From `stable` to `master`** @@ -164,5 +165,6 @@ Describes how developers should merge bug and security fixes. 2. The Pull Request is marked as priority fix. 3. Audit happens with priority. 4. It is merged into `master`. -5. It is automatically back-ported to `stable`. -6. The fix will be released in the next *Stable* release. In urgent cases, a release can happen earlier. +5. Dev adds the `A4-needs-backport` label. +6. It is automatically back-ported to `stable`. +7. The fix will be released in the next *Stable* release. In urgent cases, a release can happen earlier. diff --git a/docs/contributor/CONTRIBUTING.md b/docs/contributor/CONTRIBUTING.md index 2e2d7a7fb4f7..53f42b9ae4fb 100644 --- a/docs/contributor/CONTRIBUTING.md +++ b/docs/contributor/CONTRIBUTING.md @@ -161,11 +161,11 @@ test output there is a script * `./scripts/update-ui-tests.sh` to update the tests for a current rust version locally * `./scripts/update-ui-tests.sh 1.70` # to update the tests for a specific rust version locally -Or if you have opened PR and you're member of `paritytech` - you can use command-bot to run the tests for you in CI: -* `bot update-ui` - will run the tests for the current rust version -* `bot update-ui latest --rust_version=1.70.0` - will run the tests for the specified rust version -* `bot update-ui latest -v CMD_IMAGE=paritytech/ci-unified:bullseye-1.70.0-2023-05-23 --rust_version=1.70.0` - will run -the tests for the specified rust version and specified image +Or if you have opened PR and you're member of `paritytech` - you can use [/cmd](./commands-readme.md) +to run the tests for you in CI: +* `/cmd update-ui` - will run the tests for the current rust version +* `/cmd update-ui --image docker.io/paritytech/ci-unified:bullseye-1.70.0-2023-05-23` - +will run the tests for the specified rust version and specified image ## Feature Propagation @@ -175,7 +175,7 @@ We use [zepter](https://github.com/ggwpez/zepter) to enforce features are propag If you're member of **paritytech** org - you can use command-bot to run various of common commands in CI: -Start with comment in PR: `bot help` to see the list of available commands. +Start with comment in PR: `/cmd --help` to see the list of available commands. ## Deprecating code diff --git a/docs/contributor/PULL_REQUEST_TEMPLATE.md b/docs/contributor/PULL_REQUEST_TEMPLATE.md index 9612ab6d8d3e..99455c985076 100644 --- a/docs/contributor/PULL_REQUEST_TEMPLATE.md +++ b/docs/contributor/PULL_REQUEST_TEMPLATE.md @@ -33,8 +33,9 @@ possibly integration.* # Checklist * [ ] My PR includes a detailed description as outlined in the "Description" and its two subsections above. -* [ ] My PR follows the [labeling requirements](CONTRIBUTING.md#Process) of this project (at minimum one label for `T` - required) +* [ ] My PR follows the [labeling requirements]( +https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process +) of this project (at minimum one label for `T` required) * External contributors: ask maintainers to put the right label on your PR. * [ ] I have made corresponding changes to the documentation (if applicable) * [ ] I have added tests that prove my fix is effective or that my feature works (if applicable) diff --git a/docs/contributor/commands-readme.md b/docs/contributor/commands-readme.md new file mode 100644 index 000000000000..2bb9bd7e7d58 --- /dev/null +++ b/docs/contributor/commands-readme.md @@ -0,0 +1,44 @@ +# Running Commands in PRs + +You can run commands in PRs by triggering it via comment. It will use the context of your PR and post the results back. +Note: it works only for members of the `paritytech` organization. + +## Usage + +`/cmd --help` to see all available commands and usage format + +`/cmd --help` to see the usage of a specific command + + +### Commands + +- `/cmd fmt` to format the code in the PR. It commits back with the formatted code (fmt) and configs (taplo). + +- `/cmd bench` to generate weights for a runtime. Read more about [Weight Generation](weight-generation.md) + +### Flags + +1.`--quiet` to suppress the output of the command in the comments. +By default, the Start and End/Failure of the command will be commented with the link to a pipeline. +If you want to avoid, use this flag. Go to +[Action Tab](https://github.com/paritytech/polkadot-sdk/actions/workflows/cmd.yml) to see the pipeline status. + +2.`--continue-on-fail` to continue running the command even if something inside a command +(like specific pallet weight generation) are failed. +Basically avoids interruption in the middle with `exit 1` +The pipeline logs will include what is failed (like which runtimes/pallets), then you can re-run them separately or not. + +3.`--clean` to clean up all yours and bot's comments in PR relevant to `/cmd` commands. If you run too many commands, +or they keep failing, and you're rerunning them again, it's handy to add this flag to keep a PR clean. + +### Adding new Commands +Feel free to add new commands to the workflow, however **_note_** that triggered workflows will use the actions +from `main` (default) branch, meaning they will take effect only after the PR with new changes/command is merged. +If you want to test the new command, it's better to test in your fork and local-to-fork PRs, where you control +the default branch. + +### Examples +The regex in cmd.yml is: `^(\/cmd )([-\/\s\w.=:]+)$` accepts only alphanumeric, space, "-", "/", "=", ":", "." chars. + +`/cmd bench --runtime bridge-hub-westend --pallet=pallet_name` +`/cmd update-ui --image=docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v202407161507 --clean` diff --git a/docs/contributor/weight-generation.md b/docs/contributor/weight-generation.md new file mode 100644 index 000000000000..ebfdca59cae5 --- /dev/null +++ b/docs/contributor/weight-generation.md @@ -0,0 +1,69 @@ +# Weight Generation + +To generate weights for a runtime. +Weights generation is using self-hosted runner which is provided by Parity CI, the rest commands are using standard +GitHub runners on `ubuntu-latest` or `ubuntu-20.04`. +Self-hosted runner for benchmarks (arc-runners-Polkadot-sdk-benchmark) is configured to meet requirements of reference +hardware for running validators +https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware + +In a PR run the actions through comment: + +```sh +/cmd bench --help # outputs the actual usage documentation with examples and supported runtimes + +# or + +/cmd --help # to see all available commands +``` + +To regenerate all weights (however it will take long, +so don't do it unless you really need it), run the following command: +```sh +/cmd bench +``` + +To generate weights for all pallets in a particular runtime(s), run the following command: +```sh +/cmd bench --runtime kusama polkadot +``` + +For Substrate pallets (supports sub-modules too): +```sh +/cmd bench --runtime dev --pallet pallet_asset_conversion_ops +``` + +> **📝 Note**: The action is not being run right-away, it will be queued and run in the next available runner. +So might be quick, but might also take up to 10 mins (That's in control of Github). +Once the action is run, you'll see reaction 👀 on original comment, and if you didn't pass `--quiet` - +it will also send a link to a pipeline when started, and link to whole workflow when finished. + +--- + +> **💡Hint #1** : if you run all runtimes or all pallets, it might be that some pallet in the middle is failed +to generate weights, thus it stops (fails) the whole pipeline. +> If you want, you can make it to continue running, even if some pallets are failed, add `--continue-on-fail` +flag to the command. The report will include which runtimes/pallets have failed, then you can re-run +them separately after all is done. + +This way it runs all possible runtimes for the specified pallets, if it finds them in the runtime +```sh +/cmd bench --pallet pallet_balances pallet_xcm_benchmarks::generic pallet_xcm_benchmarks::fungible +``` + +If you want to run all specific pallet(s) for specific runtime(s), you can do it like this: +```sh +/cmd bench --runtime bridge-hub-polkadot --pallet pallet_xcm_benchmarks::generic pallet_xcm_benchmarks::fungible +``` + + +> **💡Hint #2** : Sometimes when you run too many commands, or they keep failing and you're rerunning them again, +it's handy to add `--clean` flag to the command. This will clean up all yours and bot's comments in PR relevant to +/cmd commands. + +```sh +/cmd bench --runtime kusama polkadot --pallet=pallet_balances --clean --continue-on-fail +``` + +> **💡Hint #3** : If you have questions or need help, feel free to tag @paritytech/opstooling (in github comments) +or ping in [matrix](https://matrix.to/#/#command-bot:parity.io) channel. diff --git a/docs/images/Polkadot_Logo_Horizontal_Pink_BlackOnWhite.png b/docs/images/Polkadot_Logo_Horizontal_Pink_BlackOnWhite.png new file mode 100644 index 000000000000..ef2b997100ea Binary files /dev/null and b/docs/images/Polkadot_Logo_Horizontal_Pink_BlackOnWhite.png differ diff --git a/docs/images/Polkadot_Logo_Horizontal_Pink_WhiteOnBlack.png b/docs/images/Polkadot_Logo_Horizontal_Pink_WhiteOnBlack.png new file mode 100644 index 000000000000..421a38e1bdfa Binary files /dev/null and b/docs/images/Polkadot_Logo_Horizontal_Pink_WhiteOnBlack.png differ diff --git a/docs/images/backport-ex2.png b/docs/images/backport-ex2.png new file mode 100644 index 000000000000..97ccf6b00fb9 Binary files /dev/null and b/docs/images/backport-ex2.png differ diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index 424758c32b34..adc1c1a8efbc 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -31,7 +31,7 @@ simple-mermaid = "0.1.1" docify = { workspace = true } # Polkadot SDK deps, typically all should only be in scope such that we can link to their doc item. -polkadot-sdk = { features = ["runtime"], workspace = true, default-features = true } +polkadot-sdk = { features = ["runtime-full"], workspace = true, default-features = true } node-cli = { workspace = true } kitchensink-runtime = { workspace = true } chain-spec-builder = { workspace = true, default-features = true } diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs index c45f0126337e..195d1b124474 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs @@ -32,10 +32,7 @@ use frame::{ runtime, }, prelude::*, - runtime::{ - apis::{self, impl_runtime_apis, ExtrinsicInclusionMode}, - prelude::*, - }, + runtime::{apis, prelude::*}, }; use sp_genesis_builder::PresetId; diff --git a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs index a18e667253d0..41418bcc511f 100644 --- a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs +++ b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs @@ -74,11 +74,12 @@ fn main() -> Result<(), String> { .map_err(|e| e.to_string())?; println!("{}", average_usage); - // We expect no variance for received and sent - // but use 0.001 because we operate with floats + // We expect some small variance for received and sent because the + // test messages are generated at every benchmark run and they contain + // random data so use 0.01 as the accepted variance. messages.extend(average_usage.check_network_usage(&[ - ("Received from peers", 52941.6071, 0.001), - ("Sent to peers", 63810.1859, 0.001), + ("Received from peers", 52941.6071, 0.01), + ("Sent to peers", 63995.2200, 0.01), ])); messages.extend(average_usage.check_cpu_usage(&[ ("approval-distribution", 6.3912, 0.1), diff --git a/polkadot/node/service/chain-specs/kusama.json b/polkadot/node/service/chain-specs/kusama.json index 5d2413ac1e02..ff38192906da 100644 --- a/polkadot/node/service/chain-specs/kusama.json +++ b/polkadot/node/service/chain-specs/kusama.json @@ -37,7 +37,8 @@ "/dns/ibp-boot-kusama.luckyfriday.io/tcp/30333/p2p/12D3KooW9vu1GWHBuxyhm7rZgD3fhGZpNajPXFexadvhujWMgwfT", "/dns/boot-kusama.luckyfriday.io/tcp/443/wss/p2p/12D3KooWS1Lu6DmK8YHSvkErpxpcXmk14vG6y4KVEFEkd9g62PP8", "/dns/ibp-boot-kusama.luckyfriday.io/tcp/30334/wss/p2p/12D3KooW9vu1GWHBuxyhm7rZgD3fhGZpNajPXFexadvhujWMgwfT", - "/dns4/kusama-0.boot.onfinality.io/tcp/27682/ws/p2p/12D3KooWFrwFo7ry3dEuFwhehGSSN96a5Xdzxot7SWfXeSbhELAe" + "/dns4/kusama-0.boot.onfinality.io/tcp/27682/ws/p2p/12D3KooWFrwFo7ry3dEuFwhehGSSN96a5Xdzxot7SWfXeSbhELAe", + "/dns/kusama.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWJHhnF64TXSmyxNkhPkXAHtYNRy86LuvGQu1LTi5vrJCL" ], "telemetryEndpoints": [ [ diff --git a/polkadot/node/service/chain-specs/paseo.json b/polkadot/node/service/chain-specs/paseo.json index 35ac9aa6aa93..aacfdb025786 100644 --- a/polkadot/node/service/chain-specs/paseo.json +++ b/polkadot/node/service/chain-specs/paseo.json @@ -20,7 +20,8 @@ "/dns/pso16.rotko.net/tcp/33246/p2p/12D3KooWRH8eBMhw8c7bucy6pJfy94q4dKpLkF3pmeGohHmemdRu", "/dns/pso16.rotko.net/tcp/35246/wss/p2p/12D3KooWRH8eBMhw8c7bucy6pJfy94q4dKpLkF3pmeGohHmemdRu", "/dns/paseo-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWBLLFKDGBxCwq3QmU3YwWKXUx953WwprRshJQicYu4Cfr", - "/dns/paseo-boot-ng.dwellir.com/tcp/30354/p2p/12D3KooWBLLFKDGBxCwq3QmU3YwWKXUx953WwprRshJQicYu4Cfr" + "/dns/paseo-boot-ng.dwellir.com/tcp/30354/p2p/12D3KooWBLLFKDGBxCwq3QmU3YwWKXUx953WwprRshJQicYu4Cfr", + "/dns/paseo.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWMdND5nwfCs5M2rfp5kyRo41BGDgD8V67rVRaB3acgZ53" ], "telemetryEndpoints": null, "protocolId": "pas", diff --git a/polkadot/node/service/chain-specs/polkadot.json b/polkadot/node/service/chain-specs/polkadot.json index 553ff1bb3e41..7e1e90f6a8c1 100644 --- a/polkadot/node/service/chain-specs/polkadot.json +++ b/polkadot/node/service/chain-specs/polkadot.json @@ -38,7 +38,8 @@ "/dns/ibp-boot-polkadot.luckyfriday.io/tcp/30333/p2p/12D3KooWEjk6QXrZJ26fLpaajisJGHiz6WiQsR8k7mkM9GmWKnRZ", "/dns/ibp-boot-polkadot.luckyfriday.io/tcp/30334/wss/p2p/12D3KooWEjk6QXrZJ26fLpaajisJGHiz6WiQsR8k7mkM9GmWKnRZ", "/dns/boot-polkadot.luckyfriday.io/tcp/443/wss/p2p/12D3KooWAdyiVAaeGdtBt6vn5zVetwA4z4qfm9Fi2QCSykN1wTBJ", - "/dns4/polkadot-0.boot.onfinality.io/tcp/24446/ws/p2p/12D3KooWT1PWaNdAwYrSr89dvStnoGdH3t4LNRbcVNN4JCtsotkR" + "/dns4/polkadot-0.boot.onfinality.io/tcp/24446/ws/p2p/12D3KooWT1PWaNdAwYrSr89dvStnoGdH3t4LNRbcVNN4JCtsotkR", + "/dns/polkadot.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWEymrFRHz6c17YP3FAyd8kXS5gMRLgkW4U77ZJD2ZNCLZ" ], "telemetryEndpoints": [ [ diff --git a/polkadot/node/service/chain-specs/westend.json b/polkadot/node/service/chain-specs/westend.json index 9059d525689d..08c5bd33b4bd 100644 --- a/polkadot/node/service/chain-specs/westend.json +++ b/polkadot/node/service/chain-specs/westend.json @@ -33,7 +33,8 @@ "/dns/wnd14.rotko.net/tcp/35234/wss/p2p/12D3KooWLK8Zj1uZ46phU3vQwiDVda8tB76S8J26rXZQLHpwWkDJ", "/dns/wnd14.rotko.net/tcp/33234/p2p/12D3KooWLK8Zj1uZ46phU3vQwiDVda8tB76S8J26rXZQLHpwWkDJ", "/dns/ibp-boot-westend.luckyfriday.io/tcp/30333/p2p/12D3KooWDg1YEytdwFFNWroFj6gio4YFsMB3miSbHKgdpJteUMB9", - "/dns/ibp-boot-westend.luckyfriday.io/tcp/30334/wss/p2p/12D3KooWDg1YEytdwFFNWroFj6gio4YFsMB3miSbHKgdpJteUMB9" + "/dns/ibp-boot-westend.luckyfriday.io/tcp/30334/wss/p2p/12D3KooWDg1YEytdwFFNWroFj6gio4YFsMB3miSbHKgdpJteUMB9", + "/dns/westend.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWHaQKkJiTPqeNgqDcW7dfYgJxYwT8YqJMtTkueSu6378V" ], "telemetryEndpoints": [ [ diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index e1f42e1ca86a..a907d310c105 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -486,7 +486,6 @@ fn new_partial( sc_transaction_pool::FullPool, ( impl Fn( - polkadot_rpc::DenyUnsafe, polkadot_rpc::SubscriptionTaskExecutor, ) -> Result, ( @@ -593,15 +592,13 @@ where let chain_spec = config.chain_spec.cloned_box(); let backend = backend.clone(); - move |deny_unsafe, - subscription_executor: polkadot_rpc::SubscriptionTaskExecutor| + move |subscription_executor: polkadot_rpc::SubscriptionTaskExecutor| -> Result { let deps = polkadot_rpc::FullDeps { client: client.clone(), pool: transaction_pool.clone(), select_chain: select_chain.clone(), chain_spec: chain_spec.cloned_box(), - deny_unsafe, babe: polkadot_rpc::BabeDeps { babe_worker_handle: babe_worker_handle.clone(), keystore: keystore.clone(), diff --git a/polkadot/rpc/src/lib.rs b/polkadot/rpc/src/lib.rs index eb0133b6e8fd..0007df908e2b 100644 --- a/polkadot/rpc/src/lib.rs +++ b/polkadot/rpc/src/lib.rs @@ -27,7 +27,7 @@ use sc_consensus_beefy::communication::notification::{ BeefyBestBlockStream, BeefyVersionedFinalityProofStream, }; use sc_consensus_grandpa::FinalityProofProvider; -pub use sc_rpc::{DenyUnsafe, SubscriptionTaskExecutor}; +pub use sc_rpc::SubscriptionTaskExecutor; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_application_crypto::RuntimeAppPublic; @@ -83,8 +83,6 @@ pub struct FullDeps { pub select_chain: SC, /// A copy of the chain spec. pub chain_spec: Box, - /// Whether to deny unsafe calls - pub deny_unsafe: DenyUnsafe, /// BABE specific dependencies. pub babe: BabeDeps, /// GRANDPA specific dependencies. @@ -97,7 +95,13 @@ pub struct FullDeps { /// Instantiate all RPC extensions. pub fn create_full( - FullDeps { client, pool, select_chain, chain_spec, deny_unsafe, babe, grandpa, beefy, backend } : FullDeps, + FullDeps { client, pool, select_chain, chain_spec, babe, grandpa, beefy, backend }: FullDeps< + C, + P, + SC, + B, + AuthorityId, + >, ) -> Result> where C: ProvideRuntimeApi @@ -138,8 +142,8 @@ where finality_provider, } = grandpa; - io.merge(StateMigration::new(client.clone(), backend.clone(), deny_unsafe).into_rpc())?; - io.merge(System::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; + io.merge(StateMigration::new(client.clone(), backend.clone()).into_rpc())?; + io.merge(System::new(client.clone(), pool.clone()).into_rpc())?; io.merge(TransactionPayment::new(client.clone()).into_rpc())?; io.merge( Mmr::new( @@ -151,8 +155,7 @@ where .into_rpc(), )?; io.merge( - Babe::new(client.clone(), babe_worker_handle.clone(), keystore, select_chain, deny_unsafe) - .into_rpc(), + Babe::new(client.clone(), babe_worker_handle.clone(), keystore, select_chain).into_rpc(), )?; io.merge( Grandpa::new( diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index d09962ef2b44..19df1f8c3607 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -557,7 +557,7 @@ pub mod pallet { /// The list is sorted ascending by session index. Also, this list can only contain at most /// 2 items: for the next session and for the `scheduled_session`. #[pallet::storage] - pub(crate) type PendingConfigs = + pub type PendingConfigs = StorageValue<_, Vec<(SessionIndex, HostConfiguration>)>, ValueQuery>; /// If this is set, then the configuration setters will bypass the consistency checks. This diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 50970965e11e..4aaaf94da586 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -339,4 +339,4 @@ runtime-metrics = [ # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["metadata-hash", "sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash"] diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 4226595cd2ff..83c0eb037f4a 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -358,4 +358,4 @@ runtime-metrics = [ # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["metadata-hash", "sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash"] diff --git a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_inclusion.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_inclusion.rs index 36a4c5c24c9a..0d14a6226177 100644 --- a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_inclusion.rs +++ b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_inclusion.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +<<<<<<< HEAD //! Autogenerated weights for `polkadot_runtime_parachains::inclusion` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 @@ -36,6 +37,32 @@ // --chain=westend-dev // --header=./polkadot/file_header.txt // --output=./polkadot/runtime/westend/src/weights/ +======= +//! Autogenerated weights for `runtime_parachains::inclusion` +//! +//! 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: `[]` +//! 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 + +// Executed Command: +// ./target/production/polkadot +// benchmark +// pallet +// --chain=westend-dev +// --steps=50 +// --repeat=20 +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --pallet=runtime_parachains::inclusion +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --header=./file_header.txt +// --output=./runtime/westend/src/weights/runtime_parachains_inclusion.rs +>>>>>>> master #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -45,6 +72,7 @@ use frame_support::{traits::Get, weights::Weight}; use core::marker::PhantomData; +<<<<<<< HEAD /// Weight functions for `polkadot_runtime_parachains::inclusion`. pub struct WeightInfo(PhantomData); impl polkadot_runtime_parachains::inclusion::WeightInfo for WeightInfo { @@ -127,5 +155,31 @@ impl polkadot_runtime_parachains::inclusion::WeightInfo .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(c.into()))) .saturating_add(Weight::from_parts(0, 76098).saturating_mul(c.into())) .saturating_add(Weight::from_parts(0, 2551).saturating_mul(h.into())) +======= +/// Weight functions for `runtime_parachains::inclusion`. +pub struct WeightInfo(PhantomData); +impl polkadot_runtime_parachains::inclusion::WeightInfo for WeightInfo { + /// Storage: MessageQueue BookStateFor (r:1 w:1) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(55), added: 2530, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:1 w:999) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(131122), added: 133597, mode: MaxEncodedLen) + /// Storage: unknown `0x3a72656c61795f64697370617463685f71756575655f72656d61696e696e675f` (r:0 w:1) + /// Proof Skipped: unknown `0x3a72656c61795f64697370617463685f71756575655f72656d61696e696e675f` (r:0 w:1) + /// Storage: unknown `0xf5207f03cfdce586301014700e2c2593fad157e461d71fd4c1f936839a5f1f3e` (r:0 w:1) + /// Proof Skipped: unknown `0xf5207f03cfdce586301014700e2c2593fad157e461d71fd4c1f936839a5f1f3e` (r:0 w:1) + /// The range of component `i` is `[1, 1000]`. + fn receive_upward_messages(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `131297` + // Estimated: `134587` + // Minimum execution time: 209_898_000 picoseconds. + Weight::from_parts(210_955_000, 0) + .saturating_add(Weight::from_parts(0, 134587)) + // Standard Error: 97_069 + .saturating_add(Weight::from_parts(207_030_437, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) +>>>>>>> master } } diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs index e96ed6af73d9..1e90338a0f18 100644 --- a/polkadot/statement-table/src/generic.rs +++ b/polkadot/statement-table/src/generic.rs @@ -477,10 +477,7 @@ impl Table { if !context.is_member_of(&from, &votes.group_id) { let sig = match vote { ValidityVote::Valid(s) => s, - ValidityVote::Issued(_) => panic!( - "implicit issuance vote only cast from `import_candidate` after \ - checking group membership of issuer; qed" - ), + ValidityVote::Issued(s) => s, }; return Err(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { diff --git a/prdoc/pr_4792.prdoc b/prdoc/pr_4792.prdoc new file mode 100644 index 000000000000..5ce4303bcf75 --- /dev/null +++ b/prdoc/pr_4792.prdoc @@ -0,0 +1,62 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "rpc: bind to `ipv6` if available and add `CLI --experimental-rpc-endpoint` to specify listen addr" + +doc: + - audience: Node Operator + description: | + This PR changes/adds the following: + + 1. The default setting is that substrate starts a rpc server that listens to localhost both ipv4 and ipv6 on the same port. + ipv6 is allowed to fail because some platforms may not support it + 2. A new RPC CLI option `--experimental-rpc-endpoint` is introduced which allows to configure arbitrary listen addresses including the port, + if this is enabled no other interfaces are enabled. + 3. If the local addr is not found for any of the sockets the server is not started and throws an error. + 4. Remove the deny_unsafe from the RPC implementations instead this is an extension to allow different polices for different interfaces/sockets + such one may enable unsafe on local interface and safe on only the external interface. + 5. This new `--experimental-rpc-endpoint` has several options and in the help menu all possible parameters are documented. + 6. The log emitted by jsonrpc server when it has been started has been modified to indicate all started rpc endpoints. + + So for instance it's now possible to start up three RPC endpoints as follows: + ``` + $ polkadot --experimental-rpc-endpoint "listen-addr=127.0.0.1:9944,methods=unsafe" --experimental-rpc-endpoint "listen-addr=0.0.0.0:9945,methods=safe,rate-limit=100" --experimental-rpc-endpoint "listen-addr=[::1]:9944,optional=true" + ``` + +crates: + - name: sc-rpc-server + bump: major + - name: sc-rpc + bump: major + - name: sc-cli + bump: major + - name: sc-service + bump: major + - name: sc-rpc-api + bump: patch + - name: polkadot-dispute-distribution + bump: patch + - name: polkadot-parachain-lib + bump: patch + - name: substrate-frame-rpc-system + bump: major + - name: substrate-state-trie-migration-rpc + bump: major + - name: cumulus-client-cli + bump: major + validate: false + - name: sc-consensus-beefy-rpc + bump: major + validate: false + - name: sc-consensus-grandpa-rpc + bump: major + validate: false + - name: sc-consensus-babe-rpc + bump: major + validate: false + - name: polkadot-rpc + bump: major + validate: false + - name: polkadot-service + bump: major + validate: false diff --git a/prdoc/pr_4956.prdoc b/prdoc/pr_4956.prdoc new file mode 100644 index 000000000000..a72ca303aaca --- /dev/null +++ b/prdoc/pr_4956.prdoc @@ -0,0 +1,39 @@ +title: Add build options to the srtool build step and delete `disanle-logging` feature + +doc: +- audience: Runtime Dev + description: | + This PR adds possibility to set BUILD_OPTIONS to the "Srtool Build\" step in the release pipeline while building runtimes. + And deletes the `disable-logging` feature from test runtimes to be able to build those with an activated logging. + + + +crates: +- name: people-rococo-runtime + bump: patch +- name: people-westend-runtime + bump: patch +- name: rococo-parachain-runtime + bump: patch +- name: asset-hub-rococo-runtime + bump: patch +- name: asset-hub-westend-runtime + bump: patch +- name: bridge-hub-rococo-runtime + bump: patch +- name: bridge-hub-westend-runtime + bump: patch +- name: collectives-westend-runtime + bump: patch +- name: contracts-rococo-runtime + bump: patch +- name: coretime-rococo-runtime + bump: patch +- name: coretime-westend-runtime + bump: patch +- name: glutton-westend-runtime + bump: patch +- name: rococo-runtime + bump: patch +- name: westend-runtime + bump: patch diff --git a/prdoc/pr_4962.prdoc b/prdoc/pr_4962.prdoc new file mode 100644 index 000000000000..0957f85b6628 --- /dev/null +++ b/prdoc/pr_4962.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Removed `pallet::getter` usage from the pallet-treasury + +doc: + - audience: Runtime Dev + description: | + This PR removed `pallet::getter`s from `pallet-treasury`s storage items. + Instead use the syntax `StorageItem::::get()`. + +crates: + - name: pallet-treasury + bump: minor \ No newline at end of file diff --git a/prdoc/pr_5155.prdoc b/prdoc/pr_5155.prdoc new file mode 100644 index 000000000000..373522eea1c3 --- /dev/null +++ b/prdoc/pr_5155.prdoc @@ -0,0 +1,27 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Use umbrella crate for minimal template + +doc: + - audience: Runtime Dev + description: | + Minor additions to the `polkadot-sdk-frame` crate and making it ready for usage in more templates. This PR already integrates it in the minimal template. + + +crates: + - name: polkadot-sdk + bump: major + - name: polkadot-sdk-frame + bump: patch + - name: sp-wasm-interface + bump: patch + - name: pallet-revive + bump: patch + - name: pallet-revive-fixtures + bump: patch + - name: frame-support + bump: patch + - name: pallet-balances + bump: patch + diff --git a/prdoc/pr_5269.prdoc b/prdoc/pr_5269.prdoc new file mode 100644 index 000000000000..e4401f2406ce --- /dev/null +++ b/prdoc/pr_5269.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Added the possibility to build a parachain node with block number u64 + +doc: + - audience: Node Dev + description: | + Added the possibility to build a parachain node with block number u64. + +crates: + - name: polkadot-parachain-lib + bump: minor + - name: polkadot-parachain-bin + bump: patch diff --git a/prdoc/pr_5293.prdoc b/prdoc/pr_5293.prdoc new file mode 100644 index 000000000000..90528a224e8d --- /dev/null +++ b/prdoc/pr_5293.prdoc @@ -0,0 +1,22 @@ +title: Add initial version of pallet_revive + +doc: + - audience: Runtime Dev + description: | + Adds initial **experimental** version of the new pallet_revive. It will run PolkaVM + contracts which were recompiled from YUL using the revive compiler. Do not use the + pallet in production, yet. It is work in progress. + +crates: + - name: polkadot-sdk + bump: minor + - name: pallet-revive + bump: minor + - name: pallet-revive-fixtures + bump: minor + - name: pallet-revive-proc-macro + bump: minor + - name: pallet-revive-uapi + bump: minor + - name: pallet-revive-mock-network + bump: minor diff --git a/prdoc/pr_5344.prdoc b/prdoc/pr_5344.prdoc new file mode 100644 index 000000000000..9f83c113686d --- /dev/null +++ b/prdoc/pr_5344.prdoc @@ -0,0 +1,10 @@ +title: Fix storage weight reclaim bug. + +doc: + - audience: Node Dev + description: | + Improvement in slot worker loop that will not call create inherent data providers if the major sync is in progress. Before it was called every slot and the results were discarded during major sync. + +crates: + - name: sc-consensus-slots + bump: minor diff --git a/prdoc/pr_5354.prdoc b/prdoc/pr_5354.prdoc new file mode 100644 index 000000000000..e3037b66fbca --- /dev/null +++ b/prdoc/pr_5354.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix benchmark failures when using insecure_zero_ed flag + +doc: + - audience: Runtime Dev + description: | + Currently, when the pallet is compiled with the insecure_zero_ed flag, benchmarks fail because the minimum balance is set to zero. + + The PR aims to resolve this issue by implementing a placeholder value for the minimum balance when the insecure_zero_ed flag is active. it ensures that benchmarks run successfully regardless of whether this flag is used or not + +crates: +- name: pallet-balances + bump: minor diff --git a/prdoc/pr_5384.prdoc b/prdoc/pr_5384.prdoc new file mode 100644 index 000000000000..74d477f8e153 --- /dev/null +++ b/prdoc/pr_5384.prdoc @@ -0,0 +1,25 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "`MaybeConsideration` extension trait for `Consideration`" + +doc: + - audience: Runtime Dev + description: | + The trait allows for the management of tickets that may represent no cost. While + the `MaybeConsideration` still requires proper handling, it introduces the ability + to determine if a ticket represents no cost and can be safely forgotten without any + side effects. + + The new trait is particularly useful when a consumer expects the cost to be zero under + certain conditions (e.g., when the proposal count is below a threshold N) and does not want + to store such consideration tickets in storage. The extension approach allows us to avoid + breaking changes to the existing trait and to continue using it as a non-optional version + for migrating pallets that utilize the `Currency` and `fungible` traits for `holds` and + `freezes`, without requiring any storage migration. + +crates: + - name: frame-support + bump: minor + - name: pallet-balances + bump: patch diff --git a/prdoc/pr_5393.prdoc b/prdoc/pr_5393.prdoc new file mode 100644 index 000000000000..7fcf3067fabc --- /dev/null +++ b/prdoc/pr_5393.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Allow to enable full PoV size + +doc: + - audience: Node Dev + description: | + A feature is introduced allowing a collator to enable full PoV size at compile time. + WARNING: To use this feature, security considerations must be understood and the latest + SDK version must be used. + +crates: + - name: cumulus-client-consensus-aura + bump: minor diff --git a/prdoc/pr_5410.prdoc b/prdoc/pr_5410.prdoc new file mode 100644 index 000000000000..d0a32bec7423 --- /dev/null +++ b/prdoc/pr_5410.prdoc @@ -0,0 +1,11 @@ +title: Reactive syncing metrics + +doc: + - audience: Node Dev + description: | + Syncing metrics are now updated immediate as changes happen rather than every 1100ms as it was happening before. + This resulted in minor, but breaking API changes. + +crates: + - name: sc-network-sync + bump: major diff --git a/prdoc/pr_5424.prdoc b/prdoc/pr_5424.prdoc new file mode 100644 index 000000000000..a94bf7aaeba2 --- /dev/null +++ b/prdoc/pr_5424.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Suppress the log output for transaction propagation when no transactions are present + +doc: + - audience: Node Dev + description: | + Previously, the log message `Propagating transactions` would always be printed, even when there were no transactions to propagate. This patch optimizes the logging by returning early when no transactions are present, resulting in cleaner and more relevant log output. + +crates: +- name: sc-network-transactions + bump: none diff --git a/prdoc/pr_5430.prdoc b/prdoc/pr_5430.prdoc new file mode 100644 index 000000000000..83d6d81e252e --- /dev/null +++ b/prdoc/pr_5430.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "pallet-collator-selection: correctly register weight in `new_session`" + +doc: + - audience: Runtime Dev + description: | + - Fixes an incorrect usage of the `WeightInfo` trait for `new_session`. + +crates: + - name: pallet-collator-selection + bump: patch diff --git a/prdoc/pr_5439.prdoc b/prdoc/pr_5439.prdoc new file mode 100644 index 000000000000..00fa48de0e25 --- /dev/null +++ b/prdoc/pr_5439.prdoc @@ -0,0 +1,16 @@ +title: "Deprecated calls removed in cumulus parachain system pallet" + +doc: + - audience: [Runtime Dev, Runtime User] + description: | + Call `authorize_upgrade` in parachain system pallet `cumulus-pallet-parachain-system` has + been removed, use `authorize_upgrade` or `authorize_upgrade_without_checks` calls in system + pallet `frame-system` instead. + Call `enact_authorized_upgrade` in parachain system pallet `cumulus-pallet-parachain-system` + has been removed, use `apply_authorized_upgrade` call in system pallet `frame-system` instead. + +crates: + - name: cumulus-pallet-parachain-system + bump: major + - name: cumulus-pallet-xcmp-queue + bump: none diff --git a/prdoc/pr_5442.prdoc b/prdoc/pr_5442.prdoc new file mode 100644 index 000000000000..6adc34d71ad3 --- /dev/null +++ b/prdoc/pr_5442.prdoc @@ -0,0 +1,10 @@ +title: Derive `Clone` on `EncodableOpaqueLeaf` + +doc: + - audience: Runtime Dev + description: | + `Clone` was derived on `EncodableOpaqueLeaf` for convenience of downstream users + +crates: + - name: sp-mmr-primitives + bump: patch diff --git a/prdoc/pr_5443.prdoc b/prdoc/pr_5443.prdoc new file mode 100644 index 000000000000..0fd396be06a2 --- /dev/null +++ b/prdoc/pr_5443.prdoc @@ -0,0 +1,10 @@ +crates: +- name: frame-remote-externalities + bump: patch +doc: +- audience: Runtime Dev + description: as part of https://github.com/paritytech/devops/issues/3502, try-runtime + nodes were migrated to a new provider with a new domain address. The PR fixes + all the references to try-runtime nodes on rococo, westend, kusama and polkadot + networks +title: change try-runtime rpc domains diff --git a/prdoc/pr_5450.prdoc b/prdoc/pr_5450.prdoc new file mode 100644 index 000000000000..ccd319cad246 --- /dev/null +++ b/prdoc/pr_5450.prdoc @@ -0,0 +1,18 @@ +title: Sync status refactoring + +doc: + - audience: Node Dev + description: | + `SyncingService` API in `sc-network-sync` has changed with some of the redundant methods related to sync status + removed that were mostly used internally or for testing purposes and is unlikely to impact external code. + `ExtendedPeerInfo` now has working `Clone` and `Copy` implementation. + +crates: + - name: sc-informant + bump: major + - name: sc-network-sync + bump: major + - name: sc-network-test + bump: major + - name: sc-service + bump: major diff --git a/prdoc/pr_5466.prdoc b/prdoc/pr_5466.prdoc new file mode 100644 index 000000000000..57f20b3585b4 --- /dev/null +++ b/prdoc/pr_5466.prdoc @@ -0,0 +1,14 @@ +crates: +- bump: patch + name: frame-omni-bencher +- bump: patch + name: frame-benchmarking-cli +doc: +- audience: Runtime Dev + description: | + Changes: + - Set default level to `Info` again. Seems like a dependency update set it to something higher. + - Fix docs to not use `--locked` since we rely on dependency bumps via cargo. + - Add README with rust docs. + - Fix bug where the node ignored `--heap-pages` argument. +title: frame-omni-bencher maintenance diff --git a/prdoc/pr_5467.prdoc b/prdoc/pr_5467.prdoc new file mode 100644 index 000000000000..2634c255e168 --- /dev/null +++ b/prdoc/pr_5467.prdoc @@ -0,0 +1,10 @@ +title: Make PendingConfigs storage item public + +doc: + - audience: Runtime Dev + description: | + Make PendingConfigs storage item in polkadot's configuration crate public. + +crates: + - name: polkadot-runtime-parachains + bump: minor \ No newline at end of file diff --git a/scripts/bench-all.sh b/scripts/bench-all.sh deleted file mode 100755 index e5512e26bbad..000000000000 --- a/scripts/bench-all.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -set -eu -o pipefail -shopt -s inherit_errexit -shopt -s globstar - -. "$(realpath "$(dirname "${BASH_SOURCE[0]}")/command-utils.sh")" - -get_arg optional --pallet "$@" -PALLET="${out:-""}" - -if [[ ! -z "$PALLET" ]]; then - . "$(dirname "${BASH_SOURCE[0]}")/lib/bench-all-pallet.sh" "$@" -else - . "$(dirname "${BASH_SOURCE[0]}")/bench.sh" --subcommand=all "$@" -fi diff --git a/scripts/bench.sh b/scripts/bench.sh deleted file mode 100755 index 2f4ef7ec6a14..000000000000 --- a/scripts/bench.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/bash -# Initially based on https://github.com/paritytech/bench-bot/blob/cd3b2943d911ae29e41fe6204788ef99c19412c3/bench.js - -# Most external variables used in this script, such as $GH_CONTRIBUTOR, are -# related to https://github.com/paritytech/try-runtime-bot - -# This script relies on $GITHUB_TOKEN which is probably a protected GitLab CI -# variable; if this assumption holds true, it is implied that this script should -# be ran only on protected pipelines - -set -eu -o pipefail -shopt -s inherit_errexit - -# realpath allows to reuse the current -BENCH_ROOT_DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")") - -. "$(realpath "$(dirname "${BASH_SOURCE[0]}")/command-utils.sh")" - -repository_name="$(basename "$PWD")" - -get_arg optional --target_dir "$@" -target_dir="${out:-""}" - -get_arg optional --noexit "$@" -noexit="${out:-""}" - -output_path="." - -profile="production" - -if [[ "$repository_name" == "polkadot-sdk" ]]; then - output_path="./$target_dir" -fi - -cargo_run_benchmarks="cargo run --quiet --profile=${profile}" - -echo "Repository: $repository_name" -echo "Target Dir: $target_dir" -echo "Output Path: $output_path" - -cargo_run() { - echo "Running $cargo_run_benchmarks" "${args[@]}" - - # if not patched with PATCH_something=123 then use --locked - if [[ -z "${BENCH_PATCHED:-}" ]]; then - cargo_run_benchmarks+=" --locked" - fi - - $cargo_run_benchmarks "${args[@]}" -} - - -main() { - - # Remove the "github" remote since the same repository might be reused by a - # GitLab runner, therefore the remote might already exist from a previous run - # in case it was not cleaned up properly for some reason - &>/dev/null git remote remove github || : - - tmp_dirs=() - cleanup() { - exit_code=$? - # Clean up the "github" remote at the end since it contains the - # $GITHUB_TOKEN secret, which is only available for protected pipelines on - # GitLab - &>/dev/null git remote remove github || : - rm -rf "${tmp_dirs[@]}" - echo "Done, exit: $exit_code" - exit $exit_code - } - - # avoid exit if --noexit is passed - if [ -z "$noexit" ]; then - trap cleanup EXIT - fi - - # set -x - - get_arg required --subcommand "$@" - local subcommand="${out:-""}" - - case "$subcommand" in - runtime|pallet|xcm) - echo 'Running bench_pallet' - . "$BENCH_ROOT_DIR/lib/bench-pallet.sh" "$@" - ;; - overhead) - echo 'Running bench_overhead' - . "$BENCH_ROOT_DIR/lib/bench-overhead.sh" "$@" - ;; - all) - echo "Running all-$target_dir" - . "$BENCH_ROOT_DIR/lib/bench-all-${target_dir}.sh" "$@" - ;; - *) - die "Invalid subcommand $subcommand to process_args" - ;; - esac - - # set +x - - # in case we used diener to patch some dependency during benchmark execution, - # revert the patches so that they're not included in the diff - git checkout --quiet HEAD Cargo.toml - - # Save the generated weights to GitLab artifacts in case commit+push fails - echo "Showing weights diff for command" - git diff -P | tee -a "${ARTIFACTS_DIR}/weights.patch" - echo "Wrote weights patch to \"${ARTIFACTS_DIR}/weights.patch\"" - - - # instead of using `cargo run --locked`, we allow the Cargo files to be updated - # but avoid committing them. It is so `cmd_runner_apply_patches` can work - git restore --staged Cargo.* -} - -main "$@" diff --git a/scripts/command-utils.sh b/scripts/command-utils.sh deleted file mode 100644 index 252e4c86480e..000000000000 --- a/scripts/command-utils.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env bash - -if [ "${LOADED_UTILS_SH:-}" ]; then - return -else - export LOADED_UTILS_SH=true -fi - -export ARTIFACTS_DIR="$PWD/.git/.artifacts" - -die() { - if [ "${1:-}" ]; then - >&2 echo "$1" - fi - exit 1 -} - -get_arg() { - local arg_type="$1" - shift - - local is_required - case "$arg_type" in - required|required-many) - is_required=true - ;; - optional|optional-many) ;; - *) - die "Invalid is_required argument \"$2\" in get_arg" - ;; - esac - - local has_many_values - if [ "${arg_type: -6}" == "-many" ]; then - has_many_values=true - fi - - local option_arg="$1" - shift - - local args=("$@") - - unset out - out=() - - local get_next_arg - for arg in "${args[@]}"; do - if [ "${get_next_arg:-}" ]; then - out+=("$arg") - unset get_next_arg - if [ ! "${has_many_values:-}" ]; then - break - fi - # --foo=bar (get the value after '=') - elif [ "${arg:0:$(( ${#option_arg} + 1 ))}" == "$option_arg=" ]; then - out+=("${arg:$(( ${#option_arg} + 1 ))}") - if [ ! "${has_many_values:-}" ]; then - break - fi - # --foo bar (get the next argument) - elif [ "$arg" == "$option_arg" ]; then - get_next_arg=true - fi - done - - # arg list ended with --something but no argument was provided next - if [ "${get_next_arg:-}" ]; then - die "Expected argument after \"${args[-1]}"\" - fi - - if [ "${out[0]:-}" ]; then - if [ ! "${has_many_values:-}" ]; then - out="${out[0]}" - fi - elif [ "${is_required:-}" ]; then - die "Argument $option_arg is required, but was not found" - else - unset out - fi -} diff --git a/scripts/generate-umbrella.py b/scripts/generate-umbrella.py index 3293c30bc828..e1ef6de86f9c 100644 --- a/scripts/generate-umbrella.py +++ b/scripts/generate-umbrella.py @@ -64,7 +64,7 @@ def main(path, version): if manifest['lib']['proc-macro']: nostd_crates.append((crate, path)) continue - + # Crates without a lib.rs cannot be no_std if not os.path.exists(lib_path): print(f"Skipping {crate.name} as it does not have a 'src/lib.rs'") @@ -86,16 +86,18 @@ def main(path, version): # Sort by name std_crates.sort(key=lambda x: x[0].name) nostd_crates.sort(key=lambda x: x[0].name) + + runtime_crates = [crate for crate in nostd_crates if 'frame' in crate[0].name or crate[0].name.startswith('sp-')] all_crates = std_crates + nostd_crates all_crates.sort(key=lambda x: x[0].name) dependencies = {} for (crate, path) in nostd_crates: dependencies[crate.name] = {"path": f"../{path}", "default-features": False, "optional": True} - + for (crate, path) in std_crates: dependencies[crate.name] = {"path": f"../{path}", "default-features": False, "optional": True} - + # The empty features are filled by Zepter features = { "default": [ "std" ], @@ -105,9 +107,11 @@ def main(path, version): "serde": [], "experimental": [], "with-tracing": [], - "runtime": list([f"{d.name}" for d, _ in nostd_crates]), + "runtime-full": list([f"{d.name}" for d, _ in nostd_crates]), + "runtime": list([f"{d.name}" for d, _ in runtime_crates]), "node": ["std"] + list([f"{d.name}" for d, _ in std_crates]), "tuples-96": [], + "riscv": [], } manifest = { @@ -119,7 +123,7 @@ def main(path, version): "description": "Polkadot SDK umbrella crate.", "license": "Apache-2.0", "metadata": { "docs": { "rs": { - "features": ["runtime", "node"], + "features": ["runtime-full", "node"], "targets": ["x86_64-unknown-linux-gnu"] }}} }, @@ -159,9 +163,9 @@ def main(path, version): f.write(f'\n/// {desc}') f.write(f'\n#[cfg(feature = "{crate.name}")]\n') f.write(f"pub use {use};\n") - + print(f"Wrote {lib_path}") - + add_to_workspace(workspace.path) """ @@ -188,7 +192,7 @@ def add_to_workspace(path): manifest = re.sub(r'^members = \[', 'members = [\n "umbrella",', manifest, flags=re.M) with open(os.path.join(path, "Cargo.toml"), "w") as f: f.write(manifest) - + os.chdir(path) # hack os.system("cargo metadata --format-version 1 > /dev/null") # update the lockfile os.system(f"zepter") # enable the features @@ -203,3 +207,4 @@ def parse_args(): if __name__ == "__main__": args = parse_args() main(args.sdk, args.version) + diff --git a/scripts/lib/bench-all-cumulus.sh b/scripts/lib/bench-all-cumulus.sh deleted file mode 100755 index f4c2a35c6b6b..000000000000 --- a/scripts/lib/bench-all-cumulus.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env bash -# originally moved from https://github.com/paritytech/cumulus/blob/445f9277ab55b4d930ced4fbbb38d27c617c6658/scripts/benchmarks-ci.sh - -# default RUST_LOG is warn, but could be overridden -export RUST_LOG="${RUST_LOG:-error}" - -THIS_DIR=$(dirname "${BASH_SOURCE[0]}") -. "$THIS_DIR/../command-utils.sh" - -POLKADOT_PARACHAIN="./target/$profile/polkadot-parachain" - -run_cumulus_bench() { - local artifactsDir="$ARTIFACTS_DIR" - local category=$1 - local runtimeName=$2 - local paraId=${3:-} - - local benchmarkOutput="$output_path/parachains/runtimes/$category/$runtimeName/src/weights" - local benchmarkRuntimeChain - if [[ ! -z "$paraId" ]]; then - benchmarkRuntimeChain="${runtimeName}-dev-$paraId" - else - benchmarkRuntimeChain="$runtimeName-dev" - fi - - local benchmarkMetadataOutputDir="$artifactsDir/$runtimeName" - mkdir -p "$benchmarkMetadataOutputDir" - - # Load all pallet names in an array. - echo "[+] Listing pallets for runtime $runtimeName for chain: $benchmarkRuntimeChain ..." - local pallets=($( - $POLKADOT_PARACHAIN benchmark pallet --list --chain="${benchmarkRuntimeChain}" |\ - tail -n+2 |\ - cut -d',' -f1 |\ - sort |\ - uniq - )) - - if [ ${#pallets[@]} -ne 0 ]; then - echo "[+] Benchmarking ${#pallets[@]} pallets for runtime $runtimeName for chain: $benchmarkRuntimeChain, pallets:" - for pallet in "${pallets[@]}"; do - echo " [+] $pallet" - done - else - echo "$runtimeName pallet list not found in benchmarks-ci.sh" - exit 1 - fi - - for pallet in "${pallets[@]}"; do - # (by default) do not choose output_file, like `pallet_assets.rs` because it does not work for multiple instances - # `benchmark pallet` command will decide the output_file name if there are multiple instances - local output_file="" - local extra_args="" - # a little hack for pallet_xcm_benchmarks - we want to force custom implementation for XcmWeightInfo - if [[ "$pallet" == "pallet_xcm_benchmarks::generic" ]] || [[ "$pallet" == "pallet_xcm_benchmarks::fungible" ]]; then - output_file="xcm/${pallet//::/_}.rs" - extra_args="--template=$output_path/templates/xcm-bench-template.hbs" - fi - $POLKADOT_PARACHAIN benchmark pallet \ - $extra_args \ - --chain="${benchmarkRuntimeChain}" \ - --wasm-execution=compiled \ - --pallet="$pallet" \ - --no-storage-info \ - --no-median-slopes \ - --no-min-squares \ - --extrinsic='*' \ - --steps=50 \ - --repeat=20 \ - --json \ - --header="$output_path/file_header.txt" \ - --output="${benchmarkOutput}/${output_file}" >> "$benchmarkMetadataOutputDir/${pallet//::/_}_benchmark.json" - done -} - - -echo "[+] Compiling benchmarks..." -cargo build --profile $profile --locked --features=runtime-benchmarks -p polkadot-parachain-bin - -# Run benchmarks for all pallets of a given runtime if runtime argument provided -get_arg optional --runtime "$@" -runtime="${out:-""}" - -if [[ $runtime ]]; then - paraId="" - case "$runtime" in - asset-*) - category="assets" - ;; - collectives-*) - category="collectives" - ;; - coretime-*) - category="coretime" - ;; - bridge-*) - category="bridge-hubs" - ;; - contracts-*) - category="contracts" - ;; - people-*) - category="people" - ;; - glutton-*) - category="glutton" - paraId="1300" - ;; - *) - echo "Unknown runtime: $runtime" - exit 1 - ;; - esac - - run_cumulus_bench $category $runtime $paraId - -else # run all - # Assets - run_cumulus_bench assets asset-hub-rococo - run_cumulus_bench assets asset-hub-westend - - # Collectives - run_cumulus_bench collectives collectives-westend - - # Coretime - run_cumulus_bench coretime coretime-rococo - run_cumulus_bench coretime coretime-westend - - # People - run_cumulus_bench people people-rococo - run_cumulus_bench people people-westend - - # Bridge Hubs - run_cumulus_bench bridge-hubs bridge-hub-rococo - run_cumulus_bench bridge-hubs bridge-hub-westend - - # Glutton - run_cumulus_bench glutton glutton-westend 1300 -fi diff --git a/scripts/lib/bench-all-pallet.sh b/scripts/lib/bench-all-pallet.sh deleted file mode 100644 index e6908045ddbd..000000000000 --- a/scripts/lib/bench-all-pallet.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env bash - -set -eu -o pipefail -shopt -s inherit_errexit -shopt -s globstar - -. "$(dirname "${BASH_SOURCE[0]}")/../command-utils.sh" - -get_arg required --pallet "$@" -PALLET="${out:-""}" - -REPO_NAME="$(basename "$PWD")" -BASE_COMMAND="$(dirname "${BASH_SOURCE[0]}")/../../bench/bench.sh --noexit=true --subcommand=pallet" - -WEIGHT_FILE_PATHS=( $(find . -type f -name "${PALLET}.rs" -path "**/weights/*" | sed 's|^\./||g') ) - -# convert pallet_ranked_collective to ranked-collective -CLEAN_PALLET=$(echo $PALLET | sed 's/pallet_//g' | sed 's/_/-/g') - -# add substrate pallet weights to a list -SUBSTRATE_PALLET_PATH=$(ls substrate/frame/$CLEAN_PALLET/src/weights.rs || :) -if [ ! -z "${SUBSTRATE_PALLET_PATH}" ]; then - WEIGHT_FILE_PATHS+=("$SUBSTRATE_PALLET_PATH") -fi - -# add trappist pallet weights to a list -TRAPPIST_PALLET_PATH=$(ls pallet/$CLEAN_PALLET/src/weights.rs || :) -if [ ! -z "${TRAPPIST_PALLET_PATH}" ]; then - WEIGHT_FILE_PATHS+=("$TRAPPIST_PALLET_PATH") -fi - -COMMANDS=() - -if [ "${#WEIGHT_FILE_PATHS[@]}" -eq 0 ]; then - echo "No weights files found for pallet: $PALLET" - exit 1 -else - echo "Found weights files for pallet: $PALLET" -fi - -for f in ${WEIGHT_FILE_PATHS[@]}; do - echo "- $f" - # f examples: - # cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_balances.rs - # polkadot/runtime/rococo/src/weights/pallet_balances.rs - # runtime/trappist/src/weights/pallet_assets.rs - TARGET_DIR=$(echo $f | cut -d'/' -f 1) - - if [ "$REPO_NAME" == "polkadot-sdk" ]; then - case $TARGET_DIR in - cumulus) - TYPE=$(echo $f | cut -d'/' -f 2) - # Example: cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_balances.rs - if [ "$TYPE" == "parachains" ]; then - RUNTIME=$(echo $f | cut -d'/' -f 5) - RUNTIME_DIR=$(echo $f | cut -d'/' -f 4) - COMMANDS+=("$BASE_COMMAND --runtime=$RUNTIME --runtime_dir=$RUNTIME_DIR --target_dir=$TARGET_DIR --pallet=$PALLET") - fi - ;; - polkadot) - # Example: polkadot/runtime/rococo/src/weights/pallet_balances.rs - RUNTIME=$(echo $f | cut -d'/' -f 3) - COMMANDS+=("$BASE_COMMAND --runtime=$RUNTIME --target_dir=$TARGET_DIR --pallet=$PALLET") - ;; - substrate) - # Example: substrate/frame/contracts/src/weights.rs - COMMANDS+=("$BASE_COMMAND --target_dir=$TARGET_DIR --runtime=dev --pallet=$PALLET") - ;; - *) - echo "Unknown dir: $TARGET_DIR" - exit 1 - ;; - esac - fi - - if [ "$REPO_NAME" == "trappist" ]; then - case $TARGET_DIR in - runtime) - TYPE=$(echo $f | cut -d'/' -f 2) - if [ "$TYPE" == "trappist" || "$TYPE" == "stout" ]; then - # Example: runtime/trappist/src/weights/pallet_assets.rs - COMMANDS+=("$BASE_COMMAND --target_dir=trappist --runtime=$TYPE --pallet=$PALLET") - fi - ;; - *) - echo "Unknown dir: $TARGET_DIR" - exit 1 - ;; - esac - fi -done - -for cmd in "${COMMANDS[@]}"; do - echo "Running command: $cmd" - . $cmd -done diff --git a/scripts/lib/bench-all-polkadot.sh b/scripts/lib/bench-all-polkadot.sh deleted file mode 100644 index ac52e00140e3..000000000000 --- a/scripts/lib/bench-all-polkadot.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash - -# Runs all benchmarks for all pallets, for a given runtime, provided by $1 -# Should be run on a reference machine to gain accurate benchmarks -# current reference machine: https://github.com/paritytech/polkadot/pull/6508/files -# original source: https://github.com/paritytech/polkadot/blob/b9842c4b52f6791fef6c11ecd020b22fe614f041/scripts/run_all_benches.sh - -get_arg required --runtime "$@" -runtime="${out:-""}" - -# default RUST_LOG is error, but could be overridden -export RUST_LOG="${RUST_LOG:-error}" - -echo "[+] Compiling benchmarks..." -cargo build --profile $profile --locked --features=runtime-benchmarks -p polkadot - -POLKADOT_BIN="./target/$profile/polkadot" - -# Update the block and extrinsic overhead weights. -echo "[+] Benchmarking block and extrinsic overheads..." -OUTPUT=$( - $POLKADOT_BIN benchmark overhead \ - --chain="${runtime}-dev" \ - --wasm-execution=compiled \ - --weight-path="$output_path/runtime/${runtime}/constants/src/weights/" \ - --warmup=10 \ - --repeat=100 \ - --header="$output_path/file_header.txt" -) -if [ $? -ne 0 ]; then - echo "$OUTPUT" >> "$ERR_FILE" - echo "[-] Failed to benchmark the block and extrinsic overheads. Error written to $ERR_FILE; continuing..." -fi - - -# Load all pallet names in an array. -PALLETS=($( - $POLKADOT_BIN benchmark pallet --list --chain="${runtime}-dev" |\ - tail -n+2 |\ - cut -d',' -f1 |\ - sort |\ - uniq -)) - -echo "[+] Benchmarking ${#PALLETS[@]} pallets for runtime $runtime" - -# Define the error file. -ERR_FILE="${ARTIFACTS_DIR}/benchmarking_errors.txt" -# Delete the error file before each run. -rm -f $ERR_FILE - -# Benchmark each pallet. -for PALLET in "${PALLETS[@]}"; do - echo "[+] Benchmarking $PALLET for $runtime"; - - output_file="" - if [[ $PALLET == *"::"* ]]; then - # translates e.g. "pallet_foo::bar" to "pallet_foo_bar" - output_file="${PALLET//::/_}.rs" - fi - - OUTPUT=$( - $POLKADOT_BIN benchmark pallet \ - --chain="${runtime}-dev" \ - --steps=50 \ - --repeat=20 \ - --no-storage-info \ - --no-median-slopes \ - --no-min-squares \ - --pallet="$PALLET" \ - --extrinsic="*" \ - --execution=wasm \ - --wasm-execution=compiled \ - --header="$output_path/file_header.txt" \ - --output="$output_path/runtime/${runtime}/src/weights/${output_file}" 2>&1 - ) - if [ $? -ne 0 ]; then - echo "$OUTPUT" >> "$ERR_FILE" - echo "[-] Failed to benchmark $PALLET. Error written to $ERR_FILE; continuing..." - fi -done - -# Check if the error file exists. -if [ -f "$ERR_FILE" ]; then - echo "[-] Some benchmarks failed. See: $ERR_FILE" -else - echo "[+] All benchmarks passed." -fi diff --git a/scripts/lib/bench-all-substrate.sh b/scripts/lib/bench-all-substrate.sh deleted file mode 100644 index eeb18cdd8bbb..000000000000 --- a/scripts/lib/bench-all-substrate.sh +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env bash - -# This file is part of Substrate. -# Copyright (C) 2022 Parity Technologies (UK) Ltd. -# SPDX-License-Identifier: Apache-2.0 -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This script has three parts which all use the Substrate runtime: -# - Pallet benchmarking to update the pallet weights -# - Overhead benchmarking for the Extrinsic and Block weights -# - Machine benchmarking -# -# Should be run on a reference machine to gain accurate benchmarks -# current reference machine: https://github.com/paritytech/substrate/pull/5848 - -# Original source: https://github.com/paritytech/substrate/blob/ff9921a260a67e3a71f25c8b402cd5c7da787a96/scripts/run_all_benchmarks.sh -# Fail if any sub-command in a pipe fails, not just the last one. -set -o pipefail -# Fail on undeclared variables. -set -u -# Fail if any sub-command fails. -set -e -# Fail on traps. -# set -E - -# default RUST_LOG is warn, but could be overridden -export RUST_LOG="${RUST_LOG:-error}" - -echo "[+] Compiling Substrate benchmarks..." -cargo build --profile=$profile --locked --features=runtime-benchmarks -p staging-node-cli - -# The executable to use. -SUBSTRATE="./target/$profile/substrate-node" - -# Manually exclude some pallets. -EXCLUDED_PALLETS=( - # Helper pallets - "pallet_election_provider_support_benchmarking" - # Pallets without automatic benchmarking - "pallet_babe" - "pallet_grandpa" - "pallet_mmr" - "pallet_offences" - # Only used for testing, does not need real weights. - "frame_benchmarking_pallet_pov" - "pallet_example_tasks" - "pallet_example_basic" - "pallet_example_split" - "pallet_example_kitchensink" - "pallet_example_mbm" - "tasks_example" -) - -# Load all pallet names in an array. -ALL_PALLETS=($( - $SUBSTRATE benchmark pallet --list --chain=dev |\ - tail -n+2 |\ - cut -d',' -f1 |\ - sort |\ - uniq -)) - -# Define the error file. -ERR_FILE="${ARTIFACTS_DIR}/benchmarking_errors.txt" - -# Delete the error file before each run. -rm -f "$ERR_FILE" - -mkdir -p "$(dirname "$ERR_FILE")" - -# Update the block and extrinsic overhead weights. -echo "[+] Benchmarking block and extrinsic overheads..." -OUTPUT=$( - $SUBSTRATE benchmark overhead \ - --chain=dev \ - --wasm-execution=compiled \ - --weight-path="$output_path/frame/support/src/weights/" \ - --header="$output_path/HEADER-APACHE2" \ - --warmup=10 \ - --repeat=100 2>&1 -) -if [ $? -ne 0 ]; then - echo "$OUTPUT" >> "$ERR_FILE" - echo "[-] Failed to benchmark the block and extrinsic overheads. Error written to $ERR_FILE; continuing..." -fi - -echo "[+] Benchmarking ${#ALL_PALLETS[@]} Substrate pallets and excluding ${#EXCLUDED_PALLETS[@]}." - -echo "[+] Excluded pallets ${EXCLUDED_PALLETS[@]}" -echo "[+] ------ " -echo "[+] Whole list pallets ${ALL_PALLETS[@]}" - -# Benchmark each pallet. -for PALLET in "${ALL_PALLETS[@]}"; do - FOLDER="$(echo "${PALLET#*_}" | tr '_' '-')"; - WEIGHT_FILE="$output_path/frame/${FOLDER}/src/weights.rs" - - # Skip the pallet if it is in the excluded list. - - if [[ " ${EXCLUDED_PALLETS[@]} " =~ " ${PALLET} " ]]; then - echo "[+] Skipping $PALLET as it is in the excluded list." - continue - fi - - echo "[+] Benchmarking $PALLET with weight file $WEIGHT_FILE"; - - set +e # Disable exit on error for the benchmarking of the pallets - OUTPUT=$( - $SUBSTRATE benchmark pallet \ - --chain=dev \ - --steps=50 \ - --repeat=20 \ - --pallet="$PALLET" \ - --no-storage-info \ - --no-median-slopes \ - --no-min-squares \ - --extrinsic="*" \ - --wasm-execution=compiled \ - --heap-pages=4096 \ - --output="$WEIGHT_FILE" \ - --header="$output_path/HEADER-APACHE2" \ - --template="$output_path/.maintain/frame-weight-template.hbs" 2>&1 - ) - if [ $? -ne 0 ]; then - echo -e "$PALLET: $OUTPUT\n" >> "$ERR_FILE" - echo "[-] Failed to benchmark $PALLET. Error written to $ERR_FILE; continuing..." - fi - set -e # Re-enable exit on error -done - - -# Check if the error file exists. -if [ -s "$ERR_FILE" ]; then - echo "[-] Some benchmarks failed. See: $ERR_FILE" - exit 1 -else - echo "[+] All benchmarks passed." -fi diff --git a/scripts/lib/bench-overhead.sh b/scripts/lib/bench-overhead.sh deleted file mode 100644 index c4cca8b4c128..000000000000 --- a/scripts/lib/bench-overhead.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -THIS_DIR=$(dirname "${BASH_SOURCE[0]}") -. "$THIS_DIR/../command-utils.sh" - -bench_overhead_common_args=( - -- - benchmark - overhead - --wasm-execution=compiled - --warmup=10 - --repeat=100 -) -bench_overhead() { - local args - case "$target_dir" in - substrate) - args=( - --bin=substrate - "${bench_overhead_common_args[@]}" - --header="$output_path/HEADER-APACHE2" - --weight-path="$output_path/frame/support/src/weights" - --chain="dev" - ) - ;; - polkadot) - get_arg required --runtime "$@" - local runtime="${out:-""}" - args=( - --bin=polkadot - "${bench_overhead_common_args[@]}" - --header="$output_path/file_header.txt" - --weight-path="$output_path/runtime/$runtime/constants/src/weights" - --chain="$runtime-dev" - ) - ;; - cumulus) - get_arg required --runtime "$@" - local runtime="${out:-""}" - args=( - -p=polkadot-parachain-bin - "${bench_overhead_common_args[@]}" - --header="$output_path/file_header.txt" - --weight-path="$output_path/parachains/runtimes/assets/$runtime/src/weights" - --chain="$runtime" - ) - ;; - trappist) - get_arg required --runtime "$@" - local runtime="${out:-""}" - args=( - "${bench_overhead_common_args[@]}" - --header="$output_path/templates/file_header.txt" - --weight-path="$output_path/runtime/$runtime/src/weights" - --chain="$runtime-dev" - ) - ;; - *) - die "Target Dir \"$target_dir\" is not supported in bench_overhead" - ;; - esac - - cargo_run "${args[@]}" -} - -bench_overhead "$@" diff --git a/scripts/lib/bench-pallet.sh b/scripts/lib/bench-pallet.sh deleted file mode 100644 index 15eac31e3a45..000000000000 --- a/scripts/lib/bench-pallet.sh +++ /dev/null @@ -1,178 +0,0 @@ -#!/bin/bash - -THIS_DIR=$(dirname "${BASH_SOURCE[0]}") -. "$THIS_DIR/../command-utils.sh" - -bench_pallet_common_args=( - -- - benchmark - pallet - --steps=50 - --repeat=20 - --extrinsic="*" - --wasm-execution=compiled - --heap-pages=4096 - --json-file="${ARTIFACTS_DIR}/bench.json" -) -bench_pallet() { - get_arg required --subcommand "$@" - local subcommand="${out:-""}" - - get_arg required --runtime "$@" - local runtime="${out:-""}" - - get_arg required --pallet "$@" - local pallet="${out:-""}" - - local args - case "$target_dir" in - substrate) - args=( - --features=runtime-benchmarks - --manifest-path="$output_path/bin/node/cli/Cargo.toml" - "${bench_pallet_common_args[@]}" - --pallet="$pallet" - --chain="$runtime" - ) - - case "$subcommand" in - pallet) - # Translates e.g. "pallet_foo::bar" to "pallet_foo_bar" - local output_dir="${pallet//::/_}" - - # Substrate benchmarks are output to the "frame" directory but they aren't - # named exactly after the $pallet argument. For example: - # - When $pallet == pallet_balances, the output folder is frame/balances - # - When $pallet == frame_benchmarking, the output folder is frame/benchmarking - # The common pattern we infer from those examples is that we should remove - # the prefix - if [[ "$output_dir" =~ ^[A-Za-z]*[^A-Za-z](.*)$ ]]; then - output_dir="${BASH_REMATCH[1]}" - fi - - # We also need to translate '_' to '-' due to the folders' naming - # conventions - output_dir="${output_dir//_/-}" - - args+=( - --header="$output_path/HEADER-APACHE2" - --output="$output_path/frame/$output_dir/src/weights.rs" - --template="$output_path/.maintain/frame-weight-template.hbs" - ) - ;; - *) - die "Subcommand $subcommand is not supported for $target_dir in bench_pallet" - ;; - esac - ;; - polkadot) - # For backward compatibility: replace "-dev" with "" - runtime=${runtime/-dev/} - - local weights_dir="$output_path/runtime/${runtime}/src/weights" - - args=( - --bin=polkadot - --features=runtime-benchmarks - "${bench_pallet_common_args[@]}" - --pallet="$pallet" - --chain="${runtime}-dev" - ) - - case "$subcommand" in - pallet) - args+=( - --header="$output_path/file_header.txt" - --output="${weights_dir}/" - ) - ;; - xcm) - args+=( - --header="$output_path/file_header.txt" - --template="$output_path/xcm/pallet-xcm-benchmarks/template.hbs" - --output="${weights_dir}/xcm/" - ) - ;; - *) - die "Subcommand $subcommand is not supported for $target_dir in bench_pallet" - ;; - esac - ;; - cumulus) - get_arg required --runtime_dir "$@" - local runtime_dir="${out:-""}" - local chain="$runtime" - - # to support specifying parachain id from runtime name (e.g. ["glutton-westend", "glutton-westend-dev-1300"]) - # If runtime ends with "-dev" or "-dev-\d+", leave as it is, otherwise concat "-dev" at the end of $chain - if [[ ! "$runtime" =~ -dev(-[0-9]+)?$ ]]; then - chain="${runtime}-dev" - fi - - # replace "-dev" or "-dev-\d+" with "" for runtime - runtime=$(echo "$runtime" | sed 's/-dev.*//g') - - args=( - -p=polkadot-parachain-bin - --features=runtime-benchmarks - "${bench_pallet_common_args[@]}" - --pallet="$pallet" - --chain="${chain}" - --header="$output_path/file_header.txt" - ) - - case "$subcommand" in - pallet) - args+=( - --output="$output_path/parachains/runtimes/$runtime_dir/$runtime/src/weights/" - ) - ;; - xcm) - mkdir -p "$output_path/parachains/runtimes/$runtime_dir/$runtime/src/weights/xcm" - args+=( - --template="$output_path/templates/xcm-bench-template.hbs" - --output="$output_path/parachains/runtimes/$runtime_dir/$runtime/src/weights/xcm/" - ) - ;; - *) - die "Subcommand $subcommand is not supported for $target_dir in bench_pallet" - ;; - esac - ;; - trappist) - local weights_dir="$output_path/runtime/$runtime/src/weights" - - args=( - --features=runtime-benchmarks - "${bench_pallet_common_args[@]}" - --pallet="$pallet" - --chain="${runtime}-dev" - --header="$output_path/templates/file_header.txt" - ) - - case "$subcommand" in - pallet) - args+=( - --output="${weights_dir}/" - ) - ;; - xcm) - args+=( - --template="$output_path/templates/xcm-bench-template.hbs" - --output="${weights_dir}/xcm/" - ) - ;; - *) - die "Subcommand $subcommand is not supported for $target_dir in bench_pallet" - ;; - esac - ;; - *) - die "Repository $target_dir is not supported in bench_pallet" - ;; - esac - - cargo_run "${args[@]}" -} - -bench_pallet "$@" diff --git a/scripts/sync.sh b/scripts/sync.sh deleted file mode 100755 index b5d8a5219937..000000000000 --- a/scripts/sync.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env bash - -set -eu -o pipefail - -. "$(realpath "$(dirname "${BASH_SOURCE[0]}")/command-utils.sh")" - - -# Function to check syncing status -check_syncing() { - # Send the system_health request and parse the isSyncing field - RESPONSE=$(curl -sSX POST http://127.0.0.1:9944 \ - --header 'Content-Type: application/json' \ - --data-raw '{"jsonrpc": "2.0", "method": "system_health", "params": [], "id": "1"}') - - # Check for errors in the curl command - if [ $? -ne 0 ]; then - echo "Error: Unable to send request to Polkadot node" - fi - - IS_SYNCING=$(echo $RESPONSE | jq -r '.result.isSyncing') - - # Check for errors in the jq command or missing field in the response - if [ $? -ne 0 ] || [ "$IS_SYNCING" == "null" ]; then - echo "Error: Unable to parse sync status from response" - fi - - # Return the isSyncing value - echo $IS_SYNCING -} - -main() { - get_arg required --chain "$@" - local chain="${out:-""}" - - get_arg required --type "$@" - local type="${out:-""}" - - export RUST_LOG="${RUST_LOG:-remote-ext=debug,runtime=trace}" - - cargo build --release - - cp "./target/release/polkadot" ./polkadot-bin - - # Start sync. - # "&" runs the process in the background - # "> /dev/tty" redirects the output of the process to the terminal - ./polkadot-bin --sync="$type" --chain="$chain" > "$ARTIFACTS_DIR/sync.log" 2>&1 & - - # Get the PID of process - POLKADOT_SYNC_PID=$! - - sleep 10 - - # Poll the node every 100 seconds until syncing is complete - while :; do - SYNC_STATUS="$(check_syncing)" - if [ "$SYNC_STATUS" == "true" ]; then - echo "Node is still syncing..." - sleep 100 - elif [ "$SYNC_STATUS" == "false" ]; then - echo "Node sync is complete!" - kill "$POLKADOT_SYNC_PID" # Stop the Polkadot node process once syncing is complete - exit 0 # Success - elif [[ "$SYNC_STATUS" = Error:* ]]; then - echo "$SYNC_STATUS" - exit 1 # Error - else - echo "Unknown error: $SYNC_STATUS" - exit 1 # Unknown error - fi - done -} - -main "$@" diff --git a/scripts/update-ui-tests.sh b/scripts/update-ui-tests.sh deleted file mode 100755 index d363e51e4041..000000000000 --- a/scripts/update-ui-tests.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -# Script for updating the UI tests for a new rust stable version. -# Exit on error -set -e - -# by default current rust stable will be used -RUSTUP_RUN="" -# check if we have a parameter -# ./scripts/update-ui-tests.sh 1.70 -if [ ! -z "$1" ]; then - echo "RUST_VERSION: $1" - # This will run all UI tests with the rust stable 1.70. - # The script requires that rustup is installed. - RUST_VERSION=$1 - RUSTUP_RUN="rustup run $RUST_VERSION" - - - echo "installing rustup $RUST_VERSION" - if ! command -v rustup &> /dev/null - then - echo "rustup needs to be installed" - exit - fi - - rustup install $RUST_VERSION - rustup component add rust-src --toolchain $RUST_VERSION -fi - -# Ensure we run the ui tests -export RUN_UI_TESTS=1 -# We don't need any wasm files for ui tests -export SKIP_WASM_BUILD=1 -# Let trybuild overwrite the .stderr files -export TRYBUILD=overwrite - -# ./substrate -$RUSTUP_RUN cargo test --manifest-path substrate/primitives/runtime-interface/Cargo.toml ui -$RUSTUP_RUN cargo test -p sp-api-test ui -$RUSTUP_RUN cargo test -p frame-election-provider-solution-type ui -$RUSTUP_RUN cargo test -p frame-support-test --features=no-metadata-docs,try-runtime,experimental ui -$RUSTUP_RUN cargo test -p xcm-procedural ui diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 5b827c9d718f..6e734a723cd3 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -104,6 +104,10 @@ try-runtime = [ "polkadot-sdk/try-runtime", "substrate-cli-test-utils/try-runtime", ] +riscv = [ + "kitchensink-runtime/riscv", + "polkadot-sdk/riscv", +] [[bench]] name = "transaction_pool" diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index d45713db5222..8fdcc7261b55 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -177,7 +177,6 @@ pub fn new_partial( sc_transaction_pool::FullPool, ( impl Fn( - node_rpc::DenyUnsafe, sc_rpc::SubscriptionTaskExecutor, ) -> Result, sc_service::Error>, ( @@ -318,13 +317,12 @@ pub fn new_partial( let rpc_backend = backend.clone(); let rpc_statement_store = statement_store.clone(); let rpc_extensions_builder = - move |deny_unsafe, subscription_executor: node_rpc::SubscriptionTaskExecutor| { + move |subscription_executor: node_rpc::SubscriptionTaskExecutor| { let deps = node_rpc::FullDeps { client: client.clone(), pool: pool.clone(), select_chain: select_chain.clone(), chain_spec: chain_spec.cloned_box(), - deny_unsafe, babe: node_rpc::BabeDeps { keystore: keystore.clone(), babe_worker_handle: babe_worker_handle.clone(), diff --git a/substrate/bin/node/rpc/Cargo.toml b/substrate/bin/node/rpc/Cargo.toml index d85998e3c87b..02f5d9a4a702 100644 --- a/substrate/bin/node/rpc/Cargo.toml +++ b/substrate/bin/node/rpc/Cargo.toml @@ -31,7 +31,6 @@ sc-consensus-grandpa = { workspace = true, default-features = true } sc-consensus-grandpa-rpc = { workspace = true, default-features = true } sc-mixnet = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } -sc-rpc-api = { workspace = true, default-features = true } sc-sync-state-rpc = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } diff --git a/substrate/bin/node/rpc/src/lib.rs b/substrate/bin/node/rpc/src/lib.rs index c55e03ee9d6f..988502bb2bfd 100644 --- a/substrate/bin/node/rpc/src/lib.rs +++ b/substrate/bin/node/rpc/src/lib.rs @@ -44,7 +44,6 @@ use sc_consensus_grandpa::{ FinalityProofProvider, GrandpaJustificationStream, SharedAuthoritySet, SharedVoterState, }; pub use sc_rpc::SubscriptionTaskExecutor; -pub use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_application_crypto::RuntimeAppPublic; @@ -97,8 +96,6 @@ pub struct FullDeps { pub select_chain: SC, /// A copy of the chain spec. pub chain_spec: Box, - /// Whether to deny unsafe calls - pub deny_unsafe: DenyUnsafe, /// BABE specific dependencies. pub babe: BabeDeps, /// GRANDPA specific dependencies. @@ -120,7 +117,6 @@ pub fn create_full( pool, select_chain, chain_spec, - deny_unsafe, babe, grandpa, beefy, @@ -175,7 +171,7 @@ where finality_provider, } = grandpa; - io.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; + io.merge(System::new(client.clone(), pool).into_rpc())?; // Making synchronous calls in light client freezes the browser currently, // more context: https://github.com/paritytech/substrate/pull/3480 // These RPCs should use an asynchronous caller instead. @@ -190,8 +186,7 @@ where )?; io.merge(TransactionPayment::new(client.clone()).into_rpc())?; io.merge( - Babe::new(client.clone(), babe_worker_handle.clone(), keystore, select_chain, deny_unsafe) - .into_rpc(), + Babe::new(client.clone(), babe_worker_handle.clone(), keystore, select_chain).into_rpc(), )?; io.merge( Grandpa::new( @@ -209,10 +204,9 @@ where .into_rpc(), )?; - io.merge(StateMigration::new(client.clone(), backend, deny_unsafe).into_rpc())?; - io.merge(Dev::new(client, deny_unsafe).into_rpc())?; - let statement_store = - sc_rpc::statement::StatementStore::new(statement_store, deny_unsafe).into_rpc(); + io.merge(StateMigration::new(client.clone(), backend).into_rpc())?; + io.merge(Dev::new(client).into_rpc())?; + let statement_store = sc_rpc::statement::StatementStore::new(statement_store).into_rpc(); io.merge(statement_store)?; if let Some(mixnet_api) = mixnet_api { diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 2ad655883916..6310e16d5a14 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -31,7 +31,7 @@ serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } # pallet-asset-conversion: turn on "num-traits" feature primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } -polkadot-sdk = { features = ["runtime", "tuples-96"], workspace = true } +polkadot-sdk = { features = ["runtime-full", "tuples-96"], workspace = true } # shared code between runtime and node node-primitives = { workspace = true } @@ -71,5 +71,5 @@ try-runtime = [ experimental = [ "pallet-example-tasks/experimental", ] - metadata-hash = ["substrate-wasm-builder/metadata-hash"] +riscv = ["polkadot-sdk/riscv"] diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index ea9a66862d64..ef5c52bf6e6e 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1381,6 +1381,34 @@ impl pallet_contracts::Config for Runtime { type Xcm = (); } +impl pallet_revive::Config for Runtime { + type Time = Timestamp; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type CallFilter = Nothing; + type DepositPerItem = DepositPerItem; + type DepositPerByte = DepositPerByte; + type WeightPrice = pallet_transaction_payment::Pallet; + type WeightInfo = pallet_revive::weights::SubstrateWeight; + type ChainExtension = (); + type AddressGenerator = pallet_revive::DefaultAddressGenerator; + type MaxCodeLen = ConstU32<{ 123 * 1024 }>; + type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; + type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; + type UnsafeUnstableInterface = ConstBool; + type UploadOrigin = EnsureSigned; + type InstantiateOrigin = EnsureSigned; + type RuntimeHoldReason = RuntimeHoldReason; + #[cfg(not(feature = "runtime-benchmarks"))] + type Migrations = (); + #[cfg(feature = "runtime-benchmarks")] + type Migrations = pallet_revive::migration::codegen::BenchMigrations; + type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; + type Debug = (); + type Xcm = (); +} + impl pallet_sudo::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; @@ -2482,6 +2510,9 @@ mod runtime { #[runtime::pallet_index(79)] pub type AssetConversionMigration = pallet_asset_conversion_ops::Pallet; + + #[runtime::pallet_index(80)] + pub type Revive = pallet_revive::Pallet; } /// The address format for describing accounts. @@ -2541,6 +2572,7 @@ type Migrations = ( pallet_nomination_pools::migration::versioned::V6ToV7, pallet_alliance::migration::Migration, pallet_contracts::Migration, + pallet_revive::Migration, pallet_identity::migration::versioned::V0ToV1, ); @@ -2593,6 +2625,7 @@ mod benches { [pallet_collective, Council] [pallet_conviction_voting, ConvictionVoting] [pallet_contracts, Contracts] + [pallet_revive, Revive] [pallet_core_fellowship, CoreFellowship] [tasks_example, TasksExample] [pallet_democracy, Democracy] @@ -2955,6 +2988,75 @@ impl_runtime_apis! { } } + impl pallet_revive::ReviveApi for Runtime + { + fn call( + origin: AccountId, + dest: AccountId, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, + input_data: Vec, + ) -> pallet_revive::ContractExecResult { + Revive::bare_call( + RuntimeOrigin::signed(origin), + dest, + value, + gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), + storage_deposit_limit.unwrap_or(u128::MAX), + input_data, + pallet_revive::DebugInfo::UnsafeDebug, + pallet_revive::CollectEvents::UnsafeCollect, + ) + } + + fn instantiate( + origin: AccountId, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, + code: pallet_revive::Code, + data: Vec, + salt: Vec, + ) -> pallet_revive::ContractInstantiateResult + { + Revive::bare_instantiate( + RuntimeOrigin::signed(origin), + value, + gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), + storage_deposit_limit.unwrap_or(u128::MAX), + code, + data, + salt, + pallet_revive::DebugInfo::UnsafeDebug, + pallet_revive::CollectEvents::UnsafeCollect, + ) + } + + fn upload_code( + origin: AccountId, + code: Vec, + storage_deposit_limit: Option, + ) -> pallet_revive::CodeUploadResult + { + Revive::bare_upload_code( + RuntimeOrigin::signed(origin), + code, + storage_deposit_limit.unwrap_or(u128::MAX), + ) + } + + fn get_storage( + address: AccountId, + key: Vec, + ) -> pallet_revive::GetStorageResult { + Revive::get_storage( + address, + key + ) + } + } + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< Block, Balance, diff --git a/substrate/client/cli/src/arg_enums.rs b/substrate/client/cli/src/arg_enums.rs index b5819d03447a..cd245dc01554 100644 --- a/substrate/client/cli/src/arg_enums.rs +++ b/substrate/client/cli/src/arg_enums.rs @@ -168,6 +168,19 @@ pub enum RpcMethods { Unsafe, } +impl FromStr for RpcMethods { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "safe" => Ok(RpcMethods::Safe), + "unsafe" => Ok(RpcMethods::Unsafe), + "auto" => Ok(RpcMethods::Auto), + invalid => Err(format!("Invalid rpc methods {invalid}")), + } + } +} + impl Into for RpcMethods { fn into(self) -> sc_service::config::RpcMethods { match self { diff --git a/substrate/client/cli/src/commands/run_cmd.rs b/substrate/client/cli/src/commands/run_cmd.rs index c1288b502c95..7245b46e2f7d 100644 --- a/substrate/client/cli/src/commands/run_cmd.rs +++ b/substrate/client/cli/src/commands/run_cmd.rs @@ -20,8 +20,8 @@ use crate::{ arg_enums::{Cors, RpcMethods}, error::{Error, Result}, params::{ - ImportParams, KeystoreParams, NetworkParams, OffchainWorkerParams, SharedParams, - TransactionPoolParams, + ImportParams, KeystoreParams, NetworkParams, OffchainWorkerParams, RpcEndpoint, + SharedParams, TransactionPoolParams, }, CliConfiguration, PrometheusParams, RuntimeParams, TelemetryParams, RPC_DEFAULT_MAX_CONNECTIONS, RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB, @@ -37,7 +37,7 @@ use sc_service::{ }; use sc_telemetry::TelemetryEndpoints; use std::{ - net::{IpAddr, Ipv4Addr, SocketAddr}, + net::{Ipv4Addr, Ipv6Addr, SocketAddr}, num::NonZeroU32, }; @@ -128,6 +128,47 @@ pub struct RunCmd { #[arg(long, value_name = "PORT")] pub rpc_port: Option, + /// EXPERIMENTAL: Specify the JSON-RPC server interface and this option which can be enabled + /// several times if you want expose several RPC interfaces with different configurations. + /// + /// The format for this option is: + /// `--experimental-rpc-endpoint" listen-addr=,,..."` where each option is + /// separated by a comma and `listen-addr` is the only required param. + /// + /// The following options are available: + /// • listen-addr: The socket address (ip:port) to listen on. Be careful to not expose the + /// server to the public internet unless you know what you're doing. (required) + /// • disable-batch-requests: Disable batch requests (optional) + /// • max-connections: The maximum number of concurrent connections that the server will + /// accept (optional) + /// • max-request-size: The maximum size of a request body in megabytes (optional) + /// • max-response-size: The maximum size of a response body in megabytes (optional) + /// • max-subscriptions-per-connection: The maximum number of subscriptions per connection + /// (optional) + /// • max-buffer-capacity-per-connection: The maximum buffer capacity per connection + /// (optional) + /// • max-batch-request-len: The maximum number of requests in a batch (optional) + /// • cors: The CORS allowed origins, this can enabled more than once (optional) + /// • methods: Which RPC methods to allow, valid values are "safe", "unsafe" and "auto" + /// (optional) + /// • optional: If the listen address is optional i.e the interface is not required to be + /// available For example this may be useful if some platforms doesn't support ipv6 + /// (optional) + /// • rate-limit: The rate limit in calls per minute for each connection (optional) + /// • rate-limit-trust-proxy-headers: Trust proxy headers for disable rate limiting (optional) + /// • rate-limit-whitelisted-ips: Disable rate limiting for certain ip addresses, this can be + /// enabled more than once (optional) • retry-random-port: If the port is already in use, + /// retry with a random port (optional) + /// + /// Use with care, this flag is unstable and subject to change. + #[arg( + long, + num_args = 1.., + verbatim_doc_comment, + conflicts_with_all = &["rpc_external", "unsafe_rpc_external", "rpc_port", "rpc_cors", "rpc_rate_limit_trust_proxy_headers", "rpc_rate_limit", "rpc_rate_limit_whitelisted_ips", "rpc_message_buffer_capacity_per_connection", "rpc_disable_batch_requests", "rpc_max_subscriptions_per_connection", "rpc_max_request_size", "rpc_max_response_size"] + )] + pub experimental_rpc_endpoint: Vec, + /// Maximum number of RPC server connections. #[arg(long, value_name = "COUNT", default_value_t = RPC_DEFAULT_MAX_CONNECTIONS)] pub rpc_max_connections: u32, @@ -410,15 +451,68 @@ impl CliConfiguration for RunCmd { .into()) } - fn rpc_addr(&self, default_listen_port: u16) -> Result> { - let interface = rpc_interface( + fn rpc_addr(&self, default_listen_port: u16) -> Result>> { + if !self.experimental_rpc_endpoint.is_empty() { + for endpoint in &self.experimental_rpc_endpoint { + // Technically, `0.0.0.0` isn't a public IP address, but it's a way to listen on + // all interfaces. Thus, we consider it as a public endpoint and warn about it. + if endpoint.rpc_methods == RpcMethods::Unsafe && endpoint.is_global() || + endpoint.listen_addr.ip().is_unspecified() + { + log::warn!( + "It isn't safe to expose RPC publicly without a proxy server that filters \ + available set of RPC methods." + ); + } + } + + return Ok(Some(self.experimental_rpc_endpoint.clone())); + } + + let (ipv4, ipv6) = rpc_interface( self.rpc_external, self.unsafe_rpc_external, self.rpc_methods, self.validator, )?; - Ok(Some(SocketAddr::new(interface, self.rpc_port.unwrap_or(default_listen_port)))) + let cors = self.rpc_cors(self.is_dev()?)?; + let port = self.rpc_port.unwrap_or(default_listen_port); + + Ok(Some(vec![ + RpcEndpoint { + batch_config: self.rpc_batch_config()?, + max_connections: self.rpc_max_connections, + listen_addr: SocketAddr::new(std::net::IpAddr::V4(ipv4), port), + rpc_methods: self.rpc_methods, + rate_limit: self.rpc_rate_limit, + rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips.clone(), + max_payload_in_mb: self.rpc_max_request_size, + max_payload_out_mb: self.rpc_max_response_size, + max_subscriptions_per_connection: self.rpc_max_subscriptions_per_connection, + max_buffer_capacity_per_connection: self.rpc_message_buffer_capacity_per_connection, + cors: cors.clone(), + retry_random_port: true, + is_optional: false, + }, + RpcEndpoint { + batch_config: self.rpc_batch_config()?, + max_connections: self.rpc_max_connections, + listen_addr: SocketAddr::new(std::net::IpAddr::V6(ipv6), port), + rpc_methods: self.rpc_methods, + rate_limit: self.rpc_rate_limit, + rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips.clone(), + max_payload_in_mb: self.rpc_max_request_size, + max_payload_out_mb: self.rpc_max_response_size, + max_subscriptions_per_connection: self.rpc_max_subscriptions_per_connection, + max_buffer_capacity_per_connection: self.rpc_message_buffer_capacity_per_connection, + cors: cors.clone(), + retry_random_port: true, + is_optional: true, + }, + ])) } fn rpc_methods(&self) -> Result { @@ -523,7 +617,7 @@ fn rpc_interface( is_unsafe_external: bool, rpc_methods: RpcMethods, is_validator: bool, -) -> Result { +) -> Result<(Ipv4Addr, Ipv6Addr)> { if is_external && is_validator && rpc_methods != RpcMethods::Unsafe { return Err(Error::Input( "--rpc-external option shouldn't be used if the node is running as \ @@ -541,9 +635,9 @@ fn rpc_interface( ); } - Ok(Ipv4Addr::UNSPECIFIED.into()) + Ok((Ipv4Addr::UNSPECIFIED, Ipv6Addr::UNSPECIFIED)) } else { - Ok(Ipv4Addr::LOCALHOST.into()) + Ok((Ipv4Addr::LOCALHOST, Ipv6Addr::LOCALHOST)) } } diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs index 406d1fb264dd..7c2358477611 100644 --- a/substrate/client/cli/src/config.rs +++ b/substrate/client/cli/src/config.rs @@ -20,7 +20,8 @@ use crate::{ arg_enums::Database, error::Result, DatabaseParams, ImportParams, KeystoreParams, - NetworkParams, NodeKeyParams, OffchainWorkerParams, PruningParams, SharedParams, SubstrateCli, + NetworkParams, NodeKeyParams, OffchainWorkerParams, PruningParams, RpcEndpoint, SharedParams, + SubstrateCli, }; use log::warn; use names::{Generator, Name}; @@ -34,7 +35,7 @@ use sc_service::{ BlocksPruning, ChainSpec, TracingReceiver, }; use sc_tracing::logging::LoggerBuilder; -use std::{net::SocketAddr, num::NonZeroU32, path::PathBuf}; +use std::{num::NonZeroU32, path::PathBuf}; /// The maximum number of characters for a node name. pub(crate) const NODE_NAME_MAX_LENGTH: usize = 64; @@ -301,7 +302,7 @@ pub trait CliConfiguration: Sized { } /// Get the RPC address. - fn rpc_addr(&self, _default_listen_port: u16) -> Result> { + fn rpc_addr(&self, _default_listen_port: u16) -> Result>> { Ok(None) } @@ -504,6 +505,10 @@ pub trait CliConfiguration: Sized { let telemetry_endpoints = self.telemetry_endpoints(&chain_spec)?; let runtime_cache_size = self.runtime_cache_size()?; + let rpc_addrs: Option> = self + .rpc_addr(DCV::rpc_listen_port())? + .map(|addrs| addrs.into_iter().map(Into::into).collect()); + Ok(Configuration { impl_name: C::impl_name(), impl_version: C::impl_version(), @@ -527,7 +532,7 @@ pub trait CliConfiguration: Sized { blocks_pruning: self.blocks_pruning()?, wasm_method: self.wasm_method()?, wasm_runtime_overrides: self.wasm_runtime_overrides(), - rpc_addr: self.rpc_addr(DCV::rpc_listen_port())?, + rpc_addr: rpc_addrs, rpc_methods: self.rpc_methods()?, rpc_max_connections: self.rpc_max_connections()?, rpc_cors: self.rpc_cors(is_dev)?, diff --git a/substrate/client/cli/src/params/mod.rs b/substrate/client/cli/src/params/mod.rs index f07223ec6a73..977b57ba0658 100644 --- a/substrate/client/cli/src/params/mod.rs +++ b/substrate/client/cli/src/params/mod.rs @@ -25,6 +25,7 @@ mod node_key_params; mod offchain_worker_params; mod prometheus_params; mod pruning_params; +mod rpc_params; mod runtime_params; mod shared_params; mod telemetry_params; @@ -32,6 +33,7 @@ mod transaction_pool_params; use crate::arg_enums::{CryptoScheme, OutputType}; use clap::Args; +use sc_service::config::{IpNetwork, RpcBatchRequestConfig}; use sp_core::crypto::{Ss58AddressFormat, Ss58AddressFormatRegistry}; use sp_runtime::{ generic::BlockId, @@ -42,7 +44,7 @@ use std::{fmt::Debug, str::FromStr}; pub use crate::params::{ database_params::*, import_params::*, keystore_params::*, message_params::*, mixnet_params::*, network_params::*, node_key_params::*, offchain_worker_params::*, prometheus_params::*, - pruning_params::*, runtime_params::*, shared_params::*, telemetry_params::*, + pruning_params::*, rpc_params::*, runtime_params::*, shared_params::*, telemetry_params::*, transaction_pool_params::*, }; diff --git a/substrate/client/cli/src/params/rpc_params.rs b/substrate/client/cli/src/params/rpc_params.rs new file mode 100644 index 000000000000..d0ec1fd00443 --- /dev/null +++ b/substrate/client/cli/src/params/rpc_params.rs @@ -0,0 +1,395 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::{ + arg_enums::RpcMethods, + params::{IpNetwork, RpcBatchRequestConfig}, + RPC_DEFAULT_MAX_CONNECTIONS, RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB, + RPC_DEFAULT_MAX_SUBS_PER_CONN, RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN, +}; +use std::{net::SocketAddr, num::NonZeroU32}; + +const RPC_LISTEN_ADDR: &str = "listen-addr"; +const RPC_CORS: &str = "cors"; +const RPC_MAX_CONNS: &str = "max-connections"; +const RPC_MAX_REQUEST_SIZE: &str = "max-request-size"; +const RPC_MAX_RESPONSE_SIZE: &str = "max-response-size"; +const RPC_MAX_SUBS_PER_CONN: &str = "max-subscriptions-per-connection"; +const RPC_MAX_BUF_CAP_PER_CONN: &str = "max-buffer-capacity-per-connection"; +const RPC_RATE_LIMIT: &str = "rate-limit"; +const RPC_RATE_LIMIT_TRUST_PROXY_HEADERS: &str = "rate-limit-trust-proxy-headers"; +const RPC_RATE_LIMIT_WHITELISTED_IPS: &str = "rate-limit-whitelisted-ips"; +const RPC_RETRY_RANDOM_PORT: &str = "retry-random-port"; +const RPC_METHODS: &str = "methods"; +const RPC_OPTIONAL: &str = "optional"; +const RPC_DISABLE_BATCH: &str = "disable-batch-requests"; +const RPC_BATCH_LIMIT: &str = "max-batch-request-len"; + +/// Represent a single RPC endpoint with its configuration. +#[derive(Debug, Clone)] +pub struct RpcEndpoint { + /// Listen address. + pub listen_addr: SocketAddr, + /// Batch request configuration. + pub batch_config: RpcBatchRequestConfig, + /// Maximum number of connections. + pub max_connections: u32, + /// Maximum inbound payload size in MB. + pub max_payload_in_mb: u32, + /// Maximum outbound payload size in MB. + pub max_payload_out_mb: u32, + /// Maximum number of subscriptions per connection. + pub max_subscriptions_per_connection: u32, + /// Maximum buffer capacity per connection. + pub max_buffer_capacity_per_connection: u32, + /// Rate limit per minute. + pub rate_limit: Option, + /// Whether to trust proxy headers for rate limiting. + pub rate_limit_trust_proxy_headers: bool, + /// Whitelisted IPs for rate limiting. + pub rate_limit_whitelisted_ips: Vec, + /// CORS. + pub cors: Option>, + /// RPC methods to expose. + pub rpc_methods: RpcMethods, + /// Whether it's an optional listening address i.e, it's ignored if it fails to bind. + /// For example substrate tries to bind both ipv4 and ipv6 addresses but some platforms + /// may not support ipv6. + pub is_optional: bool, + /// Whether to retry with a random port if the provided port is already in use. + pub retry_random_port: bool, +} + +impl std::str::FromStr for RpcEndpoint { + type Err = String; + + fn from_str(s: &str) -> Result { + let mut listen_addr = None; + let mut max_connections = None; + let mut max_payload_in_mb = None; + let mut max_payload_out_mb = None; + let mut max_subscriptions_per_connection = None; + let mut max_buffer_capacity_per_connection = None; + let mut cors: Option> = None; + let mut rpc_methods = None; + let mut is_optional = None; + let mut disable_batch_requests = None; + let mut max_batch_request_len = None; + let mut rate_limit = None; + let mut rate_limit_trust_proxy_headers = None; + let mut rate_limit_whitelisted_ips = Vec::new(); + let mut retry_random_port = None; + + for input in s.split(',') { + let (key, val) = input.trim().split_once('=').ok_or_else(|| invalid_input(input))?; + let key = key.trim(); + let val = val.trim(); + + match key { + RPC_LISTEN_ADDR => { + if listen_addr.is_some() { + return Err(only_once_err(RPC_LISTEN_ADDR)); + } + let val: SocketAddr = + val.parse().map_err(|_| invalid_value(RPC_LISTEN_ADDR, &val))?; + listen_addr = Some(val); + }, + RPC_CORS => { + if val.is_empty() { + return Err(invalid_value(RPC_CORS, &val)); + } + + if let Some(cors) = cors.as_mut() { + cors.push(val.to_string()); + } else { + cors = Some(vec![val.to_string()]); + } + }, + RPC_MAX_CONNS => { + if max_connections.is_some() { + return Err(only_once_err(RPC_MAX_CONNS)); + } + + let val = val.parse().map_err(|_| invalid_value(RPC_MAX_CONNS, &val))?; + max_connections = Some(val); + }, + RPC_MAX_REQUEST_SIZE => { + if max_payload_in_mb.is_some() { + return Err(only_once_err(RPC_MAX_REQUEST_SIZE)); + } + + let val = + val.parse().map_err(|_| invalid_value(RPC_MAX_RESPONSE_SIZE, &val))?; + max_payload_in_mb = Some(val); + }, + RPC_MAX_RESPONSE_SIZE => { + if max_payload_out_mb.is_some() { + return Err(only_once_err(RPC_MAX_RESPONSE_SIZE)); + } + + let val = + val.parse().map_err(|_| invalid_value(RPC_MAX_RESPONSE_SIZE, &val))?; + max_payload_out_mb = Some(val); + }, + RPC_MAX_SUBS_PER_CONN => { + if max_subscriptions_per_connection.is_some() { + return Err(only_once_err(RPC_MAX_SUBS_PER_CONN)); + } + + let val = + val.parse().map_err(|_| invalid_value(RPC_MAX_SUBS_PER_CONN, &val))?; + max_subscriptions_per_connection = Some(val); + }, + RPC_MAX_BUF_CAP_PER_CONN => { + if max_buffer_capacity_per_connection.is_some() { + return Err(only_once_err(RPC_MAX_BUF_CAP_PER_CONN)); + } + + let val = + val.parse().map_err(|_| invalid_value(RPC_MAX_BUF_CAP_PER_CONN, &val))?; + max_buffer_capacity_per_connection = Some(val); + }, + RPC_RATE_LIMIT => { + if rate_limit.is_some() { + return Err(only_once_err("rate-limit")); + } + + let val = val.parse().map_err(|_| invalid_value(RPC_RATE_LIMIT, &val))?; + rate_limit = Some(val); + }, + RPC_RATE_LIMIT_TRUST_PROXY_HEADERS => { + if rate_limit_trust_proxy_headers.is_some() { + return Err(only_once_err(RPC_RATE_LIMIT_TRUST_PROXY_HEADERS)); + } + + let val = val + .parse() + .map_err(|_| invalid_value(RPC_RATE_LIMIT_TRUST_PROXY_HEADERS, &val))?; + rate_limit_trust_proxy_headers = Some(val); + }, + RPC_RATE_LIMIT_WHITELISTED_IPS => { + let ip: IpNetwork = val + .parse() + .map_err(|_| invalid_value(RPC_RATE_LIMIT_WHITELISTED_IPS, &val))?; + rate_limit_whitelisted_ips.push(ip); + }, + RPC_RETRY_RANDOM_PORT => { + if retry_random_port.is_some() { + return Err(only_once_err(RPC_RETRY_RANDOM_PORT)); + } + let val = + val.parse().map_err(|_| invalid_value(RPC_RETRY_RANDOM_PORT, &val))?; + retry_random_port = Some(val); + }, + RPC_METHODS => { + if rpc_methods.is_some() { + return Err(only_once_err("methods")); + } + let val = val.parse().map_err(|_| invalid_value(RPC_METHODS, &val))?; + rpc_methods = Some(val); + }, + RPC_OPTIONAL => { + if is_optional.is_some() { + return Err(only_once_err(RPC_OPTIONAL)); + } + + let val = val.parse().map_err(|_| invalid_value(RPC_OPTIONAL, &val))?; + is_optional = Some(val); + }, + RPC_DISABLE_BATCH => { + if disable_batch_requests.is_some() { + return Err(only_once_err(RPC_DISABLE_BATCH)); + } + + let val = val.parse().map_err(|_| invalid_value(RPC_DISABLE_BATCH, &val))?; + disable_batch_requests = Some(val); + }, + RPC_BATCH_LIMIT => { + if max_batch_request_len.is_some() { + return Err(only_once_err(RPC_BATCH_LIMIT)); + } + + let val = val.parse().map_err(|_| invalid_value(RPC_BATCH_LIMIT, &val))?; + max_batch_request_len = Some(val); + }, + _ => return Err(invalid_key(key)), + } + } + + let listen_addr = listen_addr.ok_or("`listen-addr` must be specified exactly once")?; + + let batch_config = match (disable_batch_requests, max_batch_request_len) { + (Some(true), Some(_)) => { + return Err(format!("`{RPC_BATCH_LIMIT}` and `{RPC_DISABLE_BATCH}` are mutually exclusive and can't be used together")); + }, + (Some(false), None) => RpcBatchRequestConfig::Disabled, + (None, Some(len)) => RpcBatchRequestConfig::Limit(len), + _ => RpcBatchRequestConfig::Unlimited, + }; + + Ok(Self { + listen_addr, + batch_config, + max_connections: max_connections.unwrap_or(RPC_DEFAULT_MAX_CONNECTIONS), + max_payload_in_mb: max_payload_in_mb.unwrap_or(RPC_DEFAULT_MAX_REQUEST_SIZE_MB), + max_payload_out_mb: max_payload_out_mb.unwrap_or(RPC_DEFAULT_MAX_RESPONSE_SIZE_MB), + cors, + max_buffer_capacity_per_connection: max_buffer_capacity_per_connection + .unwrap_or(RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN), + max_subscriptions_per_connection: max_subscriptions_per_connection + .unwrap_or(RPC_DEFAULT_MAX_SUBS_PER_CONN), + rpc_methods: rpc_methods.unwrap_or(RpcMethods::Auto), + rate_limit, + rate_limit_trust_proxy_headers: rate_limit_trust_proxy_headers.unwrap_or(false), + rate_limit_whitelisted_ips, + is_optional: is_optional.unwrap_or(false), + retry_random_port: retry_random_port.unwrap_or(false), + }) + } +} + +impl Into for RpcEndpoint { + fn into(self) -> sc_service::config::RpcEndpoint { + sc_service::config::RpcEndpoint { + batch_config: self.batch_config, + listen_addr: self.listen_addr, + max_buffer_capacity_per_connection: self.max_buffer_capacity_per_connection, + max_connections: self.max_connections, + max_payload_in_mb: self.max_payload_in_mb, + max_payload_out_mb: self.max_payload_out_mb, + max_subscriptions_per_connection: self.max_subscriptions_per_connection, + rpc_methods: self.rpc_methods.into(), + rate_limit: self.rate_limit, + rate_limit_trust_proxy_headers: self.rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: self.rate_limit_whitelisted_ips, + cors: self.cors, + retry_random_port: self.retry_random_port, + is_optional: self.is_optional, + } + } +} + +impl RpcEndpoint { + /// Returns whether the endpoint is globally exposed. + pub fn is_global(&self) -> bool { + let ip = IpNetwork::from(self.listen_addr.ip()); + ip.is_global() + } +} + +fn only_once_err(reason: &str) -> String { + format!("`{reason}` is only allowed be specified once") +} + +fn invalid_input(input: &str) -> String { + format!("`{input}`, expects: `key=value`") +} + +fn invalid_value(key: &str, value: &str) -> String { + format!("value=`{value}` key=`{key}`") +} + +fn invalid_key(key: &str) -> String { + format!("unknown key=`{key}`, see `--help` for available options") +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{num::NonZeroU32, str::FromStr}; + + #[test] + fn parse_rpc_endpoint_works() { + assert!(RpcEndpoint::from_str("listen-addr=127.0.0.1:9944").is_ok()); + assert!(RpcEndpoint::from_str("listen-addr=[::1]:9944").is_ok()); + assert!(RpcEndpoint::from_str("listen-addr=127.0.0.1:9944,methods=auto").is_ok()); + assert!(RpcEndpoint::from_str("listen-addr=[::1]:9944,methods=auto").is_ok()); + assert!(RpcEndpoint::from_str( + "listen-addr=127.0.0.1:9944,methods=auto,cors=*,optional=true" + ) + .is_ok()); + + assert!(RpcEndpoint::from_str("listen-addrs=127.0.0.1:9944,foo=*").is_err()); + assert!(RpcEndpoint::from_str("listen-addrs=127.0.0.1:9944,cors=").is_err()); + } + + #[test] + fn parse_rpc_endpoint_all() { + let endpoint = RpcEndpoint::from_str( + "listen-addr=127.0.0.1:9944,methods=unsafe,cors=*,optional=true,retry-random-port=true,rate-limit=99,\ + max-batch-request-len=100,rate-limit-trust-proxy-headers=true,max-connections=33,max-request-size=4,\ + max-response-size=3,max-subscriptions-per-connection=7,max-buffer-capacity-per-connection=8,\ + rate-limit-whitelisted-ips=192.168.1.0/24,rate-limit-whitelisted-ips=ff01::0/32" + ).unwrap(); + assert_eq!(endpoint.listen_addr, ([127, 0, 0, 1], 9944).into()); + assert_eq!(endpoint.rpc_methods, RpcMethods::Unsafe); + assert_eq!(endpoint.cors, Some(vec!["*".to_string()])); + assert_eq!(endpoint.is_optional, true); + assert_eq!(endpoint.retry_random_port, true); + assert_eq!(endpoint.rate_limit, Some(NonZeroU32::new(99).unwrap())); + assert!(matches!(endpoint.batch_config, RpcBatchRequestConfig::Limit(l) if l == 100)); + assert_eq!(endpoint.rate_limit_trust_proxy_headers, true); + assert_eq!( + endpoint.rate_limit_whitelisted_ips, + vec![ + IpNetwork::V4("192.168.1.0/24".parse().unwrap()), + IpNetwork::V6("ff01::0/32".parse().unwrap()) + ] + ); + assert_eq!(endpoint.max_connections, 33); + assert_eq!(endpoint.max_payload_in_mb, 4); + assert_eq!(endpoint.max_payload_out_mb, 3); + assert_eq!(endpoint.max_subscriptions_per_connection, 7); + assert_eq!(endpoint.max_buffer_capacity_per_connection, 8); + } + + #[test] + fn parse_rpc_endpoint_multiple_cors() { + let addr = RpcEndpoint::from_str( + "listen-addr=127.0.0.1:9944,methods=auto,cors=https://polkadot.js.org,cors=*,cors=localhost:*", + ) + .unwrap(); + + assert_eq!( + addr.cors, + Some(vec![ + "https://polkadot.js.org".to_string(), + "*".to_string(), + "localhost:*".to_string() + ]) + ); + } + + #[test] + fn parse_rpc_endpoint_whitespaces() { + let addr = RpcEndpoint::from_str( + " listen-addr = 127.0.0.1:9944, methods = auto, optional = true ", + ) + .unwrap(); + assert_eq!(addr.rpc_methods, RpcMethods::Auto); + assert_eq!(addr.is_optional, true); + } + + #[test] + fn parse_rpc_endpoint_batch_options_mutually_exclusive() { + assert!(RpcEndpoint::from_str( + "listen-addr = 127.0.0.1:9944,disable-batch-requests=true,max-batch-request-len=100", + ) + .is_err()); + } +} diff --git a/substrate/client/consensus/babe/rpc/src/lib.rs b/substrate/client/consensus/babe/rpc/src/lib.rs index a3e811baecff..338d71a43256 100644 --- a/substrate/client/consensus/babe/rpc/src/lib.rs +++ b/substrate/client/consensus/babe/rpc/src/lib.rs @@ -25,12 +25,13 @@ use jsonrpsee::{ core::async_trait, proc_macros::rpc, types::{ErrorObject, ErrorObjectOwned}, + Extensions, }; use serde::{Deserialize, Serialize}; use sc_consensus_babe::{authorship, BabeWorkerHandle}; use sc_consensus_epochs::Epoch as EpochT; -use sc_rpc_api::{DenyUnsafe, UnsafeRpcError}; +use sc_rpc_api::{check_if_safe, UnsafeRpcError}; use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppCrypto; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; @@ -47,7 +48,7 @@ const BABE_ERROR: i32 = 9000; pub trait BabeApi { /// Returns data about which slots (primary or secondary) can be claimed in the current epoch /// with the keys in the keystore. - #[method(name = "babe_epochAuthorship")] + #[method(name = "babe_epochAuthorship", with_extensions)] async fn epoch_authorship(&self) -> Result, Error>; } @@ -61,8 +62,6 @@ pub struct Babe { keystore: KeystorePtr, /// The SelectChain strategy select_chain: SC, - /// Whether to deny unsafe calls - deny_unsafe: DenyUnsafe, } impl Babe { @@ -72,9 +71,8 @@ impl Babe { babe_worker_handle: BabeWorkerHandle, keystore: KeystorePtr, select_chain: SC, - deny_unsafe: DenyUnsafe, ) -> Self { - Self { client, babe_worker_handle, keystore, select_chain, deny_unsafe } + Self { client, babe_worker_handle, keystore, select_chain } } } @@ -89,8 +87,11 @@ where C::Api: BabeRuntimeApi, SC: SelectChain + Clone + 'static, { - async fn epoch_authorship(&self) -> Result, Error> { - self.deny_unsafe.check_if_safe()?; + async fn epoch_authorship( + &self, + ext: &Extensions, + ) -> Result, Error> { + check_if_safe(ext)?; let best_header = self.select_chain.best_chain().map_err(Error::SelectChain).await?; @@ -193,6 +194,7 @@ impl From for ErrorObjectOwned { mod tests { use super::*; use sc_consensus_babe::ImportQueueParams; + use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::{OffchainTransactionPoolFactory, RejectAllTxPool}; use sp_consensus_babe::inherents::InherentDataProvider; use sp_core::{crypto::key_types::BABE, testing::TaskExecutor}; @@ -211,9 +213,8 @@ mod tests { keystore.into() } - fn test_babe_rpc_module( - deny_unsafe: DenyUnsafe, - ) -> Babe> { + fn test_babe_rpc_module() -> Babe> + { let builder = TestClientBuilder::new(); let (client, longest_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); @@ -248,29 +249,31 @@ mod tests { }) .unwrap(); - Babe::new(client.clone(), babe_worker_handle, keystore, longest_chain, deny_unsafe) + Babe::new(client.clone(), babe_worker_handle, keystore, longest_chain) } #[tokio::test] async fn epoch_authorship_works() { - let babe_rpc = test_babe_rpc_module(DenyUnsafe::No); - let api = babe_rpc.into_rpc(); + let babe_rpc = test_babe_rpc_module(); + let mut api = babe_rpc.into_rpc(); + api.extensions_mut().insert(DenyUnsafe::No); - let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#; + let request = r#"{"jsonrpc":"2.0","id":1,"method":"babe_epochAuthorship","params":[]}"#; let (response, _) = api.raw_json_request(request, 1).await.unwrap(); - let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[],"secondary_vrf":[1,2,4]}},"id":1}"#; + let expected = r#"{"jsonrpc":"2.0","id":1,"result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[],"secondary_vrf":[1,2,4]}}}"#; assert_eq!(response, expected); } #[tokio::test] async fn epoch_authorship_is_unsafe() { - let babe_rpc = test_babe_rpc_module(DenyUnsafe::Yes); - let api = babe_rpc.into_rpc(); + let babe_rpc = test_babe_rpc_module(); + let mut api = babe_rpc.into_rpc(); + api.extensions_mut().insert(DenyUnsafe::Yes); let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params":[],"id":1}"#; let (response, _) = api.raw_json_request(request, 1).await.unwrap(); - let expected = r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"#; + let expected = r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"RPC call is unsafe to be called externally"}}"#; assert_eq!(response, expected); } diff --git a/substrate/client/consensus/beefy/rpc/src/lib.rs b/substrate/client/consensus/beefy/rpc/src/lib.rs index 83477d223dd2..ab58f6866275 100644 --- a/substrate/client/consensus/beefy/rpc/src/lib.rs +++ b/substrate/client/consensus/beefy/rpc/src/lib.rs @@ -201,7 +201,7 @@ mod tests { async fn uninitialized_rpc_handler() { let (rpc, _) = setup_io_handler(); let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#; - let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#; + let expected_response = r#"{"jsonrpc":"2.0","id":1,"error":{"code":1,"message":"BEEFY RPC endpoint not ready"}}"#; let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); assert_eq!(expected_response, response); @@ -220,13 +220,13 @@ mod tests { let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#; let expected = "{\ \"jsonrpc\":\"2.0\",\ - \"result\":\"0x2f0039e93a27221fcf657fb877a1d4f60307106113e885096cb44a461cd0afbf\",\ - \"id\":1\ + \"id\":1,\ + \"result\":\"0x2f0039e93a27221fcf657fb877a1d4f60307106113e885096cb44a461cd0afbf\"\ }"; - let not_ready = "{\ + let not_ready: &str = "{\ \"jsonrpc\":\"2.0\",\ - \"error\":{\"code\":1,\"message\":\"BEEFY RPC endpoint not ready\"},\ - \"id\":1\ + \"id\":1,\ + \"error\":{\"code\":1,\"message\":\"BEEFY RPC endpoint not ready\"}\ }"; let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); @@ -262,7 +262,7 @@ mod tests { ) .await .unwrap(); - let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; + let expected = r#"{"jsonrpc":"2.0","id":1,"result":false}"#; assert_eq!(response, expected); } diff --git a/substrate/client/consensus/grandpa/rpc/src/lib.rs b/substrate/client/consensus/grandpa/rpc/src/lib.rs index a41b14299089..99f98b81261a 100644 --- a/substrate/client/consensus/grandpa/rpc/src/lib.rs +++ b/substrate/client/consensus/grandpa/rpc/src/lib.rs @@ -275,7 +275,7 @@ mod tests { #[tokio::test] async fn uninitialized_rpc_handler() { let (rpc, _) = setup_io_handler(EmptyVoterState); - let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"GRANDPA RPC endpoint not ready"},"id":0}"#.to_string(); + let expected_response = r#"{"jsonrpc":"2.0","id":0,"error":{"code":1,"message":"GRANDPA RPC endpoint not ready"}}"#.to_string(); let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); @@ -285,7 +285,7 @@ mod tests { #[tokio::test] async fn working_rpc_handler() { let (rpc, _) = setup_io_handler(TestVoterState); - let expected_response = "{\"jsonrpc\":\"2.0\",\"result\":{\ + let expected_response = "{\"jsonrpc\":\"2.0\",\"id\":0,\"result\":{\ \"setId\":1,\ \"best\":{\ \"round\":2,\"totalWeight\":100,\"thresholdWeight\":67,\ @@ -297,7 +297,7 @@ mod tests { \"prevotes\":{\"currentWeight\":100,\"missing\":[]},\ \"precommits\":{\"currentWeight\":100,\"missing\":[]}\ }]\ - },\"id\":0}".to_string(); + }}".to_string(); let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); @@ -321,7 +321,7 @@ mod tests { ) .await .unwrap(); - let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; + let expected = r#"{"jsonrpc":"2.0","id":1,"result":false}"#; assert_eq!(response, expected); } diff --git a/substrate/client/consensus/slots/src/lib.rs b/substrate/client/consensus/slots/src/lib.rs index 7cdf90877dff..06e0756fc968 100644 --- a/substrate/client/consensus/slots/src/lib.rs +++ b/substrate/client/consensus/slots/src/lib.rs @@ -517,16 +517,15 @@ pub async fn start_slot_worker( CIDP: CreateInherentDataProviders + Send + 'static, CIDP::InherentDataProviders: InherentDataProviderExt + Send, { - let mut slots = Slots::new(slot_duration.as_duration(), create_inherent_data_providers, client); + let mut slots = Slots::new( + slot_duration.as_duration(), + create_inherent_data_providers, + client, + sync_oracle, + ); loop { let slot_info = slots.next_slot().await; - - if sync_oracle.is_major_syncing() { - debug!(target: LOG_TARGET, "Skipping proposal slot due to sync."); - continue - } - let _ = worker.on_slot(slot_info).await; } } diff --git a/substrate/client/consensus/slots/src/slots.rs b/substrate/client/consensus/slots/src/slots.rs index 203764310601..c0b412e8ad5b 100644 --- a/substrate/client/consensus/slots/src/slots.rs +++ b/substrate/client/consensus/slots/src/slots.rs @@ -21,7 +21,7 @@ //! This is used instead of `futures_timer::Interval` because it was unreliable. use super::{InherentDataProviderExt, Slot, LOG_TARGET}; -use sp_consensus::SelectChain; +use sp_consensus::{SelectChain, SyncOracle}; use sp_inherents::{CreateInherentDataProviders, InherentDataProvider}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; @@ -87,21 +87,23 @@ impl SlotInfo { } /// A stream that returns every time there is a new slot. -pub(crate) struct Slots { +pub(crate) struct Slots { last_slot: Slot, slot_duration: Duration, until_next_slot: Option, create_inherent_data_providers: IDP, select_chain: SC, + sync_oracle: SO, _phantom: std::marker::PhantomData, } -impl Slots { +impl Slots { /// Create a new `Slots` stream. pub fn new( slot_duration: Duration, create_inherent_data_providers: IDP, select_chain: SC, + sync_oracle: SO, ) -> Self { Slots { last_slot: 0.into(), @@ -109,17 +111,19 @@ impl Slots { until_next_slot: None, create_inherent_data_providers, select_chain, + sync_oracle, _phantom: Default::default(), } } } -impl Slots +impl Slots where Block: BlockT, SC: SelectChain, IDP: CreateInherentDataProviders + 'static, IDP::InherentDataProviders: crate::InherentDataProviderExt, + SO: SyncOracle, { /// Returns a future that fires when the next slot starts. pub async fn next_slot(&mut self) -> SlotInfo { @@ -138,6 +142,11 @@ where let wait_dur = time_until_next_slot(self.slot_duration); self.until_next_slot = Some(Delay::new(wait_dur)); + if self.sync_oracle.is_major_syncing() { + log::debug!(target: LOG_TARGET, "Skipping slot: major sync is in progress."); + continue; + } + let chain_head = match self.select_chain.best_chain().await { Ok(x) => x, Err(e) => { diff --git a/substrate/client/informant/src/display.rs b/substrate/client/informant/src/display.rs index f5e042103ea7..2decd7674782 100644 --- a/substrate/client/informant/src/display.rs +++ b/substrate/client/informant/src/display.rs @@ -65,11 +65,11 @@ impl InformantDisplay { info: &ClientInfo, net_status: NetworkStatus, sync_status: SyncStatus, + num_connected_peers: usize, ) { let best_number = info.chain.best_number; let best_hash = info.chain.best_hash; let finalized_number = info.chain.finalized_number; - let num_connected_peers = sync_status.num_connected_peers; let speed = speed::(best_number, self.last_number, self.last_update); let total_bytes_inbound = net_status.total_bytes_inbound; let total_bytes_outbound = net_status.total_bytes_outbound; diff --git a/substrate/client/informant/src/lib.rs b/substrate/client/informant/src/lib.rs index d44364539a29..0b0e13dc08bb 100644 --- a/substrate/client/informant/src/lib.rs +++ b/substrate/client/informant/src/lib.rs @@ -24,7 +24,7 @@ use futures_timer::Delay; use log::{debug, info, trace}; use sc_client_api::{BlockchainEvents, UsageProvider}; use sc_network::NetworkStatusProvider; -use sc_network_sync::SyncStatusProvider; +use sc_network_sync::{SyncStatusProvider, SyncingService}; use sp_blockchain::HeaderMetadata; use sp_runtime::traits::{Block as BlockT, Header}; use std::{collections::VecDeque, fmt::Display, sync::Arc, time::Duration}; @@ -37,10 +37,9 @@ fn interval(duration: Duration) -> impl Stream + Unpin { } /// Builds the informant and returns a `Future` that drives the informant. -pub async fn build(client: Arc, network: N, syncing: S) +pub async fn build(client: Arc, network: N, syncing: Arc>) where N: NetworkStatusProvider, - S: SyncStatusProvider, C: UsageProvider + HeaderMetadata + BlockchainEvents, >::Error: Display, { @@ -52,13 +51,14 @@ where .filter_map(|_| async { let net_status = network.status().await; let sync_status = syncing.status().await; + let num_connected_peers = syncing.num_connected_peers(); - match (net_status.ok(), sync_status.ok()) { - (Some(net), Some(sync)) => Some((net, sync)), + match (net_status, sync_status) { + (Ok(net), Ok(sync)) => Some((net, sync, num_connected_peers)), _ => None, } }) - .for_each(move |(net_status, sync_status)| { + .for_each(move |(net_status, sync_status, num_connected_peers)| { let info = client_1.usage_info(); if let Some(ref usage) = info.usage { trace!(target: "usage", "Usage statistics: {}", usage); @@ -68,7 +68,7 @@ where "Usage statistics not displayed as backend does not provide it", ) } - display.display(&info, net_status, sync_status); + display.display(&info, net_status, sync_status, num_connected_peers); future::ready(()) }); diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index a25db4f789dd..4a57d61df457 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -471,15 +471,6 @@ where )) } - /// Report Prometheus metrics. - pub fn report_metrics(&self) { - if let Some(metrics) = &self.metrics { - let n = u64::try_from(self.peers.len()).unwrap_or(std::u64::MAX); - metrics.peers.set(n); - } - self.strategy.report_metrics(); - } - fn update_peer_info( &mut self, peer_id: &PeerId, @@ -606,7 +597,11 @@ where pub async fn run(mut self) { loop { tokio::select! { - _ = self.tick_timeout.tick() => self.perform_periodic_actions(), + _ = self.tick_timeout.tick() => { + // TODO: This tick should not be necessary, but + // `self.process_strategy_actions()` is not called in some cases otherwise and + // some tests fail because of this + }, command = self.service_rx.select_next_some() => self.process_service_command(command), notification_event = self.notification_service.next_event() => match notification_event { @@ -620,7 +615,6 @@ where } // Update atomic variables - self.num_connected.store(self.peers.len(), Ordering::Relaxed); self.is_major_syncing.store(self.strategy.is_major_syncing(), Ordering::Relaxed); // Process actions requested by a syncing strategy. @@ -724,10 +718,6 @@ where Ok(()) } - fn perform_periodic_actions(&mut self) { - self.report_metrics(); - } - fn process_service_command(&mut self, command: ToServiceCommand) { match command { ToServiceCommand::SetSyncForkRequest(peers, hash, number) => { @@ -770,25 +760,11 @@ where ); }, ToServiceCommand::Status(tx) => { - let mut status = self.strategy.status(); - status.num_connected_peers = self.peers.len() as u32; - let _ = tx.send(status); + let _ = tx.send(self.strategy.status()); }, ToServiceCommand::NumActivePeers(tx) => { let _ = tx.send(self.num_active_peers()); }, - ToServiceCommand::SyncState(tx) => { - let _ = tx.send(self.strategy.status()); - }, - ToServiceCommand::BestSeenBlock(tx) => { - let _ = tx.send(self.strategy.status().best_seen_block); - }, - ToServiceCommand::NumSyncPeers(tx) => { - let _ = tx.send(self.strategy.status().num_peers); - }, - ToServiceCommand::NumQueuedBlocks(tx) => { - let _ = tx.send(self.strategy.status().queued_blocks); - }, ToServiceCommand::NumDownloadedBlocks(tx) => { let _ = tx.send(self.strategy.num_downloaded_blocks()); }, @@ -796,11 +772,8 @@ where let _ = tx.send(self.strategy.num_sync_requests()); }, ToServiceCommand::PeersInfo(tx) => { - let peers_info = self - .peers - .iter() - .map(|(peer_id, peer)| (*peer_id, peer.info.clone())) - .collect(); + let peers_info = + self.peers.iter().map(|(peer_id, peer)| (*peer_id, peer.info)).collect(); let _ = tx.send(peers_info); }, ToServiceCommand::OnBlockFinalized(hash, header) => @@ -873,6 +846,10 @@ where log::debug!(target: LOG_TARGET, "{peer_id} does not exist in `SyncingEngine`"); return }; + if let Some(metrics) = &self.metrics { + metrics.peers.dec(); + } + self.num_connected.fetch_sub(1, Ordering::AcqRel); if self.important_peers.contains(&peer_id) { log::warn!(target: LOG_TARGET, "Reserved peer {peer_id} disconnected"); @@ -1048,7 +1025,12 @@ where log::debug!(target: LOG_TARGET, "Connected {peer_id}"); - self.peers.insert(peer_id, peer); + if self.peers.insert(peer_id, peer).is_none() { + if let Some(metrics) = &self.metrics { + metrics.peers.inc(); + } + self.num_connected.fetch_add(1, Ordering::AcqRel); + } self.peer_store_handle.set_peer_role(&peer_id, status.roles.into()); if self.default_peers_set_no_slot_peers.contains(&peer_id) { diff --git a/substrate/client/network/sync/src/justification_requests.rs b/substrate/client/network/sync/src/justification_requests.rs index 2b50c85602d7..f2d7488b2c35 100644 --- a/substrate/client/network/sync/src/justification_requests.rs +++ b/substrate/client/network/sync/src/justification_requests.rs @@ -21,12 +21,14 @@ //! that don't make sense after one of the forks is finalized). use crate::{ - request_metrics::Metrics, strategy::chain_sync::{PeerSync, PeerSyncState}, LOG_TARGET, }; use fork_tree::ForkTree; use log::{debug, trace, warn}; +use prometheus_endpoint::{ + prometheus::core::GenericGauge, register, GaugeVec, Opts, PrometheusError, Registry, U64, +}; use sc_network_types::PeerId; use sp_blockchain::Error as ClientError; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; @@ -41,6 +43,34 @@ const EXTRA_RETRY_WAIT: Duration = Duration::from_secs(10); /// Pending extra data request for the given block (hash and number). type ExtraRequest = (::Hash, NumberFor); +#[derive(Debug)] +struct Metrics { + pending: GenericGauge, + active: GenericGauge, + failed: GenericGauge, + importing: GenericGauge, +} + +impl Metrics { + fn register(registry: &Registry) -> Result { + let justifications = GaugeVec::::new( + Opts::new( + "substrate_sync_extra_justifications", + "Number of extra justifications requests", + ), + &["status"], + )?; + let justifications = register(justifications, registry)?; + + Ok(Self { + pending: justifications.with_label_values(&["pending"]), + active: justifications.with_label_values(&["active"]), + failed: justifications.with_label_values(&["failed"]), + importing: justifications.with_label_values(&["importing"]), + }) + } +} + /// Manages pending block extra data (e.g. justification) requests. /// /// Multiple extras may be requested for competing forks, or for the same branch @@ -62,10 +92,14 @@ pub(crate) struct ExtraRequests { importing_requests: HashSet>, /// the name of this type of extra request (useful for logging.) request_type_name: &'static str, + metrics: Option, } impl ExtraRequests { - pub(crate) fn new(request_type_name: &'static str) -> Self { + pub(crate) fn new( + request_type_name: &'static str, + metrics_registry: Option<&Registry>, + ) -> Self { Self { tree: ForkTree::new(), best_seen_finalized_number: Zero::zero(), @@ -74,6 +108,16 @@ impl ExtraRequests { failed_requests: HashMap::new(), importing_requests: HashSet::new(), request_type_name, + metrics: metrics_registry.and_then(|registry| { + Metrics::register(registry) + .inspect_err(|error| { + log::error!( + target: LOG_TARGET, + "Failed to register `ExtraRequests` metrics {error}", + ); + }) + .ok() + }), } } @@ -83,6 +127,12 @@ impl ExtraRequests { self.pending_requests.clear(); self.active_requests.clear(); self.failed_requests.clear(); + + if let Some(metrics) = &self.metrics { + metrics.pending.set(0); + metrics.active.set(0); + metrics.failed.set(0); + } } /// Returns an iterator-like struct that yields peers which extra @@ -100,6 +150,9 @@ impl ExtraRequests { Ok(true) => { // this is a new root so we add it to the current `pending_requests` self.pending_requests.push_back((request.0, request.1)); + if let Some(metrics) = &self.metrics { + metrics.pending.inc(); + } }, Err(fork_tree::Error::Revert) => { // we have finalized further than the given request, presumably @@ -117,6 +170,10 @@ impl ExtraRequests { pub(crate) fn peer_disconnected(&mut self, who: &PeerId) { if let Some(request) = self.active_requests.remove(who) { self.pending_requests.push_front(request); + if let Some(metrics) = &self.metrics { + metrics.active.dec(); + metrics.pending.inc(); + } } } @@ -130,13 +187,21 @@ impl ExtraRequests { // currently enforced by the outer network protocol before passing on // messages to chain sync. if let Some(request) = self.active_requests.remove(&who) { + if let Some(metrics) = &self.metrics { + metrics.active.dec(); + } + if let Some(r) = resp { trace!(target: LOG_TARGET, "Queuing import of {} from {:?} for {:?}", self.request_type_name, who, request, ); - self.importing_requests.insert(request); + if self.importing_requests.insert(request) { + if let Some(metrics) = &self.metrics { + metrics.importing.inc(); + } + } return Some((who, request.0, request.1, r)) } else { trace!(target: LOG_TARGET, @@ -146,6 +211,10 @@ impl ExtraRequests { } self.failed_requests.entry(request).or_default().push((who, Instant::now())); self.pending_requests.push_front(request); + if let Some(metrics) = &self.metrics { + metrics.failed.set(self.failed_requests.len().try_into().unwrap_or(u64::MAX)); + metrics.pending.inc(); + } } else { trace!(target: LOG_TARGET, "No active {} request to {:?}", @@ -194,6 +263,11 @@ impl ExtraRequests { self.pending_requests.retain(|(h, n)| roots.contains(&(h, n, &()))); self.active_requests.retain(|_, (h, n)| roots.contains(&(h, n, &()))); self.failed_requests.retain(|(h, n), _| roots.contains(&(h, n, &()))); + if let Some(metrics) = &self.metrics { + metrics.pending.set(self.pending_requests.len().try_into().unwrap_or(u64::MAX)); + metrics.active.set(self.active_requests.len().try_into().unwrap_or(u64::MAX)); + metrics.failed.set(self.failed_requests.len().try_into().unwrap_or(u64::MAX)); + } Ok(()) } @@ -210,12 +284,18 @@ impl ExtraRequests { if !self.importing_requests.remove(&request) { return false } + if let Some(metrics) = &self.metrics { + metrics.importing.dec(); + } let (finalized_hash, finalized_number) = match result { Ok(req) => (req.0, req.1), Err(_) => { if reschedule_on_failure { self.pending_requests.push_front(request); + if let Some(metrics) = &self.metrics { + metrics.pending.inc(); + } } return true }, @@ -233,6 +313,11 @@ impl ExtraRequests { self.active_requests.clear(); self.pending_requests.clear(); self.pending_requests.extend(self.tree.roots().map(|(&h, &n, _)| (h, n))); + if let Some(metrics) = &self.metrics { + metrics.failed.set(0); + metrics.active.set(0); + metrics.pending.set(self.pending_requests.len().try_into().unwrap_or(u64::MAX)); + } self.best_seen_finalized_number = finalized_number; true @@ -249,16 +334,6 @@ impl ExtraRequests { pub(crate) fn pending_requests(&self) -> impl Iterator> { self.pending_requests.iter() } - - /// Get some key metrics. - pub(crate) fn metrics(&self) -> Metrics { - Metrics { - pending_requests: self.pending_requests.len().try_into().unwrap_or(std::u32::MAX), - active_requests: self.active_requests.len().try_into().unwrap_or(std::u32::MAX), - failed_requests: self.failed_requests.len().try_into().unwrap_or(std::u32::MAX), - importing_requests: self.importing_requests.len().try_into().unwrap_or(std::u32::MAX), - } - } } /// Matches peers with pending extra requests. @@ -301,8 +376,17 @@ impl<'a, B: BlockT> Matcher<'a, B> { for requests in self.extras.failed_requests.values_mut() { requests.retain(|(_, instant)| instant.elapsed() < EXTRA_RETRY_WAIT); } + if let Some(metrics) = &self.extras.metrics { + metrics + .failed + .set(self.extras.failed_requests.len().try_into().unwrap_or(u64::MAX)); + } while let Some(request) = self.extras.pending_requests.pop_front() { + if let Some(metrics) = &self.extras.metrics { + metrics.pending.dec(); + } + for (peer, sync) in peers.iter().filter(|(_, sync)| sync.state == PeerSyncState::Available) { @@ -326,6 +410,9 @@ impl<'a, B: BlockT> Matcher<'a, B> { continue } self.extras.active_requests.insert(*peer, request); + if let Some(metrics) = &self.extras.metrics { + metrics.active.inc(); + } trace!(target: LOG_TARGET, "Sending {} request to {:?} for {:?}", @@ -336,6 +423,9 @@ impl<'a, B: BlockT> Matcher<'a, B> { } self.extras.pending_requests.push_back(request); + if let Some(metrics) = &self.extras.metrics { + metrics.pending.inc(); + } self.remaining -= 1; if self.remaining == 0 { @@ -359,7 +449,7 @@ mod tests { #[test] fn requests_are_processed_in_order() { fn property(mut peers: ArbitraryPeers) { - let mut requests = ExtraRequests::::new("test"); + let mut requests = ExtraRequests::::new("test", None); let num_peers_available = peers.0.values().filter(|s| s.state == PeerSyncState::Available).count(); @@ -385,7 +475,7 @@ mod tests { #[test] fn new_roots_schedule_new_request() { fn property(data: Vec) { - let mut requests = ExtraRequests::::new("test"); + let mut requests = ExtraRequests::::new("test", None); for (i, number) in data.into_iter().enumerate() { let hash = [i as u8; 32].into(); let pending = requests.pending_requests.len(); @@ -402,7 +492,7 @@ mod tests { #[test] fn disconnecting_implies_rescheduling() { fn property(mut peers: ArbitraryPeers) -> bool { - let mut requests = ExtraRequests::::new("test"); + let mut requests = ExtraRequests::::new("test", None); let num_peers_available = peers.0.values().filter(|s| s.state == PeerSyncState::Available).count(); @@ -438,7 +528,7 @@ mod tests { #[test] fn no_response_reschedules() { fn property(mut peers: ArbitraryPeers) { - let mut requests = ExtraRequests::::new("test"); + let mut requests = ExtraRequests::::new("test", None); let num_peers_available = peers.0.values().filter(|s| s.state == PeerSyncState::Available).count(); @@ -480,7 +570,7 @@ mod tests { fn request_is_rescheduled_when_earlier_block_is_finalized() { sp_tracing::try_init_simple(); - let mut finality_proofs = ExtraRequests::::new("test"); + let mut finality_proofs = ExtraRequests::::new("test", None); let hash4 = [4; 32].into(); let hash5 = [5; 32].into(); @@ -521,7 +611,7 @@ mod tests { #[test] fn ancestor_roots_are_finalized_when_finality_notification_is_missed() { - let mut finality_proofs = ExtraRequests::::new("test"); + let mut finality_proofs = ExtraRequests::::new("test", None); let hash4 = [4; 32].into(); let hash5 = [5; 32].into(); diff --git a/substrate/client/network/sync/src/service/syncing_service.rs b/substrate/client/network/sync/src/service/syncing_service.rs index f4bc58afd4fd..08a2b36118a9 100644 --- a/substrate/client/network/sync/src/service/syncing_service.rs +++ b/substrate/client/network/sync/src/service/syncing_service.rs @@ -50,10 +50,6 @@ pub enum ToServiceCommand { EventStream(TracingUnboundedSender), Status(oneshot::Sender>), NumActivePeers(oneshot::Sender), - SyncState(oneshot::Sender>), - BestSeenBlock(oneshot::Sender>>), - NumSyncPeers(oneshot::Sender), - NumQueuedBlocks(oneshot::Sender), NumDownloadedBlocks(oneshot::Sender), NumSyncRequests(oneshot::Sender), PeersInfo(oneshot::Sender)>>), @@ -83,6 +79,11 @@ impl SyncingService { Self { tx, num_connected, is_major_syncing } } + /// Get the number of peers known to `SyncingEngine` (both full and light). + pub fn num_connected_peers(&self) -> usize { + self.num_connected.load(Ordering::Relaxed) + } + /// Get the number of active peers. pub async fn num_active_peers(&self) -> Result { let (tx, rx) = oneshot::channel(); @@ -91,30 +92,6 @@ impl SyncingService { rx.await } - /// Get best seen block. - pub async fn best_seen_block(&self) -> Result>, oneshot::Canceled> { - let (tx, rx) = oneshot::channel(); - let _ = self.tx.unbounded_send(ToServiceCommand::BestSeenBlock(tx)); - - rx.await - } - - /// Get the number of sync peers. - pub async fn num_sync_peers(&self) -> Result { - let (tx, rx) = oneshot::channel(); - let _ = self.tx.unbounded_send(ToServiceCommand::NumSyncPeers(tx)); - - rx.await - } - - /// Get the number of queued blocks. - pub async fn num_queued_blocks(&self) -> Result { - let (tx, rx) = oneshot::channel(); - let _ = self.tx.unbounded_send(ToServiceCommand::NumQueuedBlocks(tx)); - - rx.await - } - /// Get the number of downloaded blocks. pub async fn num_downloaded_blocks(&self) -> Result { let (tx, rx) = oneshot::channel(); @@ -149,11 +126,11 @@ impl SyncingService { /// Get sync status /// /// Returns an error if `SyncingEngine` has terminated. - pub async fn status(&self) -> Result, ()> { + pub async fn status(&self) -> Result, oneshot::Canceled> { let (tx, rx) = oneshot::channel(); let _ = self.tx.unbounded_send(ToServiceCommand::Status(tx)); - rx.await.map_err(|_| ()) + rx.await } } diff --git a/substrate/client/network/sync/src/strategy.rs b/substrate/client/network/sync/src/strategy.rs index 2c9799a9d836..ad3a9461c93b 100644 --- a/substrate/client/network/sync/src/strategy.rs +++ b/substrate/client/network/sync/src/strategy.rs @@ -210,7 +210,7 @@ where client.clone(), config.max_parallel_downloads, config.max_blocks_per_request, - config.metrics_registry.clone(), + config.metrics_registry.as_ref(), std::iter::empty(), )?; Ok(Self { @@ -455,13 +455,6 @@ where self.chain_sync.as_ref().map_or(0, |chain_sync| chain_sync.num_sync_requests()) } - /// Report Prometheus metrics - pub fn report_metrics(&self) { - if let Some(ref chain_sync) = self.chain_sync { - chain_sync.report_metrics(); - } - } - /// Get actions that should be performed by the owner on the strategy's behalf #[must_use] pub fn actions(&mut self) -> Result>, ClientError> { @@ -519,7 +512,7 @@ where self.client.clone(), self.config.max_parallel_downloads, self.config.max_blocks_per_request, - self.config.metrics_registry.clone(), + self.config.metrics_registry.as_ref(), self.peer_best_blocks.iter().map(|(peer_id, (best_hash, best_number))| { (*peer_id, *best_hash, *best_number) }), @@ -547,7 +540,7 @@ where self.client.clone(), self.config.max_parallel_downloads, self.config.max_blocks_per_request, - self.config.metrics_registry.clone(), + self.config.metrics_registry.as_ref(), self.peer_best_blocks.iter().map(|(peer_id, (best_hash, best_number))| { (*peer_id, *best_hash, *best_number) }), diff --git a/substrate/client/network/sync/src/strategy/chain_sync.rs b/substrate/client/network/sync/src/strategy/chain_sync.rs index 52870d5ba151..21e474048625 100644 --- a/substrate/client/network/sync/src/strategy/chain_sync.rs +++ b/substrate/client/network/sync/src/strategy/chain_sync.rs @@ -43,7 +43,7 @@ use crate::{ use codec::Encode; use log::{debug, error, info, trace, warn}; -use prometheus_endpoint::{register, Gauge, GaugeVec, Opts, PrometheusError, Registry, U64}; +use prometheus_endpoint::{register, Gauge, PrometheusError, Registry, U64}; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; use sc_network_common::sync::message::{ @@ -128,7 +128,6 @@ mod rep { struct Metrics { queued_blocks: Gauge, fork_targets: Gauge, - justifications: GaugeVec, } impl Metrics { @@ -143,16 +142,6 @@ impl Metrics { let g = Gauge::new("substrate_sync_fork_targets", "Number of fork sync targets")?; register(g, r)? }, - justifications: { - let g = GaugeVec::new( - Opts::new( - "substrate_sync_extra_justifications", - "Number of extra justifications requests", - ), - &["status"], - )?; - register(g, r)? - }, }) } } @@ -374,7 +363,7 @@ where client: Arc, max_parallel_downloads: u32, max_blocks_per_request: u32, - metrics_registry: Option, + metrics_registry: Option<&Registry>, initial_peers: impl Iterator)>, ) -> Result { let mut sync = Self { @@ -384,7 +373,7 @@ where blocks: BlockCollection::new(), best_queued_hash: Default::default(), best_queued_number: Zero::zero(), - extra_justifications: ExtraRequests::new("justification"), + extra_justifications: ExtraRequests::new("justification", metrics_registry), mode, queue_blocks: Default::default(), fork_targets: Default::default(), @@ -396,7 +385,7 @@ where import_existing: false, gap_sync: None, actions: Vec::new(), - metrics: metrics_registry.and_then(|r| match Metrics::register(&r) { + metrics: metrics_registry.and_then(|r| match Metrics::register(r) { Ok(metrics) => Some(metrics), Err(err) => { log::error!( @@ -449,7 +438,6 @@ where state: sync_state, best_seen_block, num_peers: self.peers.len() as u32, - num_connected_peers: 0u32, queued_blocks: self.queue_blocks.len() as u32, state_sync: self.state_sync.as_ref().map(|s| s.progress()), warp_sync: warp_sync_progress, @@ -676,7 +664,13 @@ where self.fork_targets .entry(*hash) - .or_insert_with(|| ForkTarget { number, peers: Default::default(), parent_hash: None }) + .or_insert_with(|| { + if let Some(metrics) = &self.metrics { + metrics.fork_targets.inc(); + } + + ForkTarget { number, peers: Default::default(), parent_hash: None } + }) .peers .extend(peers); } @@ -883,10 +877,16 @@ where ); self.fork_targets .entry(peer.best_hash) - .or_insert_with(|| ForkTarget { - number: peer.best_number, - parent_hash: None, - peers: Default::default(), + .or_insert_with(|| { + if let Some(metrics) = &self.metrics { + metrics.fork_targets.inc(); + } + + ForkTarget { + number: peer.best_number, + parent_hash: None, + peers: Default::default(), + } }) .peers .insert(*peer_id); @@ -1126,10 +1126,16 @@ where ); self.fork_targets .entry(hash) - .or_insert_with(|| ForkTarget { - number, - parent_hash: Some(*announce.header.parent_hash()), - peers: Default::default(), + .or_insert_with(|| { + if let Some(metrics) = &self.metrics { + metrics.fork_targets.inc(); + } + + ForkTarget { + number, + parent_hash: Some(*announce.header.parent_hash()), + peers: Default::default(), + } }) .peers .insert(peer_id); @@ -1161,6 +1167,9 @@ where target.peers.remove(peer_id); !target.peers.is_empty() }); + if let Some(metrics) = &self.metrics { + metrics.fork_targets.set(self.fork_targets.len().try_into().unwrap_or(u64::MAX)); + } let blocks = self.ready_blocks(); @@ -1169,36 +1178,6 @@ where } } - /// Report prometheus metrics. - pub fn report_metrics(&self) { - if let Some(metrics) = &self.metrics { - metrics - .fork_targets - .set(self.fork_targets.len().try_into().unwrap_or(std::u64::MAX)); - metrics - .queued_blocks - .set(self.queue_blocks.len().try_into().unwrap_or(std::u64::MAX)); - - let justifications_metrics = self.extra_justifications.metrics(); - metrics - .justifications - .with_label_values(&["pending"]) - .set(justifications_metrics.pending_requests.into()); - metrics - .justifications - .with_label_values(&["active"]) - .set(justifications_metrics.active_requests.into()); - metrics - .justifications - .with_label_values(&["failed"]) - .set(justifications_metrics.failed_requests.into()); - metrics - .justifications - .with_label_values(&["importing"]) - .set(justifications_metrics.importing_requests.into()); - } - } - /// Returns the median seen block number. fn median_seen(&self) -> Option> { let mut best_seens = self.peers.values().map(|p| p.best_number).collect::>(); @@ -1264,6 +1243,11 @@ where self.on_block_queued(h, n) } self.queue_blocks.extend(new_blocks.iter().map(|b| b.hash)); + if let Some(metrics) = &self.metrics { + metrics + .queued_blocks + .set(self.queue_blocks.len().try_into().unwrap_or(u64::MAX)); + } self.actions.push(ChainSyncAction::ImportBlocks { origin, blocks: new_blocks }) } @@ -1280,6 +1264,9 @@ where /// through all peers to update our view of their state as well. fn on_block_queued(&mut self, hash: &B::Hash, number: NumberFor) { if self.fork_targets.remove(hash).is_some() { + if let Some(metrics) = &self.metrics { + metrics.fork_targets.dec(); + } trace!(target: LOG_TARGET, "Completed fork sync {hash:?}"); } if let Some(gap_sync) = &mut self.gap_sync { @@ -1549,12 +1536,13 @@ where std::cmp::min(self.best_queued_number, self.client.info().finalized_number); let best_queued = self.best_queued_number; let client = &self.client; - let queue = &self.queue_blocks; + let queue_blocks = &self.queue_blocks; let allowed_requests = self.allowed_requests.take(); let max_parallel = if is_major_syncing { 1 } else { self.max_parallel_downloads }; let max_blocks_per_request = self.max_blocks_per_request; let gap_sync = &mut self.gap_sync; let disconnected_peers = &mut self.disconnected_peers; + let metrics = self.metrics.as_ref(); self.peers .iter_mut() .filter_map(move |(&id, peer)| { @@ -1574,7 +1562,7 @@ where MAX_BLOCKS_TO_LOOK_BACKWARDS.into() && best_queued < peer.best_number && peer.common_number < last_finalized && - queue.len() <= MAJOR_SYNC_BLOCKS.into() + queue_blocks.len() <= MAJOR_SYNC_BLOCKS.into() { trace!( target: LOG_TARGET, @@ -1617,13 +1605,14 @@ where last_finalized, attrs, |hash| { - if queue.contains(hash) { + if queue_blocks.contains(hash) { BlockStatus::Queued } else { client.block_status(*hash).unwrap_or(BlockStatus::Unknown) } }, max_blocks_per_request, + metrics, ) { trace!(target: LOG_TARGET, "Downloading fork {hash:?} from {id}"); peer.state = PeerSyncState::DownloadingStale(hash); @@ -1764,7 +1753,11 @@ where let mut has_error = false; for (_, hash) in &results { - self.queue_blocks.remove(hash); + if self.queue_blocks.remove(hash) { + if let Some(metrics) = &self.metrics { + metrics.queued_blocks.dec(); + } + } self.blocks.clear_queued(hash); if let Some(gap_sync) = &mut self.gap_sync { gap_sync.blocks.clear_queued(hash); @@ -2094,14 +2087,15 @@ fn peer_gap_block_request( /// Get pending fork sync targets for a peer. fn fork_sync_request( id: &PeerId, - targets: &mut HashMap>, + fork_targets: &mut HashMap>, best_num: NumberFor, finalized: NumberFor, attributes: BlockAttributes, check_block: impl Fn(&B::Hash) -> BlockStatus, max_blocks_per_request: u32, + metrics: Option<&Metrics>, ) -> Option<(B::Hash, BlockRequest)> { - targets.retain(|hash, r| { + fork_targets.retain(|hash, r| { if r.number <= finalized { trace!( target: LOG_TARGET, @@ -2122,7 +2116,10 @@ fn fork_sync_request( } true }); - for (hash, r) in targets { + if let Some(metrics) = metrics { + metrics.fork_targets.set(fork_targets.len().try_into().unwrap_or(u64::MAX)); + } + for (hash, r) in fork_targets { if !r.peers.contains(&id) { continue } diff --git a/substrate/client/network/sync/src/strategy/state.rs b/substrate/client/network/sync/src/strategy/state.rs index ff229863a68b..6f06f238fe3a 100644 --- a/substrate/client/network/sync/src/strategy/state.rs +++ b/substrate/client/network/sync/src/strategy/state.rs @@ -340,7 +340,6 @@ impl StateStrategy { }, best_seen_block: Some(self.state_sync.target_number()), num_peers: self.peers.len().saturated_into(), - num_connected_peers: self.peers.len().saturated_into(), queued_blocks: 0, state_sync: Some(self.state_sync.progress()), warp_sync: None, diff --git a/substrate/client/network/sync/src/strategy/warp.rs b/substrate/client/network/sync/src/strategy/warp.rs index 4cc3e9f3a68b..99405c2e5f08 100644 --- a/substrate/client/network/sync/src/strategy/warp.rs +++ b/substrate/client/network/sync/src/strategy/warp.rs @@ -576,7 +576,6 @@ where Phase::Complete => None, }, num_peers: self.peers.len().saturated_into(), - num_connected_peers: self.peers.len().saturated_into(), queued_blocks: 0, state_sync: None, warp_sync: Some(self.progress()), diff --git a/substrate/client/network/sync/src/types.rs b/substrate/client/network/sync/src/types.rs index e8b8c8900360..c3403fe1e5f7 100644 --- a/substrate/client/network/sync/src/types.rs +++ b/substrate/client/network/sync/src/types.rs @@ -39,7 +39,7 @@ pub struct PeerInfo { } /// Info about a peer's known state (both full and light). -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct ExtendedPeerInfo { /// Roles pub roles: Roles, @@ -49,6 +49,17 @@ pub struct ExtendedPeerInfo { pub best_number: NumberFor, } +impl Clone for ExtendedPeerInfo +where + B: BlockT, +{ + fn clone(&self) -> Self { + Self { roles: self.roles, best_hash: self.best_hash, best_number: self.best_number } + } +} + +impl Copy for ExtendedPeerInfo where B: BlockT {} + /// Reported sync state. #[derive(Clone, Eq, PartialEq, Debug)] pub enum SyncState { @@ -76,8 +87,6 @@ pub struct SyncStatus { pub best_seen_block: Option>, /// Number of peers participating in syncing. pub num_peers: u32, - /// Number of peers known to `SyncingEngine` (both full and light). - pub num_connected_peers: u32, /// Number of blocks queued for import pub queued_blocks: u32, /// State sync status in progress, if any. diff --git a/substrate/client/network/test/src/lib.rs b/substrate/client/network/test/src/lib.rs index b5aeb162e9fa..f84f353fb4a0 100644 --- a/substrate/client/network/test/src/lib.rs +++ b/substrate/client/network/test/src/lib.rs @@ -266,7 +266,7 @@ where /// Returns the number of peers we're connected to. pub async fn num_peers(&self) -> usize { - self.sync_service.status().await.unwrap().num_connected_peers as usize + self.sync_service.num_connected_peers() } /// Returns the number of downloaded blocks. @@ -1016,7 +1016,7 @@ pub trait TestNetFactory: Default + Sized + Send { for peer in peers { if peer.sync_service.is_major_syncing() || - peer.sync_service.num_queued_blocks().await.unwrap() != 0 + peer.sync_service.status().await.unwrap().queued_blocks != 0 { return false } @@ -1036,7 +1036,7 @@ pub trait TestNetFactory: Default + Sized + Send { async fn is_idle(&mut self) -> bool { let peers = self.peers_mut(); for peer in peers { - if peer.sync_service.num_queued_blocks().await.unwrap() != 0 { + if peer.sync_service.status().await.unwrap().queued_blocks != 0 { return false } if peer.sync_service.num_sync_requests().await.unwrap() != 0 { @@ -1094,9 +1094,7 @@ pub trait TestNetFactory: Default + Sized + Send { 'outer: loop { for sync_service in &sync_services { - if sync_service.status().await.unwrap().num_connected_peers as usize != - num_peers - 1 - { + if sync_service.num_connected_peers() != num_peers - 1 { futures::future::poll_fn::<(), _>(|cx| { self.poll(cx); Poll::Ready(()) diff --git a/substrate/client/network/test/src/sync.rs b/substrate/client/network/test/src/sync.rs index 9edd68c2ed9f..4244c49bf7fb 100644 --- a/substrate/client/network/test/src/sync.rs +++ b/substrate/client/network/test/src/sync.rs @@ -1049,7 +1049,7 @@ async fn syncs_all_forks_from_single_peer() { }) .await; - if net.peer(1).sync_service().best_seen_block().await.unwrap() == Some(12) { + if net.peer(1).sync_service().status().await.unwrap().best_seen_block == Some(12) { break } } diff --git a/substrate/client/network/transactions/src/lib.rs b/substrate/client/network/transactions/src/lib.rs index 31ad0781035e..a241041968fd 100644 --- a/substrate/client/network/transactions/src/lib.rs +++ b/substrate/client/network/transactions/src/lib.rs @@ -522,8 +522,14 @@ where return } - debug!(target: LOG_TARGET, "Propagating transactions"); let transactions = self.transaction_pool.transactions(); + + if transactions.is_empty() { + return + } + + debug!(target: LOG_TARGET, "Propagating transactions"); + let propagated_to = self.do_propagate_transactions(&transactions); self.transaction_pool.on_broadcasted(propagated_to); } diff --git a/substrate/client/rpc-api/src/author/mod.rs b/substrate/client/rpc-api/src/author/mod.rs index cfc56f4130ab..c39d6c68355a 100644 --- a/substrate/client/rpc-api/src/author/mod.rs +++ b/substrate/client/rpc-api/src/author/mod.rs @@ -34,11 +34,11 @@ pub trait AuthorApi { async fn submit_extrinsic(&self, extrinsic: Bytes) -> Result; /// Insert a key into the keystore. - #[method(name = "author_insertKey")] + #[method(name = "author_insertKey", with_extensions)] fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> Result<(), Error>; /// Generate new session keys and returns the corresponding public keys. - #[method(name = "author_rotateKeys")] + #[method(name = "author_rotateKeys", with_extensions)] fn rotate_keys(&self) -> Result; /// Checks if the keystore has private keys for the given session public keys. @@ -46,13 +46,13 @@ pub trait AuthorApi { /// `session_keys` is the SCALE encoded session keys object from the runtime. /// /// Returns `true` iff all private keys could be found. - #[method(name = "author_hasSessionKeys")] + #[method(name = "author_hasSessionKeys", with_extensions)] fn has_session_keys(&self, session_keys: Bytes) -> Result; /// Checks if the keystore has private keys for the given public key and key type. /// /// Returns `true` if a private key could be found. - #[method(name = "author_hasKey")] + #[method(name = "author_hasKey", with_extensions)] fn has_key(&self, public_key: Bytes, key_type: String) -> Result; /// Returns all pending extrinsics, potentially grouped by sender. @@ -60,7 +60,7 @@ pub trait AuthorApi { fn pending_extrinsics(&self) -> Result, Error>; /// Remove given extrinsic from the pool and temporarily ban it to prevent reimporting. - #[method(name = "author_removeExtrinsic")] + #[method(name = "author_removeExtrinsic", with_extensions)] fn remove_extrinsic( &self, bytes_or_hash: Vec>, diff --git a/substrate/client/rpc-api/src/dev/mod.rs b/substrate/client/rpc-api/src/dev/mod.rs index 5bee6df73ba9..5c6208ff5d50 100644 --- a/substrate/client/rpc-api/src/dev/mod.rs +++ b/substrate/client/rpc-api/src/dev/mod.rs @@ -59,6 +59,6 @@ pub trait DevApi { /// This function requires the specified block and its parent to be available /// at the queried node. If either the specified block or the parent is pruned, /// this function will return `None`. - #[method(name = "dev_getBlockStats")] + #[method(name = "dev_getBlockStats", with_extensions)] fn block_stats(&self, block_hash: Hash) -> Result, Error>; } diff --git a/substrate/client/rpc-api/src/lib.rs b/substrate/client/rpc-api/src/lib.rs index 451ebdf7fc00..cc580fc0e7ca 100644 --- a/substrate/client/rpc-api/src/lib.rs +++ b/substrate/client/rpc-api/src/lib.rs @@ -25,7 +25,7 @@ mod error; mod policy; -pub use policy::{DenyUnsafe, UnsafeRpcError}; +pub use policy::{check_if_safe, DenyUnsafe, UnsafeRpcError}; pub mod author; pub mod chain; diff --git a/substrate/client/rpc-api/src/offchain/mod.rs b/substrate/client/rpc-api/src/offchain/mod.rs index 469e22d2b3fa..4dd5b066d49f 100644 --- a/substrate/client/rpc-api/src/offchain/mod.rs +++ b/substrate/client/rpc-api/src/offchain/mod.rs @@ -28,10 +28,10 @@ use sp_core::{offchain::StorageKind, Bytes}; #[rpc(client, server)] pub trait OffchainApi { /// Set offchain local storage under given key and prefix. - #[method(name = "offchain_localStorageSet")] + #[method(name = "offchain_localStorageSet", with_extensions)] fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<(), Error>; /// Get offchain local storage under given key and prefix. - #[method(name = "offchain_localStorageGet")] + #[method(name = "offchain_localStorageGet", with_extensions)] fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result, Error>; } diff --git a/substrate/client/rpc-api/src/policy.rs b/substrate/client/rpc-api/src/policy.rs index c0847de89d2c..7e0657db8d17 100644 --- a/substrate/client/rpc-api/src/policy.rs +++ b/substrate/client/rpc-api/src/policy.rs @@ -23,6 +23,15 @@ use jsonrpsee::types::{error::ErrorCode, ErrorObject, ErrorObjectOwned}; +/// Checks if the RPC call is safe to be called externally. +pub fn check_if_safe(ext: &jsonrpsee::Extensions) -> Result<(), UnsafeRpcError> { + match ext.get::().map(|deny_unsafe| deny_unsafe.check_if_safe()) { + Some(Ok(())) => Ok(()), + Some(Err(e)) => Err(e), + None => unreachable!("DenyUnsafe extension is always set by the substrate rpc server; qed"), + } +} + /// Signifies whether a potentially unsafe RPC should be denied. #[derive(Clone, Copy, Debug)] pub enum DenyUnsafe { diff --git a/substrate/client/rpc-api/src/state/mod.rs b/substrate/client/rpc-api/src/state/mod.rs index e38e383c4c15..ee62387b6bd4 100644 --- a/substrate/client/rpc-api/src/state/mod.rs +++ b/substrate/client/rpc-api/src/state/mod.rs @@ -48,7 +48,7 @@ pub trait StateApi { ) -> Result, Error>; /// Returns the keys with prefix, leave empty to get all the keys - #[method(name = "state_getPairs", blocking)] + #[method(name = "state_getPairs", blocking, with_extensions)] fn storage_pairs( &self, prefix: StorageKey, @@ -76,7 +76,7 @@ pub trait StateApi { fn storage_hash(&self, key: StorageKey, hash: Option) -> Result, Error>; /// Returns the size of a storage entry at a block's state. - #[method(name = "state_getStorageSize", aliases = ["state_getStorageSizeAt"])] + #[method(name = "state_getStorageSize", aliases = ["state_getStorageSizeAt"], with_extensions)] async fn storage_size(&self, key: StorageKey, hash: Option) -> Result, Error>; @@ -95,7 +95,7 @@ pub trait StateApi { /// Subsequent values in the vector represent changes to the previous state (diffs). /// WARNING: The time complexity of this query is O(|keys|*dist(block, hash)), and the /// memory complexity is O(dist(block, hash)) -- use with caution. - #[method(name = "state_queryStorage", blocking)] + #[method(name = "state_queryStorage", blocking, with_extensions)] fn query_storage( &self, keys: Vec, @@ -136,6 +136,7 @@ pub trait StateApi { name = "state_subscribeStorage" => "state_storage", unsubscribe = "state_unsubscribeStorage", item = StorageChangeSet, + with_extensions, )] fn subscribe_storage(&self, keys: Option>); @@ -291,7 +292,7 @@ pub trait StateApi { /// /// If you are having issues with maximum payload size you can use the flag /// `-ltracing=trace` to get some logging during tracing. - #[method(name = "state_traceBlock", blocking)] + #[method(name = "state_traceBlock", blocking, with_extensions)] fn trace_block( &self, block: Hash, diff --git a/substrate/client/rpc-api/src/statement/mod.rs b/substrate/client/rpc-api/src/statement/mod.rs index 39ec52cbea01..eee58f506e16 100644 --- a/substrate/client/rpc-api/src/statement/mod.rs +++ b/substrate/client/rpc-api/src/statement/mod.rs @@ -27,7 +27,7 @@ pub mod error; #[rpc(client, server)] pub trait StatementApi { /// Return all statements, SCALE-encoded. - #[method(name = "statement_dump")] + #[method(name = "statement_dump", with_extensions)] fn dump(&self) -> RpcResult>; /// Return the data of all known statements which include all topics and have no `DecryptionKey` diff --git a/substrate/client/rpc-api/src/system/mod.rs b/substrate/client/rpc-api/src/system/mod.rs index c38fa8f3d817..2d6d48e1ddb4 100644 --- a/substrate/client/rpc-api/src/system/mod.rs +++ b/substrate/client/rpc-api/src/system/mod.rs @@ -69,7 +69,7 @@ pub trait SystemApi { async fn system_local_listen_addresses(&self) -> Result, Error>; /// Returns currently connected peers - #[method(name = "system_peers")] + #[method(name = "system_peers", with_extensions)] async fn system_peers(&self) -> Result>, Error>; /// Returns current state of the network. @@ -78,7 +78,7 @@ pub trait SystemApi { /// as its format might change at any time. // TODO: the future of this call is uncertain: https://github.com/paritytech/substrate/issues/1890 // https://github.com/paritytech/substrate/issues/5541 - #[method(name = "system_unstable_networkState")] + #[method(name = "system_unstable_networkState", with_extensions)] async fn system_network_state(&self) -> Result; /// Adds a reserved peer. Returns the empty string or an error. The string @@ -86,12 +86,12 @@ pub trait SystemApi { /// /// `/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV` /// is an example of a valid, passing multiaddr with PeerId attached. - #[method(name = "system_addReservedPeer")] + #[method(name = "system_addReservedPeer", with_extensions)] async fn system_add_reserved_peer(&self, peer: String) -> Result<(), Error>; /// Remove a reserved peer. Returns the empty string or an error. The string /// should encode only the PeerId e.g. `QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV`. - #[method(name = "system_removeReservedPeer")] + #[method(name = "system_removeReservedPeer", with_extensions)] async fn system_remove_reserved_peer(&self, peer_id: String) -> Result<(), Error>; /// Returns the list of reserved peers @@ -112,10 +112,10 @@ pub trait SystemApi { /// The syntax is identical to the CLI `=`: /// /// `sync=debug,state=trace` - #[method(name = "system_addLogFilter")] + #[method(name = "system_addLogFilter", with_extensions)] fn system_add_log_filter(&self, directives: String) -> Result<(), Error>; /// Resets the log filter to Substrate defaults - #[method(name = "system_resetLogFilter")] + #[method(name = "system_resetLogFilter", with_extensions)] fn system_reset_log_filter(&self) -> Result<(), Error>; } diff --git a/substrate/client/rpc-servers/Cargo.toml b/substrate/client/rpc-servers/Cargo.toml index 7e3d3bf55b74..b39c3fdda67c 100644 --- a/substrate/client/rpc-servers/Cargo.toml +++ b/substrate/client/rpc-servers/Cargo.toml @@ -16,21 +16,20 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +dyn-clone = { workspace = true } forwarded-header-value = { workspace = true } futures = { workspace = true } governor = { workspace = true } http = { workspace = true } http-body-util = { workspace = true } +hyper = { workspace = true } ip_network = { workspace = true } jsonrpsee = { features = ["server"], workspace = true } log = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +sc-rpc-api = { workspace = true } serde = { workspace = true } serde_json = { workspace = true, default-features = true } tokio = { features = ["parking_lot"], workspace = true, default-features = true } tower = { workspace = true, features = ["util"] } tower-http = { workspace = true, features = ["cors"] } - -# Dependencies outside the polkadot-sdk workspace -# which requires hyper v1 -hyper = "1.3" diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index 0bae16b113df..ca74c2371c25 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -23,246 +23,255 @@ pub mod middleware; pub mod utils; -use std::{error::Error as StdError, net::SocketAddr, num::NonZeroU32, sync::Arc, time::Duration}; +use std::{error::Error as StdError, time::Duration}; use jsonrpsee::{ core::BoxError, - server::{ - serve_with_graceful_shutdown, stop_channel, ws, PingConfig, StopHandle, TowerServiceBuilder, - }, + server::{serve_with_graceful_shutdown, stop_channel, ws, PingConfig, StopHandle}, Methods, RpcModule, }; use middleware::NodeHealthProxyLayer; -use tokio::net::TcpListener; use tower::Service; -use utils::{build_rpc_api, format_cors, get_proxy_ip, host_filtering, try_into_cors}; +use utils::{ + build_rpc_api, deny_unsafe, format_listen_addrs, get_proxy_ip, ListenAddrError, RpcSettings, +}; pub use ip_network::IpNetwork; pub use jsonrpsee::{ - core::{ - id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, - traits::IdProvider, - }, + core::id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, server::{middleware::rpc::RpcServiceBuilder, BatchRequestConfig}, }; pub use middleware::{Metrics, MiddlewareLayer, RpcMetrics}; +pub use utils::{RpcEndpoint, RpcMethods}; const MEGABYTE: u32 = 1024 * 1024; /// Type alias for the JSON-RPC server. pub type Server = jsonrpsee::server::ServerHandle; +/// Trait for providing subscription IDs that can be cloned. +pub trait SubscriptionIdProvider: + jsonrpsee::core::traits::IdProvider + dyn_clone::DynClone +{ +} + +dyn_clone::clone_trait_object!(SubscriptionIdProvider); + /// RPC server configuration. #[derive(Debug)] -pub struct Config<'a, M: Send + Sync + 'static> { - /// Socket addresses. - pub addrs: [SocketAddr; 2], - /// CORS. - pub cors: Option<&'a Vec>, - /// Maximum connections. - pub max_connections: u32, - /// Maximum subscriptions per connection. - pub max_subs_per_conn: u32, - /// Maximum rpc request payload size. - pub max_payload_in_mb: u32, - /// Maximum rpc response payload size. - pub max_payload_out_mb: u32, +pub struct Config { + /// RPC interfaces to start. + pub endpoints: Vec, /// Metrics. pub metrics: Option, - /// Message buffer size - pub message_buffer_capacity: u32, /// RPC API. pub rpc_api: RpcModule, /// Subscription ID provider. - pub id_provider: Option>, + pub id_provider: Option>, /// Tokio runtime handle. pub tokio_handle: tokio::runtime::Handle, - /// Batch request config. - pub batch_config: BatchRequestConfig, - /// Rate limit calls per minute. - pub rate_limit: Option, - /// Disable rate limit for certain ips. - pub rate_limit_whitelisted_ips: Vec, - /// Trust proxy headers for rate limiting. - pub rate_limit_trust_proxy_headers: bool, } #[derive(Debug, Clone)] -struct PerConnection { +struct PerConnection { methods: Methods, stop_handle: StopHandle, metrics: Option, tokio_handle: tokio::runtime::Handle, - service_builder: TowerServiceBuilder, - rate_limit_whitelisted_ips: Arc>, } /// Start RPC server listening on given address. -pub async fn start_server( - config: Config<'_, M>, -) -> Result> +pub async fn start_server(config: Config) -> Result> where M: Send + Sync, { - let Config { - addrs, - batch_config, - cors, - max_payload_in_mb, - max_payload_out_mb, - max_connections, - max_subs_per_conn, - metrics, - message_buffer_capacity, - id_provider, - tokio_handle, - rpc_api, - rate_limit, - rate_limit_whitelisted_ips, - rate_limit_trust_proxy_headers, - } = config; - - let listener = TcpListener::bind(addrs.as_slice()).await?; - let local_addr = listener.local_addr().ok(); - let host_filter = host_filtering(cors.is_some(), local_addr); - - let http_middleware = tower::ServiceBuilder::new() - .option_layer(host_filter) - // Proxy `GET /health, /health/readiness` requests to the internal `system_health` method. - .layer(NodeHealthProxyLayer::default()) - .layer(try_into_cors(cors)?); - - let mut builder = jsonrpsee::server::Server::builder() - .max_request_body_size(max_payload_in_mb.saturating_mul(MEGABYTE)) - .max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE)) - .max_connections(max_connections) - .max_subscriptions_per_connection(max_subs_per_conn) - .enable_ws_ping( - PingConfig::new() - .ping_interval(Duration::from_secs(30)) - .inactive_limit(Duration::from_secs(60)) - .max_failures(3), - ) - .set_http_middleware(http_middleware) - .set_message_buffer_capacity(message_buffer_capacity) - .set_batch_request_config(batch_config) - .custom_tokio_runtime(tokio_handle.clone()); - - if let Some(provider) = id_provider { - builder = builder.set_id_provider(provider); - } else { - builder = builder.set_id_provider(RandomStringIdProvider::new(16)); - }; + let Config { endpoints, metrics, tokio_handle, rpc_api, id_provider } = config; let (stop_handle, server_handle) = stop_channel(); let cfg = PerConnection { methods: build_rpc_api(rpc_api).into(), - service_builder: builder.to_service_builder(), metrics, tokio_handle: tokio_handle.clone(), stop_handle, - rate_limit_whitelisted_ips: Arc::new(rate_limit_whitelisted_ips), }; - tokio_handle.spawn(async move { - loop { - let (sock, remote_addr) = tokio::select! { - res = listener.accept() => { - match res { - Ok(s) => s, - Err(e) => { - log::debug!(target: "rpc", "Failed to accept ipv4 connection: {:?}", e); - continue; + let mut local_addrs = Vec::new(); + + for endpoint in endpoints { + let allowed_to_fail = endpoint.is_optional; + let local_addr = endpoint.listen_addr; + + let mut listener = match endpoint.bind().await { + Ok(l) => l, + Err(e) if allowed_to_fail => { + log::debug!(target: "rpc", "JSON-RPC server failed to bind optional address: {:?}, error: {:?}", local_addr, e); + continue; + }, + Err(e) => return Err(e), + }; + let local_addr = listener.local_addr(); + local_addrs.push(local_addr); + let cfg = cfg.clone(); + + let mut id_provider2 = id_provider.clone(); + + tokio_handle.spawn(async move { + loop { + let (sock, remote_addr, rpc_cfg) = tokio::select! { + res = listener.accept() => { + match res { + Ok(s) => s, + Err(e) => { + log::debug!(target: "rpc", "Failed to accept connection: {:?}", e); + continue; + } } } - } - _ = cfg.stop_handle.clone().shutdown() => break, - }; - - let ip = remote_addr.ip(); - let cfg2 = cfg.clone(); - let svc = tower::service_fn(move |req: http::Request| { - let PerConnection { - methods, - service_builder, - metrics, - tokio_handle, - stop_handle, - rate_limit_whitelisted_ips, - } = cfg2.clone(); - - let proxy_ip = - if rate_limit_trust_proxy_headers { get_proxy_ip(&req) } else { None }; - - let rate_limit_cfg = if rate_limit_whitelisted_ips - .iter() - .any(|ips| ips.contains(proxy_ip.unwrap_or(ip))) - { - log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is trusted, disabling rate-limit", proxy_ip); - None - } else { - if !rate_limit_whitelisted_ips.is_empty() { - log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is not trusted, rate-limit enabled", proxy_ip); - } - rate_limit + _ = cfg.stop_handle.clone().shutdown() => break, }; - let is_websocket = ws::is_upgrade_request(&req); - let transport_label = if is_websocket { "ws" } else { "http" }; - - let middleware_layer = match (metrics, rate_limit_cfg) { - (None, None) => None, - (Some(metrics), None) => Some( - MiddlewareLayer::new().with_metrics(Metrics::new(metrics, transport_label)), - ), - (None, Some(rate_limit)) => - Some(MiddlewareLayer::new().with_rate_limit_per_minute(rate_limit)), - (Some(metrics), Some(rate_limit)) => Some( - MiddlewareLayer::new() - .with_metrics(Metrics::new(metrics, transport_label)) - .with_rate_limit_per_minute(rate_limit), - ), + let RpcSettings { + batch_config, + max_connections, + max_payload_in_mb, + max_payload_out_mb, + max_buffer_capacity_per_connection, + max_subscriptions_per_connection, + rpc_methods, + rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips, + host_filter, + cors, + rate_limit, + } = rpc_cfg; + + let http_middleware = tower::ServiceBuilder::new() + .option_layer(host_filter) + // Proxy `GET /health, /health/readiness` requests to the internal + // `system_health` method. + .layer(NodeHealthProxyLayer::default()) + .layer(cors); + + let mut builder = jsonrpsee::server::Server::builder() + .max_request_body_size(max_payload_in_mb.saturating_mul(MEGABYTE)) + .max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE)) + .max_connections(max_connections) + .max_subscriptions_per_connection(max_subscriptions_per_connection) + .enable_ws_ping( + PingConfig::new() + .ping_interval(Duration::from_secs(30)) + .inactive_limit(Duration::from_secs(60)) + .max_failures(3), + ) + .set_http_middleware(http_middleware) + .set_message_buffer_capacity(max_buffer_capacity_per_connection) + .set_batch_request_config(batch_config) + .custom_tokio_runtime(cfg.tokio_handle.clone()) + .set_id_provider(RandomStringIdProvider::new(16)); + + if let Some(provider) = id_provider2.take() { + builder = builder.set_id_provider(provider); + } else { + builder = builder.set_id_provider(RandomStringIdProvider::new(16)); }; - let rpc_middleware = RpcServiceBuilder::new() - .rpc_logger(1024) - .option_layer(middleware_layer.clone()); - let mut svc = - service_builder.set_rpc_middleware(rpc_middleware).build(methods, stop_handle); - - async move { - if is_websocket { - let on_disconnect = svc.on_session_closed(); - - // Spawn a task to handle when the connection is closed. - tokio_handle.spawn(async move { - let now = std::time::Instant::now(); - middleware_layer.as_ref().map(|m| m.ws_connect()); - on_disconnect.await; - middleware_layer.as_ref().map(|m| m.ws_disconnect(now)); - }); - } - - // https://github.com/rust-lang/rust/issues/102211 the error type can't be inferred - // to be `Box` so we need to convert it to - // a concrete type as workaround. - svc.call(req).await.map_err(|e| BoxError::from(e)) - } - }); - - cfg.tokio_handle.spawn(serve_with_graceful_shutdown( - sock, - svc, - cfg.stop_handle.clone().shutdown(), - )); - } - }); - - log::info!( - "Running JSON-RPC server: addr={}, allowed origins={}", - local_addr.map_or_else(|| "unknown".to_string(), |a| a.to_string()), - format_cors(cors) - ); + let service_builder = builder.to_service_builder(); + let deny_unsafe = deny_unsafe(&local_addr, &rpc_methods); + + let ip = remote_addr.ip(); + let cfg2 = cfg.clone(); + let service_builder2 = service_builder.clone(); + + let svc = + tower::service_fn(move |mut req: http::Request| { + req.extensions_mut().insert(deny_unsafe); + + let PerConnection { methods, metrics, tokio_handle, stop_handle } = + cfg2.clone(); + let service_builder = service_builder2.clone(); + + let proxy_ip = + if rate_limit_trust_proxy_headers { get_proxy_ip(&req) } else { None }; + + let rate_limit_cfg = if rate_limit_whitelisted_ips + .iter() + .any(|ips| ips.contains(proxy_ip.unwrap_or(ip))) + { + log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is trusted, disabling rate-limit", proxy_ip); + None + } else { + if !rate_limit_whitelisted_ips.is_empty() { + log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is not trusted, rate-limit enabled", proxy_ip); + } + rate_limit + }; + + let is_websocket = ws::is_upgrade_request(&req); + let transport_label = if is_websocket { "ws" } else { "http" }; + + let middleware_layer = match (metrics, rate_limit_cfg) { + (None, None) => None, + (Some(metrics), None) => Some( + MiddlewareLayer::new() + .with_metrics(Metrics::new(metrics, transport_label)), + ), + (None, Some(rate_limit)) => + Some(MiddlewareLayer::new().with_rate_limit_per_minute(rate_limit)), + (Some(metrics), Some(rate_limit)) => Some( + MiddlewareLayer::new() + .with_metrics(Metrics::new(metrics, transport_label)) + .with_rate_limit_per_minute(rate_limit), + ), + }; + + let rpc_middleware = + RpcServiceBuilder::new().option_layer(middleware_layer.clone()); + let mut svc = service_builder + .set_rpc_middleware(rpc_middleware) + .build(methods, stop_handle); + + async move { + if is_websocket { + let on_disconnect = svc.on_session_closed(); + + // Spawn a task to handle when the connection is closed. + tokio_handle.spawn(async move { + let now = std::time::Instant::now(); + middleware_layer.as_ref().map(|m| m.ws_connect()); + on_disconnect.await; + middleware_layer.as_ref().map(|m| m.ws_disconnect(now)); + }); + } + + // https://github.com/rust-lang/rust/issues/102211 the error type can't be inferred + // to be `Box` so we need to + // convert it to a concrete type as workaround. + svc.call(req).await.map_err(|e| BoxError::from(e)) + } + }); + + cfg.tokio_handle.spawn(serve_with_graceful_shutdown( + sock, + svc, + cfg.stop_handle.clone().shutdown(), + )); + } + }); + } + + if local_addrs.is_empty() { + return Err(Box::new(ListenAddrError)); + } + + // The previous logging format was before + // `Running JSON-RPC server: addr=127.0.0.1:9944, allowed origins=["*"]` + // + // The new format is `Running JSON-RPC server: addr=` + // with the exception that for a single address it will be `Running JSON-RPC server: addr=addr,` + // with a trailing comma. + // + // This is to make it work with old scripts/utils that parse the logs. + log::info!("Running JSON-RPC server: addr={}", format_listen_addrs(&local_addrs)); Ok(server_handle) } diff --git a/substrate/client/rpc-servers/src/utils.rs b/substrate/client/rpc-servers/src/utils.rs index d9d943c7c1fb..5b4a4bf22b95 100644 --- a/substrate/client/rpc-servers/src/utils.rs +++ b/substrate/client/rpc-servers/src/utils.rs @@ -18,29 +18,190 @@ //! Substrate RPC server utils. +use crate::BatchRequestConfig; use std::{ error::Error as StdError, net::{IpAddr, SocketAddr}, + num::NonZeroU32, str::FromStr, }; use forwarded_header_value::ForwardedHeaderValue; use http::header::{HeaderName, HeaderValue}; +use ip_network::IpNetwork; use jsonrpsee::{server::middleware::http::HostFilterLayer, RpcModule}; +use sc_rpc_api::DenyUnsafe; use tower_http::cors::{AllowOrigin, CorsLayer}; const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for"); const X_REAL_IP: HeaderName = HeaderName::from_static("x-real-ip"); const FORWARDED: HeaderName = HeaderName::from_static("forwarded"); -pub(crate) fn host_filtering(enabled: bool, addr: Option) -> Option { - // If the local_addr failed, fallback to wildcard. - let port = addr.map_or("*".to_string(), |p| p.port().to_string()); +#[derive(Debug)] +pub(crate) struct ListenAddrError; +impl std::error::Error for ListenAddrError {} + +impl std::fmt::Display for ListenAddrError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "No listen address was successfully bound") + } +} + +/// Available RPC methods. +#[derive(Debug, Copy, Clone)] +pub enum RpcMethods { + /// Allow only a safe subset of RPC methods. + Safe, + /// Expose every RPC method (even potentially unsafe ones). + Unsafe, + /// Automatically determine the RPC methods based on the connection. + Auto, +} + +impl Default for RpcMethods { + fn default() -> Self { + RpcMethods::Auto + } +} + +impl FromStr for RpcMethods { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "safe" => Ok(RpcMethods::Safe), + "unsafe" => Ok(RpcMethods::Unsafe), + "auto" => Ok(RpcMethods::Auto), + invalid => Err(format!("Invalid rpc methods {invalid}")), + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct RpcSettings { + pub(crate) batch_config: BatchRequestConfig, + pub(crate) max_connections: u32, + pub(crate) max_payload_in_mb: u32, + pub(crate) max_payload_out_mb: u32, + pub(crate) max_subscriptions_per_connection: u32, + pub(crate) max_buffer_capacity_per_connection: u32, + pub(crate) rpc_methods: RpcMethods, + pub(crate) rate_limit: Option, + pub(crate) rate_limit_trust_proxy_headers: bool, + pub(crate) rate_limit_whitelisted_ips: Vec, + pub(crate) cors: CorsLayer, + pub(crate) host_filter: Option, +} + +/// Represent a single RPC endpoint with its configuration. +#[derive(Debug, Clone)] +pub struct RpcEndpoint { + /// Listen address. + pub listen_addr: SocketAddr, + /// Batch request configuration. + pub batch_config: BatchRequestConfig, + /// Maximum number of connections. + pub max_connections: u32, + /// Maximum inbound payload size in MB. + pub max_payload_in_mb: u32, + /// Maximum outbound payload size in MB. + pub max_payload_out_mb: u32, + /// Maximum number of subscriptions per connection. + pub max_subscriptions_per_connection: u32, + /// Maximum buffer capacity per connection. + pub max_buffer_capacity_per_connection: u32, + /// Rate limit per minute. + pub rate_limit: Option, + /// Whether to trust proxy headers for rate limiting. + pub rate_limit_trust_proxy_headers: bool, + /// Whitelisted IPs for rate limiting. + pub rate_limit_whitelisted_ips: Vec, + /// CORS. + pub cors: Option>, + /// RPC methods to expose. + pub rpc_methods: RpcMethods, + /// Whether it's an optional listening address i.e, it's ignored if it fails to bind. + /// For example substrate tries to bind both ipv4 and ipv6 addresses but some platforms + /// may not support ipv6. + pub is_optional: bool, + /// Whether to retry with a random port if the provided port is already in use. + pub retry_random_port: bool, +} + +impl RpcEndpoint { + /// Binds to the listen address. + pub(crate) async fn bind(self) -> Result> { + let listener = match tokio::net::TcpListener::bind(self.listen_addr).await { + Ok(listener) => listener, + Err(_) if self.retry_random_port => { + let mut addr = self.listen_addr; + addr.set_port(0); + + tokio::net::TcpListener::bind(addr).await? + }, + Err(e) => return Err(e.into()), + }; + let local_addr = listener.local_addr()?; + let host_filter = host_filtering(self.cors.is_some(), local_addr); + let cors = try_into_cors(self.cors)?; + + Ok(Listener { + listener, + local_addr, + cfg: RpcSettings { + batch_config: self.batch_config, + max_connections: self.max_connections, + max_payload_in_mb: self.max_payload_in_mb, + max_payload_out_mb: self.max_payload_out_mb, + max_subscriptions_per_connection: self.max_subscriptions_per_connection, + max_buffer_capacity_per_connection: self.max_buffer_capacity_per_connection, + rpc_methods: self.rpc_methods, + rate_limit: self.rate_limit, + rate_limit_trust_proxy_headers: self.rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: self.rate_limit_whitelisted_ips, + host_filter, + cors, + }, + }) + } +} + +/// TCP socket server with RPC settings. +pub(crate) struct Listener { + listener: tokio::net::TcpListener, + local_addr: SocketAddr, + cfg: RpcSettings, +} + +impl Listener { + /// Accepts a new connection. + pub(crate) async fn accept( + &mut self, + ) -> std::io::Result<(tokio::net::TcpStream, SocketAddr, RpcSettings)> { + let (sock, remote_addr) = self.listener.accept().await?; + Ok((sock, remote_addr, self.cfg.clone())) + } + + /// Returns the local address the listener is bound to. + pub fn local_addr(&self) -> SocketAddr { + self.local_addr + } +} + +pub(crate) fn host_filtering(enabled: bool, addr: SocketAddr) -> Option { if enabled { // NOTE: The listening addresses are whitelisted by default. - let hosts = - [format!("localhost:{port}"), format!("127.0.0.1:{port}"), format!("[::1]:{port}")]; + + let mut hosts = Vec::new(); + + if addr.is_ipv4() { + hosts.push(format!("localhost:{}", addr.port())); + hosts.push(format!("127.0.0.1:{}", addr.port())); + } else { + hosts.push(format!("[::1]:{}", addr.port())); + } + Some(HostFilterLayer::new(hosts).expect("Valid hosts; qed")) } else { None @@ -65,13 +226,15 @@ pub(crate) fn build_rpc_api(mut rpc_api: RpcModule) } pub(crate) fn try_into_cors( - maybe_cors: Option<&Vec>, + maybe_cors: Option>, ) -> Result> { if let Some(cors) = maybe_cors { let mut list = Vec::new(); + for origin in cors { - list.push(HeaderValue::from_str(origin)?); + list.push(HeaderValue::from_str(&origin)?) } + Ok(CorsLayer::new().allow_origin(AllowOrigin::list(list))) } else { // allow all cors @@ -79,14 +242,6 @@ pub(crate) fn try_into_cors( } } -pub(crate) fn format_cors(maybe_cors: Option<&Vec>) -> String { - if let Some(cors) = maybe_cors { - format!("{:?}", cors) - } else { - format!("{:?}", ["*"]) - } -} - /// Extracts the IP addr from the HTTP request. /// /// It is extracted in the following order: @@ -126,6 +281,34 @@ pub(crate) fn get_proxy_ip(req: &http::Request) -> Option { None } +/// Get the `deny_unsafe` setting based on the address and the RPC methods exposed by the interface. +pub fn deny_unsafe(addr: &SocketAddr, methods: &RpcMethods) -> DenyUnsafe { + match (addr.ip().is_loopback(), methods) { + | (_, RpcMethods::Unsafe) | (false, RpcMethods::Auto) => DenyUnsafe::No, + _ => DenyUnsafe::Yes, + } +} + +pub(crate) fn format_listen_addrs(addr: &[SocketAddr]) -> String { + let mut s = String::new(); + + let mut it = addr.iter().peekable(); + + while let Some(addr) = it.next() { + s.push_str(&addr.to_string()); + + if it.peek().is_some() { + s.push(','); + } + } + + if addr.len() == 1 { + s.push(','); + } + + s +} + #[cfg(test)] mod tests { use super::*; diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index 1ec7895e3088..ae21895de38d 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } +jsonrpsee = { workspace = true, features = ["client-core", "macros", "server-core"] } # Internal chain structures for "chain_spec". sc-chain-spec = { workspace = true, default-features = true } # Pool for submitting extrinsics required by "transaction" @@ -45,7 +45,7 @@ rand = { workspace = true, default-features = true } schnellru = { workspace = true } [dev-dependencies] -jsonrpsee = { features = ["server", "ws-client"], workspace = true } +jsonrpsee = { workspace = true, features = ["server", "ws-client"] } serde_json = { workspace = true, default-features = true } tokio = { features = ["macros"], workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } diff --git a/substrate/client/rpc/Cargo.toml b/substrate/client/rpc/Cargo.toml index 4a8f4b3ec630..6fe28a3873e9 100644 --- a/substrate/client/rpc/Cargo.toml +++ b/substrate/client/rpc/Cargo.toml @@ -43,7 +43,6 @@ sp-statement-store = { workspace = true, default-features = true } tokio = { workspace = true, default-features = true } [dev-dependencies] -sp-tracing = { workspace = true } assert_matches = { workspace = true } sc-block-builder = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } @@ -55,7 +54,6 @@ tokio = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } pretty_assertions = { workspace = true } -tracing-subscriber = { features = ["env-filter"], workspace = true } [features] test-helpers = [] diff --git a/substrate/client/rpc/src/author/mod.rs b/substrate/client/rpc/src/author/mod.rs index 2fc21a238bc9..731f4df2f6f3 100644 --- a/substrate/client/rpc/src/author/mod.rs +++ b/substrate/client/rpc/src/author/mod.rs @@ -30,8 +30,8 @@ use crate::{ use codec::{Decode, Encode}; use futures::TryFutureExt; -use jsonrpsee::{core::async_trait, types::ErrorObject, PendingSubscriptionSink}; -use sc_rpc_api::DenyUnsafe; +use jsonrpsee::{core::async_trait, types::ErrorObject, Extensions, PendingSubscriptionSink}; +use sc_rpc_api::check_if_safe; use sc_transaction_pool_api::{ error::IntoPoolError, BlockHash, InPoolTransaction, TransactionFor, TransactionPool, TransactionSource, TxHash, @@ -55,8 +55,6 @@ pub struct Author { pool: Arc

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

, keystore: KeystorePtr, - deny_unsafe: DenyUnsafe, executor: SubscriptionTaskExecutor, ) -> Self { - Author { client, pool, keystore, deny_unsafe, executor } + Author { client, pool, keystore, executor } } } @@ -104,8 +101,14 @@ where }) } - fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> Result<()> { - self.deny_unsafe.check_if_safe()?; + fn insert_key( + &self, + ext: &Extensions, + key_type: String, + suri: String, + public: Bytes, + ) -> Result<()> { + check_if_safe(ext)?; let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?; self.keystore @@ -114,8 +117,8 @@ where Ok(()) } - fn rotate_keys(&self) -> Result { - self.deny_unsafe.check_if_safe()?; + fn rotate_keys(&self, ext: &Extensions) -> Result { + check_if_safe(ext)?; let best_block_hash = self.client.info().best_hash; let mut runtime_api = self.client.runtime_api(); @@ -128,8 +131,8 @@ where .map_err(|api_err| Error::Client(Box::new(api_err)).into()) } - fn has_session_keys(&self, session_keys: Bytes) -> Result { - self.deny_unsafe.check_if_safe()?; + fn has_session_keys(&self, ext: &Extensions, session_keys: Bytes) -> Result { + check_if_safe(ext)?; let best_block_hash = self.client.info().best_hash; let keys = self @@ -142,8 +145,8 @@ where Ok(self.keystore.has_keys(&keys)) } - fn has_key(&self, public_key: Bytes, key_type: String) -> Result { - self.deny_unsafe.check_if_safe()?; + fn has_key(&self, ext: &Extensions, public_key: Bytes, key_type: String) -> Result { + check_if_safe(ext)?; let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?; Ok(self.keystore.has_keys(&[(public_key.to_vec(), key_type)])) @@ -155,9 +158,10 @@ where fn remove_extrinsic( &self, + ext: &Extensions, bytes_or_hash: Vec>>, ) -> Result>> { - self.deny_unsafe.check_if_safe()?; + check_if_safe(ext)?; let hashes = bytes_or_hash .into_iter() .map(|x| match x { diff --git a/substrate/client/rpc/src/author/tests.rs b/substrate/client/rpc/src/author/tests.rs index 6bcb3e7863c0..bde60960eaf4 100644 --- a/substrate/client/rpc/src/author/tests.rs +++ b/substrate/client/rpc/src/author/tests.rs @@ -22,6 +22,7 @@ use crate::testing::{test_executor, timeout_secs}; use assert_matches::assert_matches; use codec::Encode; use jsonrpsee::{core::EmptyServerParams as EmptyParams, MethodsError as RpcError, RpcModule}; +use sc_rpc_api::DenyUnsafe; use sc_transaction_pool::{BasicPool, FullChainApi}; use sc_transaction_pool_api::TransactionStatus; use sp_core::{ @@ -72,26 +73,27 @@ impl Default for TestSetup { } impl TestSetup { - fn author(&self) -> Author> { - Author { + fn to_rpc(&self) -> RpcModule>> { + let mut module = Author { client: self.client.clone(), pool: self.pool.clone(), keystore: self.keystore.clone(), - deny_unsafe: DenyUnsafe::No, executor: test_executor(), } + .into_rpc(); + module.extensions_mut().insert(DenyUnsafe::No); + module } fn into_rpc() -> RpcModule>> { - Self::default().author().into_rpc() + Self::default().to_rpc() } } #[tokio::test] async fn author_submit_transaction_should_not_cause_error() { - sp_tracing::init_for_tests(); - let author = TestSetup::default().author(); - let api = author.into_rpc(); + let api = TestSetup::into_rpc(); + let xt: Bytes = uxt(AccountKeyring::Alice, 1).encode().into(); let extrinsic_hash: H256 = blake2_256(&xt).into(); let response: H256 = api.call("author_submitExtrinsic", [xt.clone()]).await.unwrap(); @@ -179,7 +181,7 @@ async fn author_should_return_pending_extrinsics() { async fn author_should_remove_extrinsics() { const METHOD: &'static str = "author_removeExtrinsic"; let setup = TestSetup::default(); - let api = setup.author().into_rpc(); + let api = setup.to_rpc(); // Submit three extrinsics, then remove two of them (will cause the third to be removed as well, // having a higher nonce) @@ -214,7 +216,7 @@ async fn author_should_remove_extrinsics() { #[tokio::test] async fn author_should_insert_key() { let setup = TestSetup::default(); - let api = setup.author().into_rpc(); + let api = setup.to_rpc(); let suri = "//Alice"; let keypair = ed25519::Pair::from_string(suri, None).expect("generates keypair"); let params: (String, String, Bytes) = ( @@ -231,7 +233,7 @@ async fn author_should_insert_key() { #[tokio::test] async fn author_should_rotate_keys() { let setup = TestSetup::default(); - let api = setup.author().into_rpc(); + let api = setup.to_rpc(); let new_pubkeys: Bytes = api.call("author_rotateKeys", EmptyParams::new()).await.unwrap(); let session_keys = @@ -244,7 +246,6 @@ async fn author_should_rotate_keys() { #[tokio::test] async fn author_has_session_keys() { - // Setup let api = TestSetup::into_rpc(); // Add a valid session key @@ -255,7 +256,7 @@ async fn author_has_session_keys() { // Add a session key in a different keystore let non_existent_pubkeys: Bytes = { - let api2 = TestSetup::default().author().into_rpc(); + let api2 = TestSetup::into_rpc(); api2.call("author_rotateKeys", EmptyParams::new()) .await .expect("Rotates the keys") @@ -279,8 +280,6 @@ async fn author_has_session_keys() { #[tokio::test] async fn author_has_key() { - sp_tracing::init_for_tests(); - let api = TestSetup::into_rpc(); let suri = "//Alice"; let alice_keypair = ed25519::Pair::from_string(suri, None).expect("Generates keypair"); diff --git a/substrate/client/rpc/src/dev/mod.rs b/substrate/client/rpc/src/dev/mod.rs index 424ef72b694e..3ccda760c3f5 100644 --- a/substrate/client/rpc/src/dev/mod.rs +++ b/substrate/client/rpc/src/dev/mod.rs @@ -22,8 +22,9 @@ #[cfg(test)] mod tests; +use jsonrpsee::Extensions; use sc_client_api::{BlockBackend, HeaderBackend}; -use sc_rpc_api::{dev::error::Error, DenyUnsafe}; +use sc_rpc_api::{check_if_safe, dev::error::Error}; use sp_api::{ApiExt, Core, ProvideRuntimeApi}; use sp_core::Encode; use sp_runtime::{ @@ -42,14 +43,13 @@ type HasherOf = <::Header as Header>::Hashing; /// The Dev API. All methods are unsafe. pub struct Dev { client: Arc, - deny_unsafe: DenyUnsafe, _phantom: PhantomData, } impl Dev { /// Create a new Dev API. - pub fn new(client: Arc, deny_unsafe: DenyUnsafe) -> Self { - Self { client, deny_unsafe, _phantom: PhantomData::default() } + pub fn new(client: Arc) -> Self { + Self { client, _phantom: PhantomData::default() } } } @@ -64,8 +64,12 @@ where + 'static, Client::Api: Core, { - fn block_stats(&self, hash: Block::Hash) -> Result, Error> { - self.deny_unsafe.check_if_safe()?; + fn block_stats( + &self, + ext: &Extensions, + hash: Block::Hash, + ) -> Result, Error> { + check_if_safe(ext)?; let block = { let block = self.client.block(hash).map_err(|e| Error::BlockQueryError(Box::new(e)))?; diff --git a/substrate/client/rpc/src/dev/tests.rs b/substrate/client/rpc/src/dev/tests.rs index 8f09d48e8a5d..ff691af7de41 100644 --- a/substrate/client/rpc/src/dev/tests.rs +++ b/substrate/client/rpc/src/dev/tests.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use super::*; +use crate::DenyUnsafe; use sc_block_builder::BlockBuilderBuilder; use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; @@ -25,7 +26,8 @@ use substrate_test_runtime_client::{prelude::*, runtime::Block}; #[tokio::test] async fn block_stats_work() { let client = Arc::new(substrate_test_runtime_client::new()); - let api = >::new(client.clone(), DenyUnsafe::No).into_rpc(); + let mut api = >::new(client.clone()).into_rpc(); + api.extensions_mut().insert(DenyUnsafe::No); let block = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().genesis_hash) @@ -77,7 +79,8 @@ async fn block_stats_work() { #[tokio::test] async fn deny_unsafe_works() { let client = Arc::new(substrate_test_runtime_client::new()); - let api = >::new(client.clone(), DenyUnsafe::Yes).into_rpc(); + let mut api = >::new(client.clone()).into_rpc(); + api.extensions_mut().insert(DenyUnsafe::Yes); let block = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().genesis_hash) @@ -101,6 +104,6 @@ async fn deny_unsafe_works() { assert_eq!( resp, - r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"# + r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"RPC call is unsafe to be called externally"}}"# ); } diff --git a/substrate/client/rpc/src/lib.rs b/substrate/client/rpc/src/lib.rs index b40d0341e321..33e4ac2f4c89 100644 --- a/substrate/client/rpc/src/lib.rs +++ b/substrate/client/rpc/src/lib.rs @@ -22,12 +22,9 @@ #![warn(missing_docs)] -pub use jsonrpsee::core::{ - id_providers::{ - RandomIntegerIdProvider as RandomIntegerSubscriptionId, - RandomStringIdProvider as RandomStringSubscriptionId, - }, - traits::IdProvider as RpcSubscriptionIdProvider, +pub use jsonrpsee::core::id_providers::{ + RandomIntegerIdProvider as RandomIntegerSubscriptionId, + RandomStringIdProvider as RandomStringSubscriptionId, }; pub use sc_rpc_api::DenyUnsafe; diff --git a/substrate/client/rpc/src/offchain/mod.rs b/substrate/client/rpc/src/offchain/mod.rs index 661673866053..af6bc1ba58c8 100644 --- a/substrate/client/rpc/src/offchain/mod.rs +++ b/substrate/client/rpc/src/offchain/mod.rs @@ -22,11 +22,11 @@ mod tests; use self::error::Error; -use jsonrpsee::core::async_trait; +use jsonrpsee::{core::async_trait, Extensions}; use parking_lot::RwLock; +use sc_rpc_api::check_if_safe; /// Re-export the API for backward compatibility. pub use sc_rpc_api::offchain::*; -use sc_rpc_api::DenyUnsafe; use sp_core::{ offchain::{OffchainStorage, StorageKind}, Bytes, @@ -38,20 +38,25 @@ use std::sync::Arc; pub struct Offchain { /// Offchain storage storage: Arc>, - deny_unsafe: DenyUnsafe, } impl Offchain { /// Create new instance of Offchain API. - pub fn new(storage: T, deny_unsafe: DenyUnsafe) -> Self { - Offchain { storage: Arc::new(RwLock::new(storage)), deny_unsafe } + pub fn new(storage: T) -> Self { + Offchain { storage: Arc::new(RwLock::new(storage)) } } } #[async_trait] impl OffchainApiServer for Offchain { - fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<(), Error> { - self.deny_unsafe.check_if_safe()?; + fn set_local_storage( + &self, + ext: &Extensions, + kind: StorageKind, + key: Bytes, + value: Bytes, + ) -> Result<(), Error> { + check_if_safe(ext)?; let prefix = match kind { StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX, @@ -61,8 +66,13 @@ impl OffchainApiServer for Offchain { Ok(()) } - fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result, Error> { - self.deny_unsafe.check_if_safe()?; + fn get_local_storage( + &self, + ext: &Extensions, + kind: StorageKind, + key: Bytes, + ) -> Result, Error> { + check_if_safe(ext)?; let prefix = match kind { StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX, diff --git a/substrate/client/rpc/src/offchain/tests.rs b/substrate/client/rpc/src/offchain/tests.rs index 7758fac7fabd..41f22c2dc964 100644 --- a/substrate/client/rpc/src/offchain/tests.rs +++ b/substrate/client/rpc/src/offchain/tests.rs @@ -17,22 +17,25 @@ // along with this program. If not, see . use super::*; +use crate::testing::{allow_unsafe, deny_unsafe}; use assert_matches::assert_matches; use sp_core::{offchain::storage::InMemOffchainStorage, Bytes}; #[test] fn local_storage_should_work() { let storage = InMemOffchainStorage::default(); - let offchain = Offchain::new(storage, DenyUnsafe::No); + let offchain = Offchain::new(storage); let key = Bytes(b"offchain_storage".to_vec()); let value = Bytes(b"offchain_value".to_vec()); + let ext = allow_unsafe(); + assert_matches!( - offchain.set_local_storage(StorageKind::PERSISTENT, key.clone(), value.clone()), + offchain.set_local_storage(&ext, StorageKind::PERSISTENT, key.clone(), value.clone()), Ok(()) ); assert_matches!( - offchain.get_local_storage(StorageKind::PERSISTENT, key), + offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), Ok(Some(ref v)) if *v == value ); } @@ -40,18 +43,20 @@ fn local_storage_should_work() { #[test] fn offchain_calls_considered_unsafe() { let storage = InMemOffchainStorage::default(); - let offchain = Offchain::new(storage, DenyUnsafe::Yes); + let offchain = Offchain::new(storage); let key = Bytes(b"offchain_storage".to_vec()); let value = Bytes(b"offchain_value".to_vec()); + let ext = deny_unsafe(); + assert_matches!( - offchain.set_local_storage(StorageKind::PERSISTENT, key.clone(), value.clone()), + offchain.set_local_storage(&ext, StorageKind::PERSISTENT, key.clone(), value.clone()), Err(Error::UnsafeRpcCalled(e)) => { assert_eq!(e.to_string(), "RPC call is unsafe to be called externally") } ); assert_matches!( - offchain.get_local_storage(StorageKind::PERSISTENT, key), + offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), Err(Error::UnsafeRpcCalled(e)) => { assert_eq!(e.to_string(), "RPC call is unsafe to be called externally") } diff --git a/substrate/client/rpc/src/state/mod.rs b/substrate/client/rpc/src/state/mod.rs index c9a41e25eda8..d8989b3e1bee 100644 --- a/substrate/client/rpc/src/state/mod.rs +++ b/substrate/client/rpc/src/state/mod.rs @@ -25,11 +25,11 @@ mod utils; mod tests; use crate::SubscriptionTaskExecutor; -use jsonrpsee::{core::async_trait, PendingSubscriptionSink}; +use jsonrpsee::{core::async_trait, Extensions, PendingSubscriptionSink}; use sc_client_api::{ Backend, BlockBackend, BlockchainEvents, ExecutorProvider, ProofProvider, StorageProvider, }; -use sc_rpc_api::DenyUnsafe; +use sc_rpc_api::{check_if_safe, DenyUnsafe}; use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi}; use sp_blockchain::{HeaderBackend, HeaderMetadata}; use sp_core::{ @@ -164,7 +164,6 @@ where pub fn new_full( client: Arc, executor: SubscriptionTaskExecutor, - deny_unsafe: DenyUnsafe, ) -> (State, ChildState) where Block: BlockT + 'static, @@ -187,14 +186,12 @@ where let child_backend = Box::new(self::state_full::FullState::new(client.clone(), executor.clone())); let backend = Box::new(self::state_full::FullState::new(client, executor)); - (State { backend, deny_unsafe }, ChildState { backend: child_backend }) + (State { backend }, ChildState { backend: child_backend }) } /// State API with subscriptions support. pub struct State { backend: Box>, - /// Whether to deny unsafe calls - deny_unsafe: DenyUnsafe, } #[async_trait] @@ -222,10 +219,11 @@ where fn storage_pairs( &self, + ext: &Extensions, key_prefix: StorageKey, block: Option, ) -> Result, Error> { - self.deny_unsafe.check_if_safe()?; + check_if_safe(ext)?; self.backend.storage_pairs(block, key_prefix).map_err(Into::into) } @@ -262,13 +260,15 @@ where async fn storage_size( &self, + ext: &Extensions, key: StorageKey, block: Option, ) -> Result, Error> { - self.backend - .storage_size(block, key, self.deny_unsafe) - .await - .map_err(Into::into) + let deny_unsafe = ext + .get::() + .cloned() + .expect("DenyUnsafe extension is always set by the substrate rpc server; qed"); + self.backend.storage_size(block, key, deny_unsafe).await.map_err(Into::into) } fn metadata(&self, block: Option) -> Result { @@ -281,11 +281,12 @@ where fn query_storage( &self, + ext: &Extensions, keys: Vec, from: Block::Hash, to: Option, ) -> Result>, Error> { - self.deny_unsafe.check_if_safe()?; + check_if_safe(ext)?; self.backend.query_storage(from, to, keys).map_err(Into::into) } @@ -312,12 +313,13 @@ where /// Note: requires runtimes compiled with wasm tracing support, `--features with-tracing`. fn trace_block( &self, + ext: &Extensions, block: Block::Hash, targets: Option, storage_keys: Option, methods: Option, ) -> Result { - self.deny_unsafe.check_if_safe()?; + check_if_safe(ext)?; self.backend .trace_block(block, targets, storage_keys, methods) .map_err(Into::into) @@ -327,8 +329,17 @@ where self.backend.subscribe_runtime_version(pending) } - fn subscribe_storage(&self, pending: PendingSubscriptionSink, keys: Option>) { - self.backend.subscribe_storage(pending, keys, self.deny_unsafe) + fn subscribe_storage( + &self, + pending: PendingSubscriptionSink, + ext: &Extensions, + keys: Option>, + ) { + let deny_unsafe = ext + .get::() + .cloned() + .expect("DenyUnsafe extension is always set by the substrate rpc server; qed"); + self.backend.subscribe_storage(pending, keys, deny_unsafe) } } diff --git a/substrate/client/rpc/src/state/tests.rs b/substrate/client/rpc/src/state/tests.rs index 86df46f63a0f..eef795070343 100644 --- a/substrate/client/rpc/src/state/tests.rs +++ b/substrate/client/rpc/src/state/tests.rs @@ -18,12 +18,11 @@ use self::error::Error; use super::*; -use crate::testing::{test_executor, timeout_secs}; +use crate::testing::{allow_unsafe, test_executor, timeout_secs}; use assert_matches::assert_matches; use futures::executor; use jsonrpsee::{core::EmptyServerParams as EmptyParams, MethodsError as RpcError}; use sc_block_builder::BlockBuilderBuilder; -use sc_rpc_api::DenyUnsafe; use sp_consensus::BlockOrigin; use sp_core::{hash::H256, storage::ChildInfo}; use std::sync::Arc; @@ -39,14 +38,6 @@ fn prefixed_storage_key() -> PrefixedStorageKey { child_info.prefixed_storage_key() } -fn init_logger() { - use tracing_subscriber::{EnvFilter, FmtSubscriber}; - - let _ = FmtSubscriber::builder() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); -} - #[tokio::test] async fn should_return_storage() { const KEY: &[u8] = b":mock"; @@ -62,8 +53,9 @@ async fn should_return_storage() { .add_extra_storage(b":map:acc2".to_vec(), vec![1, 2, 3]) .build(); let genesis_hash = client.genesis_hash(); - let (client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No); + let (client, child) = new_full(Arc::new(client), test_executor()); let key = StorageKey(KEY.to_vec()); + let ext = allow_unsafe(); assert_eq!( client @@ -78,11 +70,15 @@ async fn should_return_storage() { Ok(true) ); assert_eq!( - client.storage_size(key.clone(), None).await.unwrap().unwrap() as usize, + client.storage_size(&ext, key.clone(), None).await.unwrap().unwrap() as usize, VALUE.len(), ); assert_eq!( - client.storage_size(StorageKey(b":map".to_vec()), None).await.unwrap().unwrap() as usize, + client + .storage_size(&ext, StorageKey(b":map".to_vec()), None) + .await + .unwrap() + .unwrap() as usize, 2 + 3, ); assert_eq!( @@ -110,7 +106,7 @@ async fn should_return_storage_entries() { .add_extra_child_storage(&child_info, KEY2.to_vec(), CHILD_VALUE2.to_vec()) .build(); let genesis_hash = client.genesis_hash(); - let (_client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No); + let (_client, child) = new_full(Arc::new(client), test_executor()); let keys = &[StorageKey(KEY1.to_vec()), StorageKey(KEY2.to_vec())]; assert_eq!( @@ -141,7 +137,7 @@ async fn should_return_child_storage() { .build(), ); let genesis_hash = client.genesis_hash(); - let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No); + let (_client, child) = new_full(client, test_executor()); let child_key = prefixed_storage_key(); let key = StorageKey(b"key".to_vec()); @@ -172,7 +168,7 @@ async fn should_return_child_storage_entries() { .build(), ); let genesis_hash = client.genesis_hash(); - let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No); + let (_client, child) = new_full(client, test_executor()); let child_key = prefixed_storage_key(); let keys = vec![StorageKey(b"key1".to_vec()), StorageKey(b"key2".to_vec())]; @@ -203,7 +199,7 @@ async fn should_return_child_storage_entries() { async fn should_call_contract() { let client = Arc::new(substrate_test_runtime_client::new()); let genesis_hash = client.genesis_hash(); - let (client, _child) = new_full(client, test_executor(), DenyUnsafe::No); + let (client, _child) = new_full(client, test_executor()); assert_matches!( client.call("balanceOf".into(), Bytes(vec![1, 2, 3]), Some(genesis_hash).into()), @@ -213,13 +209,12 @@ async fn should_call_contract() { #[tokio::test] async fn should_notify_about_storage_changes() { - init_logger(); - let mut sub = { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No); + let (api, _child) = new_full(client.clone(), test_executor()); + let mut api_rpc = api.into_rpc(); + api_rpc.extensions_mut().insert(DenyUnsafe::No); - let api_rpc = api.into_rpc(); let sub = api_rpc .subscribe_unbounded("state_subscribeStorage", EmptyParams::new()) .await @@ -253,11 +248,9 @@ async fn should_notify_about_storage_changes() { #[tokio::test] async fn should_send_initial_storage_changes_and_notifications() { - init_logger(); - let mut sub = { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No); + let (api, _child) = new_full(client.clone(), test_executor()); let alice_balance_key = [ sp_crypto_hashing::twox_128(b"System"), @@ -270,7 +263,9 @@ async fn should_send_initial_storage_changes_and_notifications() { .cloned() .collect::>(); - let api_rpc = api.into_rpc(); + let mut api_rpc = api.into_rpc(); + api_rpc.extensions_mut().insert(DenyUnsafe::No); + let sub = api_rpc .subscribe_unbounded( "state_subscribeStorage", @@ -305,7 +300,7 @@ async fn should_send_initial_storage_changes_and_notifications() { #[tokio::test] async fn should_query_storage() { async fn run_tests(client: Arc) { - let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No); + let (api, _child) = new_full(client.clone(), test_executor()); let add_block = |index| { let mut builder = BlockBuilderBuilder::new(&*client) @@ -377,14 +372,16 @@ async fn should_query_storage() { }, ]; + let ext = allow_unsafe(); + // Query changes only up to block1 let keys = (1..6).map(|k| StorageKey(vec![k])).collect::>(); - let result = api.query_storage(keys.clone(), genesis_hash, Some(block1_hash).into()); + let result = api.query_storage(&ext, keys.clone(), genesis_hash, Some(block1_hash).into()); assert_eq!(result.unwrap(), expected); // Query all changes - let result = api.query_storage(keys.clone(), genesis_hash, None.into()); + let result = api.query_storage(&ext, keys.clone(), genesis_hash, None.into()); expected.push(StorageChangeSet { block: block2_hash, @@ -397,13 +394,13 @@ async fn should_query_storage() { assert_eq!(result.unwrap(), expected); // Query changes up to block2. - let result = api.query_storage(keys.clone(), genesis_hash, Some(block2_hash)); + let result = api.query_storage(&ext, keys.clone(), genesis_hash, Some(block2_hash)); assert_eq!(result.unwrap(), expected); // Inverted range. assert_matches!( - api.query_storage(keys.clone(), block1_hash, Some(genesis_hash)), + api.query_storage(&ext, keys.clone(), block1_hash, Some(genesis_hash)), Err(Error::InvalidBlockRange { from, to, details }) if from == format!("1 ({:?})", block1_hash) && to == format!("0 ({:?})", genesis_hash) && details == "from number > to number".to_owned() ); @@ -412,7 +409,7 @@ async fn should_query_storage() { // Invalid second hash. assert_matches!( - api.query_storage(keys.clone(), genesis_hash, Some(random_hash1)), + api.query_storage(&ext, keys.clone(), genesis_hash, Some(random_hash1)), Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", genesis_hash) && to == format!("{:?}", Some(random_hash1)) && details == format!( "UnknownBlock: Header was not found in the database: {:?}", random_hash1 @@ -421,7 +418,7 @@ async fn should_query_storage() { // Invalid first hash with Some other hash. assert_matches!( - api.query_storage(keys.clone(), random_hash1, Some(genesis_hash)), + api.query_storage(&ext, keys.clone(), random_hash1, Some(genesis_hash)), Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", random_hash1) && to == format!("{:?}", Some(genesis_hash)) && details == format!( "UnknownBlock: Header was not found in the database: {:?}", random_hash1 @@ -430,7 +427,7 @@ async fn should_query_storage() { // Invalid first hash with None. assert_matches!( - api.query_storage(keys.clone(), random_hash1, None), + api.query_storage(&ext, keys.clone(), random_hash1, None), Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", random_hash1) && to == format!("{:?}", Some(block2_hash)) && details == format!( "UnknownBlock: Header was not found in the database: {:?}", random_hash1 @@ -439,7 +436,7 @@ async fn should_query_storage() { // Both hashes invalid. assert_matches!( - api.query_storage(keys.clone(), random_hash1, Some(random_hash2)), + api.query_storage(&ext, keys.clone(), random_hash1, Some(random_hash2)), Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", random_hash1) && to == format!("{:?}", Some(random_hash2)) && details == format!( "UnknownBlock: Header was not found in the database: {:?}", random_hash1 @@ -471,7 +468,7 @@ async fn should_query_storage() { #[tokio::test] async fn should_return_runtime_version() { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No); + let (api, _child) = new_full(client.clone(), test_executor()); // it is basically json-encoded substrate_test_runtime_client::runtime::VERSION let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\ @@ -493,9 +490,10 @@ async fn should_return_runtime_version() { async fn should_notify_on_runtime_version_initially() { let mut sub = { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client, test_executor(), DenyUnsafe::No); + let (api, _child) = new_full(client, test_executor()); + let mut api_rpc = api.into_rpc(); + api_rpc.extensions_mut().insert(DenyUnsafe::No); - let api_rpc = api.into_rpc(); let sub = api_rpc .subscribe_unbounded("state_subscribeRuntimeVersion", EmptyParams::new()) .await @@ -518,12 +516,11 @@ fn should_deserialize_storage_key() { #[tokio::test] async fn wildcard_storage_subscriptions_are_rpc_unsafe() { - init_logger(); - let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes); + let (api, _child) = new_full(client, test_executor()); + let mut api_rpc = api.into_rpc(); + api_rpc.extensions_mut().insert(DenyUnsafe::Yes); - let api_rpc = api.into_rpc(); let err = api_rpc.subscribe_unbounded("state_subscribeStorage", EmptyParams::new()).await; assert_matches!(err, Err(RpcError::JsonRpc(e)) if e.message() == "RPC call is unsafe to be called externally"); } @@ -531,8 +528,9 @@ async fn wildcard_storage_subscriptions_are_rpc_unsafe() { #[tokio::test] async fn concrete_storage_subscriptions_are_rpc_safe() { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes); - let api_rpc = api.into_rpc(); + let (api, _child) = new_full(client, test_executor()); + let mut api_rpc = api.into_rpc(); + api_rpc.extensions_mut().insert(DenyUnsafe::Yes); let key = StorageKey(STORAGE_KEY.to_vec()); let sub = api_rpc.subscribe_unbounded("state_subscribeStorage", [[key]]).await; diff --git a/substrate/client/rpc/src/statement/mod.rs b/substrate/client/rpc/src/statement/mod.rs index e99135aec38c..81aa50f73430 100644 --- a/substrate/client/rpc/src/statement/mod.rs +++ b/substrate/client/rpc/src/statement/mod.rs @@ -19,10 +19,12 @@ //! Substrate statement store API. use codec::{Decode, Encode}; -use jsonrpsee::core::{async_trait, RpcResult}; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + Extensions, +}; /// Re-export the API for backward compatibility. pub use sc_rpc_api::statement::{error::Error, StatementApiServer}; -use sc_rpc_api::DenyUnsafe; use sp_core::Bytes; use sp_statement_store::{StatementSource, SubmitResult}; use std::sync::Arc; @@ -30,23 +32,19 @@ use std::sync::Arc; /// Statement store API pub struct StatementStore { store: Arc, - deny_unsafe: DenyUnsafe, } impl StatementStore { /// Create new instance of Offchain API. - pub fn new( - store: Arc, - deny_unsafe: DenyUnsafe, - ) -> Self { - StatementStore { store, deny_unsafe } + pub fn new(store: Arc) -> Self { + StatementStore { store } } } #[async_trait] impl StatementApiServer for StatementStore { - fn dump(&self) -> RpcResult> { - self.deny_unsafe.check_if_safe()?; + fn dump(&self, ext: &Extensions) -> RpcResult> { + sc_rpc_api::check_if_safe(ext)?; let statements = self.store.statements().map_err(|e| Error::StatementStore(e.to_string()))?; diff --git a/substrate/client/rpc/src/system/mod.rs b/substrate/client/rpc/src/system/mod.rs index 8c7510c64cb5..e0752749bcbd 100644 --- a/substrate/client/rpc/src/system/mod.rs +++ b/substrate/client/rpc/src/system/mod.rs @@ -22,8 +22,11 @@ mod tests; use futures::channel::oneshot; -use jsonrpsee::core::{async_trait, JsonValue}; -use sc_rpc_api::DenyUnsafe; +use jsonrpsee::{ + core::{async_trait, JsonValue}, + Extensions, +}; +use sc_rpc_api::check_if_safe; use sc_tracing::logging; use sc_utils::mpsc::TracingUnboundedSender; use sp_runtime::traits::{self, Header as HeaderT}; @@ -35,7 +38,6 @@ pub use sc_rpc_api::system::*; pub struct System { info: SystemInfo, send_back: TracingUnboundedSender>, - deny_unsafe: DenyUnsafe, } /// Request to be processed. @@ -68,12 +70,8 @@ impl System { /// /// The `send_back` will be used to transmit some of the requests. The user is responsible for /// reading from that channel and answering the requests. - pub fn new( - info: SystemInfo, - send_back: TracingUnboundedSender>, - deny_unsafe: DenyUnsafe, - ) -> Self { - System { info, send_back, deny_unsafe } + pub fn new(info: SystemInfo, send_back: TracingUnboundedSender>) -> Self { + System { info, send_back } } } @@ -119,22 +117,23 @@ impl SystemApiServer::Number> async fn system_peers( &self, + ext: &Extensions, ) -> Result::Number>>, Error> { - self.deny_unsafe.check_if_safe()?; + check_if_safe(ext)?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::Peers(tx)); rx.await.map_err(|e| Error::Internal(e.to_string())) } - async fn system_network_state(&self) -> Result { - self.deny_unsafe.check_if_safe()?; + async fn system_network_state(&self, ext: &Extensions) -> Result { + check_if_safe(ext)?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkState(tx)); rx.await.map_err(|e| Error::Internal(e.to_string())) } - async fn system_add_reserved_peer(&self, peer: String) -> Result<(), Error> { - self.deny_unsafe.check_if_safe()?; + async fn system_add_reserved_peer(&self, ext: &Extensions, peer: String) -> Result<(), Error> { + check_if_safe(ext)?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkAddReservedPeer(peer, tx)); match rx.await { @@ -144,8 +143,12 @@ impl SystemApiServer::Number> } } - async fn system_remove_reserved_peer(&self, peer: String) -> Result<(), Error> { - self.deny_unsafe.check_if_safe()?; + async fn system_remove_reserved_peer( + &self, + ext: &Extensions, + peer: String, + ) -> Result<(), Error> { + check_if_safe(ext)?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkRemoveReservedPeer(peer, tx)); match rx.await { @@ -173,15 +176,15 @@ impl SystemApiServer::Number> rx.await.map_err(|e| Error::Internal(e.to_string())) } - fn system_add_log_filter(&self, directives: String) -> Result<(), Error> { - self.deny_unsafe.check_if_safe()?; + fn system_add_log_filter(&self, ext: &Extensions, directives: String) -> Result<(), Error> { + check_if_safe(ext)?; logging::add_directives(&directives); logging::reload_filter().map_err(|e| Error::Internal(e)) } - fn system_reset_log_filter(&self) -> Result<(), Error> { - self.deny_unsafe.check_if_safe()?; + fn system_reset_log_filter(&self, ext: &Extensions) -> Result<(), Error> { + check_if_safe(ext)?; logging::reset_log_filter().map_err(|e| Error::Internal(e)) } } diff --git a/substrate/client/rpc/src/system/tests.rs b/substrate/client/rpc/src/system/tests.rs index 03967c63523c..bfef0e429ceb 100644 --- a/substrate/client/rpc/src/system/tests.rs +++ b/substrate/client/rpc/src/system/tests.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use super::{helpers::SyncState, *}; +use crate::DenyUnsafe; use assert_matches::assert_matches; use futures::prelude::*; use jsonrpsee::{core::EmptyServerParams as EmptyParams, MethodsError as RpcError, RpcModule}; @@ -127,7 +128,7 @@ fn api>>(sync: T) -> RpcModule> { future::ready(()) })) }); - System::new( + let mut module = System::new( SystemInfo { impl_name: "testclient".into(), impl_version: "0.2.0".into(), @@ -136,9 +137,11 @@ fn api>>(sync: T) -> RpcModule> { chain_type: Default::default(), }, tx, - sc_rpc_api::DenyUnsafe::No, ) - .into_rpc() + .into_rpc(); + + module.extensions_mut().insert(DenyUnsafe::No); + module } #[tokio::test] diff --git a/substrate/client/rpc/src/testing.rs b/substrate/client/rpc/src/testing.rs index e04f80a7b9e6..990f81ba551e 100644 --- a/substrate/client/rpc/src/testing.rs +++ b/substrate/client/rpc/src/testing.rs @@ -20,6 +20,9 @@ use std::{future::Future, sync::Arc}; +use jsonrpsee::Extensions; +use sc_rpc_api::DenyUnsafe; + /// A task executor that can be used for running RPC tests. /// /// Warning: the tokio runtime must be initialized before calling this. @@ -70,3 +73,17 @@ pub fn test_executor() -> Arc { pub fn timeout_secs>(s: u64, f: F) -> tokio::time::Timeout { tokio::time::timeout(std::time::Duration::from_secs(s), f) } + +/// Helper to create an extension that denies unsafe calls. +pub fn deny_unsafe() -> Extensions { + let mut ext = Extensions::new(); + ext.insert(DenyUnsafe::Yes); + ext +} + +/// Helper to create an extension that allows unsafe calls. +pub fn allow_unsafe() -> Extensions { + let mut ext = Extensions::new(); + ext.insert(DenyUnsafe::No); + ext +} diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index d9a91e715fd5..9a6c3fde37d9 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -361,8 +361,7 @@ pub struct SpawnTasksParams<'a, TBl: BlockT, TCl, TExPool, TRpc, Backend> { /// A shared transaction pool. pub transaction_pool: Arc, /// Builds additional [`RpcModule`]s that should be added to the server - pub rpc_builder: - Box Result, Error>>, + pub rpc_builder: Box Result, Error>>, /// A shared network instance. pub network: Arc, /// A Sender for RPC requests. @@ -491,9 +490,8 @@ where let rpc_id_provider = config.rpc_id_provider.take(); // jsonrpsee RPC - let gen_rpc_module = |deny_unsafe: DenyUnsafe| { + let gen_rpc_module = || { gen_rpc_module( - deny_unsafe, task_manager.spawn_handle(), client.clone(), transaction_pool.clone(), @@ -505,8 +503,14 @@ where ) }; - let rpc = start_rpc_servers(&config, gen_rpc_module, rpc_id_provider)?; - let rpc_handlers = RpcHandlers::new(Arc::new(gen_rpc_module(sc_rpc::DenyUnsafe::No)?.into())); + let rpc_server_handle = start_rpc_servers(&config, gen_rpc_module, rpc_id_provider)?; + let in_memory_rpc = { + let mut module = gen_rpc_module()?; + module.extensions_mut().insert(DenyUnsafe::No); + module + }; + + let in_memory_rpc_handle = RpcHandlers::new(Arc::new(in_memory_rpc)); // Spawn informant task spawn_handle.spawn( @@ -515,9 +519,9 @@ where sc_informant::build(client.clone(), network, sync_service.clone()), ); - task_manager.keep_alive((config.base_path, rpc)); + task_manager.keep_alive((config.base_path, rpc_server_handle)); - Ok(rpc_handlers) + Ok(in_memory_rpc_handle) } /// Returns a future that forwards imported transactions to the transaction networking protocol. @@ -590,7 +594,6 @@ where /// Generate RPC module using provided configuration pub fn gen_rpc_module( - deny_unsafe: DenyUnsafe, spawn_handle: SpawnTaskHandle, client: Arc, transaction_pool: Arc, @@ -598,7 +601,7 @@ pub fn gen_rpc_module( system_rpc_tx: TracingUnboundedSender>, config: &Configuration, backend: Arc, - rpc_builder: &(dyn Fn(DenyUnsafe, SubscriptionTaskExecutor) -> Result, Error>), + rpc_builder: &(dyn Fn(SubscriptionTaskExecutor) -> Result, Error>), ) -> Result, Error> where TBl: BlockT, @@ -633,8 +636,7 @@ where let (chain, state, child_state) = { let chain = sc_rpc::chain::new_full(client.clone(), task_executor.clone()).into_rpc(); - let (state, child_state) = - sc_rpc::state::new_full(client.clone(), task_executor.clone(), deny_unsafe); + let (state, child_state) = sc_rpc::state::new_full(client.clone(), task_executor.clone()); let state = state.into_rpc(); let child_state = child_state.into_rpc(); @@ -698,15 +700,14 @@ where client.clone(), transaction_pool, keystore, - deny_unsafe, task_executor.clone(), ) .into_rpc(); - let system = sc_rpc::system::System::new(system_info, system_rpc_tx, deny_unsafe).into_rpc(); + let system = sc_rpc::system::System::new(system_info, system_rpc_tx).into_rpc(); if let Some(storage) = backend.offchain_storage() { - let offchain = sc_rpc::offchain::Offchain::new(storage, deny_unsafe).into_rpc(); + let offchain = sc_rpc::offchain::Offchain::new(storage).into_rpc(); rpc_api.merge(offchain).map_err(|e| Error::Application(e.into()))?; } @@ -726,7 +727,7 @@ where rpc_api.merge(state).map_err(|e| Error::Application(e.into()))?; rpc_api.merge(child_state).map_err(|e| Error::Application(e.into()))?; // Additional [`RpcModule`]s defined in the node to fit the specific blockchain - let extra_rpcs = rpc_builder(deny_unsafe, task_executor.clone())?; + let extra_rpcs = rpc_builder(task_executor.clone())?; rpc_api.merge(extra_rpcs).map_err(|e| Error::Application(e.into()))?; Ok(rpc_api) diff --git a/substrate/client/service/src/config.rs b/substrate/client/service/src/config.rs index f8d8511f2718..8387f818e68b 100644 --- a/substrate/client/service/src/config.rs +++ b/substrate/client/service/src/config.rs @@ -33,7 +33,9 @@ pub use sc_network::{ }, Multiaddr, }; -pub use sc_rpc_server::IpNetwork; +pub use sc_rpc_server::{ + IpNetwork, RpcEndpoint, RpcMethods, SubscriptionIdProvider as RpcSubscriptionIdProvider, +}; pub use sc_telemetry::TelemetryEndpoints; pub use sc_transaction_pool::Options as TransactionPoolOptions; use sp_core::crypto::SecretString; @@ -82,8 +84,8 @@ pub struct Configuration { /// over on-chain runtimes when the spec version matches. Set to `None` to /// disable overrides (default). pub wasm_runtime_overrides: Option, - /// JSON-RPC server binding address. - pub rpc_addr: Option, + /// JSON-RPC server endpoints. + pub rpc_addr: Option>, /// Maximum number of connections for JSON-RPC server. pub rpc_max_connections: u32, /// CORS settings for HTTP & WS servers. `None` if all origins are allowed. @@ -97,7 +99,7 @@ pub struct Configuration { /// Custom JSON-RPC subscription ID provider. /// /// Default: [`crate::RandomStringSubscriptionId`]. - pub rpc_id_provider: Option>, + pub rpc_id_provider: Option>, /// Maximum allowed subscriptions per rpc connection pub rpc_max_subs_per_conn: u32, /// JSON-RPC server default port. @@ -255,24 +257,6 @@ impl Configuration { } } -/// Available RPC methods. -#[derive(Debug, Copy, Clone)] -pub enum RpcMethods { - /// Expose every RPC method only when RPC is listening on `localhost`, - /// otherwise serve only safe RPC methods. - Auto, - /// Allow only a safe subset of RPC methods. - Safe, - /// Expose every RPC method (even potentially unsafe ones). - Unsafe, -} - -impl Default for RpcMethods { - fn default() -> RpcMethods { - RpcMethods::Auto - } -} - #[static_init::dynamic(drop, lazy)] static mut BASE_PATH_TEMP: Option = None; diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index ed108a3102bc..9e8aed39c897 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -34,7 +34,10 @@ mod client; mod metrics; mod task_manager; -use std::{collections::HashMap, net::SocketAddr}; +use std::{ + collections::HashMap, + net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, +}; use codec::{Decode, Encode}; use futures::{pin_mut, FutureExt, StreamExt}; @@ -85,9 +88,7 @@ pub use sc_executor::NativeExecutionDispatch; pub use sc_network_sync::WarpSyncConfig; #[doc(hidden)] pub use sc_network_transactions::config::{TransactionImport, TransactionImportFuture}; -pub use sc_rpc::{ - RandomIntegerSubscriptionId, RandomStringSubscriptionId, RpcSubscriptionIdProvider, -}; +pub use sc_rpc::{RandomIntegerSubscriptionId, RandomStringSubscriptionId}; pub use sc_tracing::TracingReceiver; pub use sc_transaction_pool::Options as TransactionPoolOptions; pub use sc_transaction_pool_api::{error::IntoPoolError, InPoolTransaction, TransactionPool}; @@ -341,7 +342,7 @@ pub async fn build_system_rpc_future< sc_rpc::system::Request::SyncState(sender) => { use sc_rpc::system::SyncState; - match sync_service.best_seen_block().await { + match sync_service.status().await.map(|status| status.best_seen_block) { Ok(best_seen_block) => { let best_number = client.info().best_number; let _ = sender.send(SyncState { @@ -377,45 +378,64 @@ mod waiting { pub fn start_rpc_servers( config: &Configuration, gen_rpc_module: R, - rpc_id_provider: Option>, + rpc_id_provider: Option>, ) -> Result, error::Error> where - R: Fn(sc_rpc::DenyUnsafe) -> Result, Error>, + R: Fn() -> Result, Error>, { - fn deny_unsafe(addr: SocketAddr, methods: &RpcMethods) -> sc_rpc::DenyUnsafe { - let is_exposed_addr = !addr.ip().is_loopback(); - match (is_exposed_addr, methods) { - | (_, RpcMethods::Unsafe) | (false, RpcMethods::Auto) => sc_rpc::DenyUnsafe::No, - _ => sc_rpc::DenyUnsafe::Yes, - } - } - - // if binding the specified port failed then a random port is assigned by the OS. - let backup_port = |mut addr: SocketAddr| { - addr.set_port(0); - addr + let endpoints: Vec = if let Some(endpoints) = + config.rpc_addr.as_ref() + { + endpoints.clone() + } else { + let ipv6 = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, config.rpc_port, 0, 0)); + let ipv4 = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, config.rpc_port)); + + vec![ + sc_rpc_server::RpcEndpoint { + batch_config: config.rpc_batch_config, + cors: config.rpc_cors.clone(), + listen_addr: ipv4, + max_buffer_capacity_per_connection: config.rpc_message_buffer_capacity, + max_connections: config.rpc_max_connections, + max_payload_in_mb: config.rpc_max_request_size, + max_payload_out_mb: config.rpc_max_response_size, + max_subscriptions_per_connection: config.rpc_max_subs_per_conn, + rpc_methods: config.rpc_methods.into(), + rate_limit: config.rpc_rate_limit, + rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(), + retry_random_port: true, + is_optional: false, + }, + sc_rpc_server::RpcEndpoint { + batch_config: config.rpc_batch_config, + cors: config.rpc_cors.clone(), + listen_addr: ipv6, + max_buffer_capacity_per_connection: config.rpc_message_buffer_capacity, + max_connections: config.rpc_max_connections, + max_payload_in_mb: config.rpc_max_request_size, + max_payload_out_mb: config.rpc_max_response_size, + max_subscriptions_per_connection: config.rpc_max_subs_per_conn, + rpc_methods: config.rpc_methods.into(), + rate_limit: config.rpc_rate_limit, + rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(), + retry_random_port: true, + is_optional: true, + }, + ] }; - let addr = config.rpc_addr.unwrap_or_else(|| ([127, 0, 0, 1], config.rpc_port).into()); - let backup_addr = backup_port(addr); let metrics = sc_rpc_server::RpcMetrics::new(config.prometheus_registry())?; + let rpc_api = gen_rpc_module()?; let server_config = sc_rpc_server::Config { - addrs: [addr, backup_addr], - batch_config: config.rpc_batch_config, - max_connections: config.rpc_max_connections, - max_payload_in_mb: config.rpc_max_request_size, - max_payload_out_mb: config.rpc_max_response_size, - max_subs_per_conn: config.rpc_max_subs_per_conn, - message_buffer_capacity: config.rpc_message_buffer_capacity, - rpc_api: gen_rpc_module(deny_unsafe(addr, &config.rpc_methods))?, + endpoints, + rpc_api, metrics, id_provider: rpc_id_provider, - cors: config.rpc_cors.as_ref(), tokio_handle: config.tokio_handle.clone(), - rate_limit: config.rpc_rate_limit, - rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(), - rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers, }; // TODO: https://github.com/paritytech/substrate/issues/13773 diff --git a/substrate/frame/balances/src/benchmarking.rs b/substrate/frame/balances/src/benchmarking.rs index 5740f8081c07..c825300218d4 100644 --- a/substrate/frame/balances/src/benchmarking.rs +++ b/substrate/frame/balances/src/benchmarking.rs @@ -31,6 +31,14 @@ const SEED: u32 = 0; // existential deposit multiplier const ED_MULTIPLIER: u32 = 10; +fn minimum_balance, I: 'static>() -> T::Balance { + if cfg!(feature = "insecure_zero_ed") { + 100u32.into() + } else { + T::ExistentialDeposit::get() + } +} + #[instance_benchmarks] mod benchmarks { use super::*; @@ -40,7 +48,7 @@ mod benchmarks { // * Transfer will create the recipient account. #[benchmark] fn transfer_allow_death() { - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let caller = whitelisted_caller(); // Give some multiple of the existential deposit @@ -75,7 +83,7 @@ mod benchmarks { as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); // Give the recipient account existential deposit (thus their account already exists). - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let _ = as Currency<_>>::make_free_balance_be(&recipient, existential_deposit); let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); @@ -98,7 +106,7 @@ mod benchmarks { // Give the sender account max funds, thus a transfer will not kill account. let _ = as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); #[extrinsic_call] @@ -115,7 +123,7 @@ mod benchmarks { let user_lookup = T::Lookup::unlookup(user.clone()); // Give the user some initial balance. - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); @@ -132,7 +140,7 @@ mod benchmarks { let user_lookup = T::Lookup::unlookup(user.clone()); // Give the user some initial balance. - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); @@ -147,7 +155,7 @@ mod benchmarks { // * Transfer will create the recipient account. #[benchmark] fn force_transfer() { - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let source: T::AccountId = account("source", 0, SEED); let source_lookup = T::Lookup::unlookup(source.clone()); @@ -175,7 +183,7 @@ mod benchmarks { #[benchmark(extra)] fn transfer_increasing_users(u: Linear<0, 1_000>) { // 1_000 is not very much, but this upper bound can be controlled by the CLI. - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let caller = whitelisted_caller(); // Give some multiple of the existential deposit @@ -214,7 +222,7 @@ mod benchmarks { let recipient_lookup = T::Lookup::unlookup(recipient.clone()); // Give some multiple of the existential deposit - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); let _ = as Currency<_>>::make_free_balance_be(&caller, balance); @@ -231,7 +239,7 @@ mod benchmarks { let user_lookup = T::Lookup::unlookup(user.clone()); // Give some multiple of the existential deposit - let ed = T::ExistentialDeposit::get(); + let ed = minimum_balance::(); let balance = ed + ed; let _ = as Currency<_>>::make_free_balance_be(&user, balance); @@ -257,8 +265,8 @@ mod benchmarks { .map(|i| -> T::AccountId { let user = account("old_user", i, SEED); let account = AccountData { - free: T::ExistentialDeposit::get(), - reserved: T::ExistentialDeposit::get(), + free: minimum_balance::(), + reserved: minimum_balance::(), frozen: Zero::zero(), flags: ExtraFlags::old_logic(), }; diff --git a/substrate/frame/balances/src/tests/fungible_tests.rs b/substrate/frame/balances/src/tests/fungible_tests.rs index e1c1be9b1335..ac373a351d34 100644 --- a/substrate/frame/balances/src/tests/fungible_tests.rs +++ b/substrate/frame/balances/src/tests/fungible_tests.rs @@ -25,7 +25,7 @@ use frame_support::traits::{ Preservation::{Expendable, Preserve, Protect}, Restriction::Free, }, - Consideration, Footprint, LinearStoragePrice, + Consideration, Footprint, LinearStoragePrice, MaybeConsideration, }; use fungible::{ FreezeConsideration, HoldConsideration, Inspect, InspectFreeze, InspectHold, @@ -519,6 +519,7 @@ fn freeze_consideration_works() { // freeze amount taken somewhere outside of our (Consideration) scope. let extend_freeze = 15; let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); + assert!(zero_ticket.is_none()); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); @@ -533,7 +534,9 @@ fn freeze_consideration_works() { let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap(); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 8 + extend_freeze); - assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), zero_ticket); + let ticket = ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(); + assert!(ticket.is_none()); + assert_eq!(ticket, zero_ticket); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0 + extend_freeze); let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); @@ -563,6 +566,7 @@ fn hold_consideration_works() { let extend_hold = 15; let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); + assert!(zero_ticket.is_none()); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); @@ -577,7 +581,9 @@ fn hold_consideration_works() { let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap(); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 8 + extend_hold); - assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), zero_ticket); + let ticket = ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(); + assert!(ticket.is_none()); + assert_eq!(ticket, zero_ticket); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0 + extend_hold); let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); diff --git a/substrate/frame/contracts/fixtures/Cargo.toml b/substrate/frame/contracts/fixtures/Cargo.toml index 6b0751571cc9..6cb6447d8fd7 100644 --- a/substrate/frame/contracts/fixtures/Cargo.toml +++ b/substrate/frame/contracts/fixtures/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] frame-system = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -anyhow = { workspace = true } +anyhow = { workspace = true, default-features = true } [build-dependencies] parity-wasm = { workspace = true } @@ -21,7 +21,7 @@ tempfile = { workspace = true } toml = { workspace = true } twox-hash = { workspace = true, default-features = true } polkavm-linker = { workspace = true, optional = true } -anyhow = { workspace = true } +anyhow = { workspace = true, default-features = true } [features] riscv = ["polkavm-linker"] diff --git a/substrate/frame/examples/multi-block-migrations/src/migrations/v1/tests.rs b/substrate/frame/examples/multi-block-migrations/src/migrations/v1/tests.rs index 838ba29a6212..3d2360d63a73 100644 --- a/substrate/frame/examples/multi-block-migrations/src/migrations/v1/tests.rs +++ b/substrate/frame/examples/multi-block-migrations/src/migrations/v1/tests.rs @@ -27,6 +27,7 @@ use crate::{ System, }, }; +use codec::Decode; use frame_support::traits::OnRuntimeUpgrade; use pallet_migrations::WeightInfo as _; @@ -51,10 +52,18 @@ fn lazy_migration_works() { let mut last_decodable = 0; for block in 2..=65 { run_to_block(block); + let mut decodable = 0; for i in 0..1024 { - if crate::MyMap::::get(i).is_some() { + let key = crate::MyMap::::hashed_key_for(i); + let value = + frame_support::storage::unhashed::get_raw(&key[..]).expect("value exists"); + + if let Ok(value) = u64::decode(&mut &value[..]) { + assert_eq!(value, i as u64); decodable += 1; + } else { + assert_eq!(u32::decode(&mut &value[..]).expect("not migrated yet"), i); } } diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml new file mode 100644 index 000000000000..747c2283e217 --- /dev/null +++ b/substrate/frame/revive/Cargo.toml @@ -0,0 +1,123 @@ +[package] +name = "pallet-revive" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +build = "build.rs" +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "FRAME pallet for PolkaVM contracts." +readme = "README.md" +include = ["CHANGELOG.md", "README.md", "build.rs", "src/**/*"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +paste = { workspace = true } +polkavm = { version = "0.10.0", default-features = false } +bitflags = { workspace = true } +codec = { features = [ + "derive", + "max-encoded-len", +], workspace = true } +scale-info = { features = ["derive"], workspace = true } +log = { workspace = true } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +impl-trait-for-tuples = { workspace = true } + +# Substrate Dependencies +environmental = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-balances = { optional = true, workspace = true } +pallet-revive-fixtures = { workspace = true, default-features = false } +pallet-revive-uapi = { workspace = true, default-features = true } +pallet-revive-proc-macro = { workspace = true, default-features = true } +sp-api = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +xcm = { workspace = true } +xcm-builder = { workspace = true } + +[dev-dependencies] +array-bytes = { workspace = true, default-features = true } +assert_matches = { workspace = true } +pretty_assertions = { workspace = true } +wat = { workspace = true } +pallet-revive-fixtures = { workspace = true, default-features = true } + +# Polkadot Dependencies +xcm-builder = { workspace = true, default-features = true } + +# Substrate Dependencies +pallet-balances = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +pallet-message-queue = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } +pallet-assets = { workspace = true, default-features = true } +pallet-proxy = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } + +[features] +default = ["std"] +# enabling this feature will require having a riscv toolchain installed +# if no tests are ran and runtime benchmarks will not work +# apart from this the pallet will stay functional +riscv = ["pallet-revive-fixtures/riscv"] +std = [ + "codec/std", + "environmental/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances?/std", + "pallet-proxy/std", + "pallet-revive-fixtures/std", + "pallet-timestamp/std", + "pallet-utility/std", + "polkavm/std", + "scale-info/std", + "serde", + "sp-api/std", + "sp-core/std", + "sp-io/std", + "sp-keystore/std", + "sp-runtime/std", + "sp-std/std", + "xcm-builder/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-utility/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime", + "pallet-message-queue/try-runtime", + "pallet-proxy/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-utility/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/revive/README.md b/substrate/frame/revive/README.md new file mode 100644 index 000000000000..5352e636c252 --- /dev/null +++ b/substrate/frame/revive/README.md @@ -0,0 +1,104 @@ +# Revive Pallet + +This is an **experimental** module that provides functionality for the runtime to deploy and execute PolkaVM +smart-contracts. It is a heavily modified `pallet_contracts` fork. + +## Overview + +This module extends accounts based on the [`frame_support::traits::fungible`] traits to have smart-contract +functionality. It can be used with other modules that implement accounts based on [`frame_support::traits::fungible`]. +These "smart-contract accounts" have the ability to instantiate smart-contracts and make calls to other contract and +non-contract accounts. + +The smart-contract code is stored once, and later retrievable via its `code_hash`. This means that multiple +smart-contracts can be instantiated from the same `code`, without replicating the code each time. + +When a smart-contract is called, its associated code is retrieved via the code hash and gets executed. This call can +alter the storage entries of the smart-contract account, instantiate new smart-contracts, or call other smart-contracts. + +Finally, when an account is reaped, its associated code and storage of the smart-contract account will also be deleted. + +### Weight + +Senders must specify a [`Weight`](https://paritytech.github.io/substrate/master/sp_weights/struct.Weight.html) limit +with every call, as all instructions invoked by the smart-contract require weight. Unused weight is refunded after the +call, regardless of the execution outcome. + +If the weight limit is reached, then all calls and state changes (including balance transfers) are only reverted at the +current call's contract level. For example, if contract A calls B and B runs out of weight mid-call, then all of B's +calls are reverted. Assuming correct error handling by contract A, A's other calls and state changes still persist. + +One `ref_time` `Weight` is defined as one picosecond of execution time on the runtime's reference machine. + +### Revert Behaviour + +Contract call failures are not cascading. When failures occur in a sub-call, they do not "bubble up", and the call will +only revert at the specific contract level. For example, if contract A calls contract B, and B fails, A can decide how +to handle that failure, either proceeding or reverting A's changes. + +## Interface + +### Dispatchable functions + +Those are documented in the [reference +documentation](https://paritytech.github.io/substrate/master/pallet_revive/index.html#dispatchable-functions). + +## Usage + +This module executes PolkaVM smart contracts. These can potentially be written in any language that compiles to +RISC-V. For now, the only officially supported languages are Solidity (via [`revive`](https://github.com/xermicus/revive)) +and Rust (check the `fixtures` directory for Rust examples). + +## Debugging + +Contracts can emit messages to the client when called as RPC through the +[`debug_message`](https://paritytech.github.io/substrate/master/pallet_revive/trait.SyscallDocs.html#tymethod.debug_message) +API. + +Those messages are gathered into an internal buffer and sent to the RPC client. It is up to the individual client if +and how those messages are presented to the user. + +This buffer is also printed as a debug message. In order to see these messages on the node console the log level for the +`runtime::revive` target needs to be raised to at least the `debug` level. However, those messages are easy to +overlook because of the noise generated by block production. A good starting point for observing them on the console is +using this command line in the root directory of the Substrate repository: + +```bash +cargo run --release -- --dev -lerror,runtime::revive=debug +``` + +This raises the log level of `runtime::revive` to `debug` and all other targets to `error` in order to prevent them +from spamming the console. + +`--dev`: Use a dev chain spec `--tmp`: Use temporary storage for chain data (the chain state is deleted on exit) + +## Host function tracing + +For contract authors, it can be a helpful debugging tool to see which host functions are called, with which arguments, +and what the result was. + +In order to see these messages on the node console, the log level for the `runtime::revive::strace` target needs to +be raised to the `trace` level. + +Example: + +```bash +cargo run --release -- --dev -lerror,runtime::revive::strace=trace,runtime::revive=debug +``` + +## Unstable Interfaces + +Driven by the desire to have an iterative approach in developing new contract interfaces this pallet contains the +concept of an unstable interface. Akin to the rust nightly compiler it allows us to add new interfaces but mark them as +unstable so that contract languages can experiment with them and give feedback before we stabilize those. + +In order to access interfaces which don't have a stable `#[api_version(x)]` in [`runtime.rs`](src/wasm/runtime.rs) +one need to set `pallet_revive::Config::UnsafeUnstableInterface` to `ConstU32`. +**It should be obvious that any production runtime should never be compiled with this feature: In addition to be +subject to change or removal those interfaces might not have proper weights associated with them and are therefore +considered unsafe**. + +New interfaces are generally added as unstable and might go through several iterations before they are promoted to a +stable interface. + +License: Apache-2.0 diff --git a/substrate/frame/revive/build.rs b/substrate/frame/revive/build.rs new file mode 100644 index 000000000000..ca8e62df6047 --- /dev/null +++ b/substrate/frame/revive/build.rs @@ -0,0 +1,78 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::io::Write; + +/// We start with version 2 instead of 0 when adding the pallet. +/// +/// Because otherwise we can't test any migrations since they require the storage version +/// to be lower than the pallet version in order to be triggerd. With the pallet version +/// at the minimum (0) this would not work. +const LOWEST_STORAGE_VERSION: u16 = 2; + +/// Get the latest migration version. +/// +/// Find the highest version number from the available migration files. +/// Each migration file should follow the naming convention `vXX.rs`, where `XX` is the version +/// number. +fn get_latest_version() -> u16 { + let Ok(dir) = std::fs::read_dir("src/migration") else { return LOWEST_STORAGE_VERSION }; + dir.filter_map(|entry| { + let file_name = entry.as_ref().ok()?.file_name(); + let file_name = file_name.to_str()?; + if file_name.starts_with('v') && file_name.ends_with(".rs") { + let version = &file_name[1..&file_name.len() - 3]; + let version = version.parse::().ok()?; + + // Ensure that the version matches the one defined in the file. + let path = entry.unwrap().path(); + let file_content = std::fs::read_to_string(&path).ok()?; + assert!( + file_content.contains(&format!("const VERSION: u16 = {}", version)), + "Invalid MigrationStep::VERSION in {:?}", + path + ); + + return Some(version) + } + None + }) + .max() + .unwrap_or(LOWEST_STORAGE_VERSION) +} + +/// Generates a module that exposes the latest migration version, and the benchmark migrations type. +fn main() -> Result<(), Box> { + let out_dir = std::env::var("OUT_DIR")?; + let path = std::path::Path::new(&out_dir).join("migration_codegen.rs"); + let mut f = std::fs::File::create(path)?; + let version = get_latest_version(); + write!( + f, + " + pub mod codegen {{ + use crate::NoopMigration; + /// The latest migration version, pulled from the latest migration file. + pub const LATEST_MIGRATION_VERSION: u16 = {version}; + /// The Migration Steps used for benchmarking the migration framework. + pub type BenchMigrations = (NoopMigration<{}>, NoopMigration<{version}>); + }}", + version - 1, + )?; + + Ok(()) +} diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml new file mode 100644 index 000000000000..9e54acdace70 --- /dev/null +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "pallet-revive-fixtures" +publish = true +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +description = "Fixtures for testing and benchmarking" + +[lints] +workspace = true + +[dependencies] +frame-system = { workspace = true, default-features = true, optional = true } +sp-runtime = { workspace = true, default-features = true, optional = true } +anyhow = { workspace = true } + +[build-dependencies] +parity-wasm = { workspace = true } +tempfile = { workspace = true } +toml = { workspace = true } +polkavm-linker = { version = "0.10.0" } +anyhow = { workspace = true } + +[features] +default = ["std"] +# only if the feature is set we are building the test fixtures +# this is because it requires a custom toolchain supporting polkavm +# we will remove this once there is an upstream toolchain +riscv = [] +# only when std is enabled all fixtures are available +std = [ + "anyhow/std", + "frame-system", + "sp-runtime", +] diff --git a/substrate/frame/revive/fixtures/build.rs b/substrate/frame/revive/fixtures/build.rs new file mode 100644 index 000000000000..ed981a75467b --- /dev/null +++ b/substrate/frame/revive/fixtures/build.rs @@ -0,0 +1,214 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Compile text fixtures to PolkaVM binaries. +use anyhow::Result; + +fn main() -> Result<()> { + build::run() +} + +#[cfg(feature = "riscv")] +mod build { + use super::Result; + use anyhow::{bail, Context}; + use std::{ + cfg, env, fs, + path::{Path, PathBuf}, + process::Command, + }; + + /// A contract entry. + struct Entry { + /// The path to the contract source file. + path: PathBuf, + } + + impl Entry { + /// Create a new contract entry from the given path. + fn new(path: PathBuf) -> Self { + Self { path } + } + + /// Return the path to the contract source file. + fn path(&self) -> &str { + self.path.to_str().expect("path is valid unicode; qed") + } + + /// Return the name of the contract. + fn name(&self) -> &str { + self.path + .file_stem() + .expect("file exits; qed") + .to_str() + .expect("name is valid unicode; qed") + } + + /// Return the name of the polkavm file. + fn out_filename(&self) -> String { + format!("{}.polkavm", self.name()) + } + } + + /// Collect all contract entries from the given source directory. + /// Contracts that have already been compiled are filtered out. + fn collect_entries(contracts_dir: &Path) -> Vec { + fs::read_dir(contracts_dir) + .expect("src dir exists; qed") + .filter_map(|file| { + let path = file.expect("file exists; qed").path(); + if path.extension().map_or(true, |ext| ext != "rs") { + return None + } + + Some(Entry::new(path)) + }) + .collect::>() + } + + /// Create a `Cargo.toml` to compile the given contract entries. + fn create_cargo_toml<'a>( + fixtures_dir: &Path, + entries: impl Iterator, + output_dir: &Path, + ) -> Result<()> { + let mut cargo_toml: toml::Value = toml::from_str(include_str!("./build/Cargo.toml"))?; + let mut set_dep = |name, path| -> Result<()> { + cargo_toml["dependencies"][name]["path"] = toml::Value::String( + fixtures_dir.join(path).canonicalize()?.to_str().unwrap().to_string(), + ); + Ok(()) + }; + set_dep("uapi", "../uapi")?; + set_dep("common", "./contracts/common")?; + + cargo_toml["bin"] = toml::Value::Array( + entries + .map(|entry| { + let name = entry.name(); + let path = entry.path(); + toml::Value::Table(toml::toml! { + name = name + path = path + }) + }) + .collect::>(), + ); + + let cargo_toml = toml::to_string_pretty(&cargo_toml)?; + fs::write(output_dir.join("Cargo.toml"), cargo_toml).map_err(Into::into) + } + + fn invoke_build(current_dir: &Path) -> Result<()> { + let encoded_rustflags = [ + "-Crelocation-model=pie", + "-Clink-arg=--emit-relocs", + "-Clink-arg=--export-dynamic-symbol=__polkavm_symbol_export_hack__*", + ] + .join("\x1f"); + + let build_res = Command::new(env::var("CARGO")?) + .current_dir(current_dir) + .env_clear() + .env("PATH", env::var("PATH").unwrap_or_default()) + .env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags) + .env("RUSTUP_TOOLCHAIN", "rve-nightly") + .env("RUSTC_BOOTSTRAP", "1") + .env("RUSTUP_HOME", env::var("RUSTUP_HOME").unwrap_or_default()) + .args([ + "build", + "--release", + "--target=riscv32ema-unknown-none-elf", + "-Zbuild-std=core", + "-Zbuild-std-features=panic_immediate_abort", + ]) + .output() + .expect("failed to execute process"); + + if build_res.status.success() { + return Ok(()) + } + + let stderr = String::from_utf8_lossy(&build_res.stderr); + + if stderr.contains("'rve-nightly' is not installed") { + eprintln!("RISC-V toolchain is not installed.\nDownload and install toolchain from https://github.com/paritytech/rustc-rv32e-toolchain."); + eprintln!("{}", stderr); + } else { + eprintln!("{}", stderr); + } + + bail!("Failed to build contracts"); + } + + /// Post-process the compiled code. + fn post_process(input_path: &Path, output_path: &Path) -> Result<()> { + let mut config = polkavm_linker::Config::default(); + config.set_strip(true); + let orig = + fs::read(input_path).with_context(|| format!("Failed to read {:?}", input_path))?; + let linked = polkavm_linker::program_from_elf(config, orig.as_ref()) + .map_err(|err| anyhow::format_err!("Failed to link polkavm program: {}", err))?; + fs::write(output_path, linked).map_err(Into::into) + } + + /// Write the compiled contracts to the given output directory. + fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec) -> Result<()> { + for entry in entries { + post_process( + &build_dir.join("target/riscv32ema-unknown-none-elf/release").join(entry.name()), + &out_dir.join(entry.out_filename()), + )?; + } + + Ok(()) + } + + pub fn run() -> Result<()> { + let fixtures_dir: PathBuf = env::var("CARGO_MANIFEST_DIR")?.into(); + let contracts_dir = fixtures_dir.join("contracts"); + let uapi_dir = fixtures_dir.parent().expect("uapi dir exits; qed").join("uapi"); + let out_dir: PathBuf = env::var("OUT_DIR")?.into(); + + // the fixtures have a dependency on the uapi crate + println!("cargo::rerun-if-changed={}", fixtures_dir.display()); + println!("cargo::rerun-if-changed={}", uapi_dir.display()); + + let entries = collect_entries(&contracts_dir); + if entries.is_empty() { + return Ok(()) + } + + let tmp_dir = tempfile::tempdir()?; + let tmp_dir_path = tmp_dir.path(); + + create_cargo_toml(&fixtures_dir, entries.iter(), tmp_dir.path())?; + invoke_build(tmp_dir_path)?; + + write_output(tmp_dir_path, &out_dir, entries)?; + Ok(()) + } +} + +#[cfg(not(feature = "riscv"))] +mod build { + use super::Result; + + pub fn run() -> Result<()> { + Ok(()) + } +} diff --git a/substrate/frame/revive/fixtures/build/Cargo.toml b/substrate/frame/revive/fixtures/build/Cargo.toml new file mode 100644 index 000000000000..7dead51b2306 --- /dev/null +++ b/substrate/frame/revive/fixtures/build/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "contracts" +publish = false +version = "1.0.0" +edition = "2021" + +# Binary targets are injected dynamically by the build script. +[[bin]] + +# All paths are injected dynamically by the build script. +[dependencies] +uapi = { package = 'pallet-revive-uapi', path = "", default-features = false } +common = { package = 'pallet-revive-fixtures-common', path = "" } +polkavm-derive = { version = "0.10.0" } + +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 diff --git a/substrate/frame/revive/fixtures/contracts/balance.rs b/substrate/frame/revive/fixtures/contracts/balance.rs new file mode 100644 index 000000000000..4011b8379cbf --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/balance.rs @@ -0,0 +1,36 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::output; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + // Initialize buffer with 1s so that we can check that it is overwritten. + output!(balance, [1u8; 8], api::balance,); + + // Assert that the balance is 0. + assert_eq!(&[0u8; 8], balance); +} diff --git a/substrate/frame/revive/fixtures/contracts/call.rs b/substrate/frame/revive/fixtures/contracts/call.rs new file mode 100644 index 000000000000..a75aee65c205 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call.rs @@ -0,0 +1,49 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This calls another contract as passed as its account id. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + callee_input: [u8; 4], + callee_addr: [u8; 32], + ); + + // Call the callee + api::call( + uapi::CallFlags::empty(), + callee_addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. + callee_input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_return_code.rs b/substrate/frame/revive/fixtures/contracts/call_return_code.rs new file mode 100644 index 000000000000..32654d59ef1f --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_return_code.rs @@ -0,0 +1,56 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This calls the supplied dest and transfers 100 balance during this call and copies +//! the return code of this call to the output buffer. +//! It also forwards its input to the callee. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 100, + callee_addr: [u8; 32], + input: [u8], + ); + + // Call the callee + let err_code = match api::call( + uapi::CallFlags::empty(), + callee_addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &100u64.to_le_bytes(), // Value transferred to the contract. + input, + None, + ) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_runtime.rs b/substrate/frame/revive/fixtures/contracts/call_runtime.rs new file mode 100644 index 000000000000..2b132398fb68 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_runtime.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This passes its input to `call_runtime` and returns the return value to its caller. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + // Fixture calls should fit into 100 bytes. + input!(100, call: [u8], ); + + // Use the call passed as input to call the runtime. + let err_code = match api::call_runtime(call) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs new file mode 100644 index 000000000000..1323c8c5d55d --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 512, + callee_input: [u8; 4], + callee_addr: [u8; 32], + call: [u8], + ); + + // Use the call passed as input to call the runtime. + api::call_runtime(call).unwrap(); + + // Call the callee + api::call( + uapi::CallFlags::empty(), + callee_addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. + callee_input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs new file mode 100644 index 000000000000..a078162d7995 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs @@ -0,0 +1,51 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This fixture calls the account_id with the flags and value. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 256, + callee_addr: [u8; 32], + flags: u32, + value: u64, + forwarded_input: [u8], + ); + + api::call( + uapi::CallFlags::from_bits(flags).unwrap(), + callee_addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &value.to_le_bytes(), // Value transferred to the contract. + forwarded_input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_with_limit.rs b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs new file mode 100644 index 000000000000..a5356924f24f --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This fixture calls the account_id with the 2D Weight limit. +//! It returns the result of the call as output data. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 256, + callee_addr: [u8; 32], + ref_time: u64, + proof_size: u64, + forwarded_input: [u8], + ); + + api::call( + uapi::CallFlags::empty(), + callee_addr, + ref_time, + proof_size, + None, // No deposit limit. + &0u64.to_le_bytes(), // value transferred to the contract. + forwarded_input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/caller_contract.rs b/substrate/frame/revive/fixtures/contracts/caller_contract.rs new file mode 100644 index 000000000000..2fa11df82d04 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/caller_contract.rs @@ -0,0 +1,145 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(code_hash: [u8; 32],); + + // The value to transfer on instantiation and calls. Chosen to be greater than existential + // deposit. + let value = 32768u64.to_le_bytes(); + let salt = [0u8; 0]; + + // Callee will use the first 4 bytes of the input to return an exit status. + let input = [0u8, 1, 34, 51, 68, 85, 102, 119]; + let reverted_input = [1u8, 34, 51, 68, 85, 102, 119]; + + // Fail to deploy the contract since it returns a non-zero exit status. + let res = api::instantiate( + code_hash, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &value, + &reverted_input, + None, + None, + &salt, + ); + assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted))); + + // Fail to deploy the contract due to insufficient ref_time weight. + let res = api::instantiate( + code_hash, 1u64, // too little ref_time weight + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &value, &input, None, None, &salt, + ); + assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + + // Fail to deploy the contract due to insufficient proof_size weight. + let res = api::instantiate( + code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 1u64, // Too little proof_size weight + None, // No deposit limit. + &value, &input, None, None, &salt, + ); + assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + + // Deploy the contract successfully. + let mut callee = [0u8; 32]; + let callee = &mut &mut callee[..]; + + api::instantiate( + code_hash, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &value, + &input, + Some(callee), + None, + &salt, + ) + .unwrap(); + assert_eq!(callee.len(), 32); + + // Call the new contract and expect it to return failing exit code. + let res = api::call( + uapi::CallFlags::empty(), + callee, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &value, + &reverted_input, + None, + ); + assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted))); + + // Fail to call the contract due to insufficient ref_time weight. + let res = api::call( + uapi::CallFlags::empty(), + callee, + 1u64, // Too little ref_time weight. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &value, + &input, + None, + ); + assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + + // Fail to call the contract due to insufficient proof_size weight. + let res = api::call( + uapi::CallFlags::empty(), + callee, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 1u64, // too little proof_size weight + None, // No deposit limit. + &value, + &input, + None, + ); + assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + + // Call the contract successfully. + let mut output = [0u8; 4]; + api::call( + uapi::CallFlags::empty(), + callee, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &value, + &input, + Some(&mut &mut output[..]), + ) + .unwrap(); + assert_eq!(&output, &input[4..]) +} diff --git a/substrate/frame/revive/fixtures/contracts/caller_is_origin_n.rs b/substrate/frame/revive/fixtures/contracts/caller_is_origin_n.rs new file mode 100644 index 000000000000..fd6f59802fa0 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/caller_is_origin_n.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This fixture calls caller_is_origin `n` times. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(n: u32, ); + + for _ in 0..n { + let _ = api::caller_is_origin(); + } +} diff --git a/substrate/frame/revive/fixtures/contracts/chain_extension.rs b/substrate/frame/revive/fixtures/contracts/chain_extension.rs new file mode 100644 index 000000000000..474df00d6912 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/chain_extension.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Call chain extension by passing through input and output of this contract. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(input, 8, func_id: u32,); + + // the chain extension passes through the input and returns it as output + let mut output_buffer = [0u8; 32]; + let output = &mut &mut output_buffer[0..input.len()]; + + let ret_id = api::call_chain_extension(func_id, input, Some(output)); + assert_eq!(ret_id, func_id); + + api::return_value(uapi::ReturnFlags::empty(), output); +} diff --git a/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs new file mode 100644 index 000000000000..c7596e44dab3 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs @@ -0,0 +1,65 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Call chain extension two times with the specified func_ids +//! It then calls itself once +#![no_std] +#![no_main] + +use common::{input, output}; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + input, + func_id1: u32, + func_id2: u32, + stop_recurse: u8, + ); + + api::call_chain_extension(func_id1, input, None); + api::call_chain_extension(func_id2, input, None); + + if stop_recurse == 0 { + // Setup next call + input[0..4].copy_from_slice(&((3 << 16) | 2u32).to_le_bytes()); + input[4..8].copy_from_slice(&((3 << 16) | 3u32).to_le_bytes()); + input[8] = 1u8; + + // Read the contract address. + output!(addr, [0u8; 32], api::address,); + + // call self + api::call( + uapi::CallFlags::ALLOW_REENTRY, + addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. + input, + None, + ) + .unwrap(); + } +} diff --git a/substrate/frame/revive/fixtures/contracts/common/Cargo.toml b/substrate/frame/revive/fixtures/contracts/common/Cargo.toml new file mode 100644 index 000000000000..7dadb7b821a2 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/common/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "pallet-revive-fixtures-common" +publish = false +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +description = "Common utilities for pallet-revive-fixtures." + +[dependencies] +uapi = { package = 'pallet-revive-uapi', path = "../../../uapi", default-features = false } diff --git a/substrate/frame/revive/fixtures/contracts/common/src/lib.rs b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs new file mode 100644 index 000000000000..6631af8292fe --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs @@ -0,0 +1,157 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] + +pub use uapi::{HostFn, HostFnImpl as api}; + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + // Safety: The unimp instruction is guaranteed to trap + unsafe { + core::arch::asm!("unimp"); + core::hint::unreachable_unchecked(); + } +} + +/// Utility macro to read input passed to a contract. +/// +/// Example: +/// +/// ``` +/// input$!( +/// var1: u32, // [0, 4) var1 decoded as u32 +/// var2: [u8; 32], // [4, 36) var2 decoded as a [u8] slice +/// var3: u8, // [36, 37) var3 decoded as a u8 +/// ); +/// +/// // Input and size can be specified as well: +/// input$!( +/// input, // input buffer (optional) +/// 512, // input size (optional) +/// var4: u32, // [0, 4) var4 decoded as u32 +/// var5: [u8], // [4, ..) var5 decoded as a [u8] slice +/// ); +/// ``` +#[macro_export] +macro_rules! input { + (@inner $input:expr, $cursor:expr,) => {}; + (@size $size:expr, ) => { $size }; + + // Match a u8 variable. + // e.g input!(var1: u8, ); + (@inner $input:expr, $cursor:expr, $var:ident: u8, $($rest:tt)*) => { + let $var = $input[$cursor]; + input!(@inner $input, $cursor + 1, $($rest)*); + }; + + // Size of u8 variable. + (@size $size:expr, $var:ident: u8, $($rest:tt)*) => { + input!(@size $size + 1, $($rest)*) + }; + + // Match a u64 variable. + // e.g input!(var1: u64, ); + (@inner $input:expr, $cursor:expr, $var:ident: u64, $($rest:tt)*) => { + let $var = u64::from_le_bytes($input[$cursor..$cursor + 8].try_into().unwrap()); + input!(@inner $input, $cursor + 8, $($rest)*); + }; + + // Size of u64 variable. + (@size $size:expr, $var:ident: u64, $($rest:tt)*) => { + input!(@size $size + 8, $($rest)*) + }; + + // Match a u32 variable. + // e.g input!(var1: u32, ); + (@inner $input:expr, $cursor:expr, $var:ident: u32, $($rest:tt)*) => { + let $var = u32::from_le_bytes($input[$cursor..$cursor + 4].try_into().unwrap()); + input!(@inner $input, $cursor + 4, $($rest)*); + }; + + // Size of u32 variable. + (@size $size:expr, $var:ident: u32, $($rest:tt)*) => { + input!(@size $size + 4, $($rest)*) + }; + + // Match a u8 slice with the remaining bytes. + // e.g input!(512, var1: [u8; 32], var2: [u8], ); + (@inner $input:expr, $cursor:expr, $var:ident: [u8],) => { + let $var = &$input[$cursor..]; + }; + + // Match a u8 slice of the given size. + // e.g input!(var1: [u8; 32], ); + (@inner $input:expr, $cursor:expr, $var:ident: [u8; $n:expr], $($rest:tt)*) => { + let $var = &$input[$cursor..$cursor+$n]; + input!(@inner $input, $cursor + $n, $($rest)*); + }; + + // Size of a u8 slice. + (@size $size:expr, $var:ident: [u8; $n:expr], $($rest:tt)*) => { + input!(@size $size + $n, $($rest)*) + }; + + // Entry point, with the buffer and it's size specified first. + // e.g input!(buffer, 512, var1: u32, var2: [u8], ); + ($buffer:ident, $size:expr, $($rest:tt)*) => { + let mut $buffer = [0u8; $size]; + let $buffer = &mut &mut $buffer[..]; + $crate::api::input($buffer); + input!(@inner $buffer, 0, $($rest)*); + }; + + // Entry point, with the name of the buffer specified and size of the input buffer computed. + // e.g input!(buffer, var1: u32, var2: u64, ); + ($buffer: ident, $($rest:tt)*) => { + input!($buffer, input!(@size 0, $($rest)*), $($rest)*); + }; + + // Entry point, with the size of the input buffer computed. + // e.g input!(var1: u32, var2: u64, ); + ($($rest:tt)*) => { + input!(buffer, $($rest)*); + }; +} + +/// Utility macro to invoke a host function that expect a `output: &mut &mut [u8]` as last argument. +/// +/// Example: +/// ``` +/// // call `api::caller` and store the output in `caller` +/// output!(caller, [0u8; 32], api::caller,); +/// +/// // call `api::get_storage` and store the output in `address` +/// output!(address, [0u8; 32], api::get_storage, &[1u8; 32]); +/// ``` +#[macro_export] +macro_rules! output { + ($output: ident, $buffer: expr, $host_fn:path, $($arg:expr),*) => { + let mut $output = $buffer; + let $output = &mut &mut $output[..]; + $host_fn($($arg,)* $output); + }; +} + +/// Similar to `output!` but unwraps the result. +#[macro_export] +macro_rules! unwrap_output { + ($output: ident, $buffer: expr, $host_fn:path, $($arg:expr),*) => { + let mut $output = $buffer; + let $output = &mut &mut $output[..]; + $host_fn($($arg,)* $output).unwrap(); + }; +} diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs new file mode 100644 index 000000000000..d0d3651dfe4d --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs @@ -0,0 +1,58 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This calls another contract as passed as its account id. It also creates some storage. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + buffer, + input: [u8; 4], + callee: [u8; 32], + deposit_limit: [u8; 8], + ); + + // create 4 byte of storage before calling + api::set_storage(StorageFlags::empty(), buffer, &[1u8; 4]); + + // Call the callee + api::call( + uapi::CallFlags::empty(), + callee, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + Some(deposit_limit), + &0u64.to_le_bytes(), // Value transferred to the contract. + input, + None, + ) + .unwrap(); + + // create 8 byte of storage after calling + // item of 12 bytes because we override 4 bytes + api::set_storage(StorageFlags::empty(), buffer, &[1u8; 12]); +} diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs new file mode 100644 index 000000000000..918a4abe6b21 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs @@ -0,0 +1,58 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This instantiates another contract and passes some input to its constructor. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + input: [u8; 4], + code_hash: [u8; 32], + deposit_limit: [u8; 8], + ); + + let value = 10_000u64.to_le_bytes(); + let salt = [0u8; 0]; + let mut address = [0u8; 32]; + let address = &mut &mut address[..]; + + api::instantiate( + code_hash, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + Some(deposit_limit), + &value, + input, + Some(address), + None, + &salt, + ) + .unwrap(); + + // Return the deployed contract address. + api::return_value(uapi::ReturnFlags::empty(), address); +} diff --git a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs new file mode 100644 index 000000000000..a2a0e85bcf64 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs @@ -0,0 +1,60 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This calls another contract as passed as its account id. It also creates some transient storage. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static BUFFER: [u8; 512] = [0u8; 512]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + buffer, + len: u32, + input: [u8; 4], + callee: [u8; 32], + ); + + let rounds = len as usize / BUFFER.len(); + let rest = len as usize / BUFFER.len(); + for i in 0..rounds { + api::set_storage(StorageFlags::TRANSIENT, &i.to_le_bytes(), &BUFFER); + } + api::set_storage(StorageFlags::TRANSIENT, &u32::MAX.to_le_bytes(), &BUFFER[..rest]); + + // Call the callee + api::call( + uapi::CallFlags::empty(), + callee, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, + &0u64.to_le_bytes(), // Value transferred to the contract. + input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/crypto_hashes.rs b/substrate/frame/revive/fixtures/contracts/crypto_hashes.rs new file mode 100644 index 000000000000..35cc03f1e723 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/crypto_hashes.rs @@ -0,0 +1,84 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +/// Called by the tests. +/// +/// The `call` function expects data in a certain format in the input buffer. +/// +/// 1. The first byte encodes an identifier for the crypto hash function under test. (*) +/// 2. The rest encodes the input data that is directly fed into the crypto hash function chosen in +/// 1. +/// +/// The `deploy` function then computes the chosen crypto hash function +/// given the input and puts the result into the output buffer. +/// After contract execution the test driver then asserts that the returned +/// values are equal to the expected bytes for the input and chosen hash +/// function. +/// +/// (*) The possible value for the crypto hash identifiers can be found below: +/// +/// | value | Algorithm | Bit Width | +/// |-------|-----------|-----------| +/// | 0 | SHA2 | 256 | +/// | 1 | KECCAK | 256 | +/// | 2 | BLAKE2 | 256 | +/// | 3 | BLAKE2 | 128 | +/// --------------------------------- + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 256, + chosen_hash_fn: u8, + input: [u8], + ); + + match chosen_hash_fn { + 1 => { + let mut output = [0u8; 32]; + api::hash_sha2_256(input, &mut output); + api::return_value(uapi::ReturnFlags::empty(), &output); + }, + 2 => { + let mut output = [0u8; 32]; + api::hash_keccak_256(input, &mut output); + api::return_value(uapi::ReturnFlags::empty(), &output); + }, + 3 => { + let mut output = [0u8; 32]; + api::hash_blake2_256(input, &mut output); + api::return_value(uapi::ReturnFlags::empty(), &output); + }, + 4 => { + let mut output = [0u8; 16]; + api::hash_blake2_128(input, &mut output); + api::return_value(uapi::ReturnFlags::empty(), &output); + }, + _ => panic!("unknown crypto hash function identifier"), + } +} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs b/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs new file mode 100644 index 000000000000..6c850a9ec663 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs @@ -0,0 +1,33 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Emit a debug message with an invalid utf-8 code. +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + api::debug_message(b"\xFC").unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs b/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs new file mode 100644 index 000000000000..0ce2b6b5628d --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs @@ -0,0 +1,33 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Emit a "Hello World!" debug message but assume that logging is disabled. +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + assert_eq!(api::debug_message(b"Hello World!"), Err(ReturnErrorCode::LoggingDisabled)); +} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_works.rs b/substrate/frame/revive/fixtures/contracts/debug_message_works.rs new file mode 100644 index 000000000000..3a2509509d8f --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/debug_message_works.rs @@ -0,0 +1,33 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Emit a "Hello World!" debug message. +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + api::debug_message(b"Hello World!").unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call.rs b/substrate/frame/revive/fixtures/contracts/delegate_call.rs new file mode 100644 index 000000000000..d03ddab1bc58 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/delegate_call.rs @@ -0,0 +1,49 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(code_hash: [u8; 32],); + + let mut key = [0u8; 32]; + key[0] = 1u8; + + let mut value = [0u8; 32]; + let value = &mut &mut value[..]; + value[0] = 2u8; + + api::set_storage(StorageFlags::empty(), &key, value); + api::get_storage(StorageFlags::empty(), &key, value).unwrap(); + assert!(value[0] == 2u8); + + let input = [0u8; 0]; + api::delegate_call(uapi::CallFlags::empty(), code_hash, &input, None).unwrap(); + + api::get_storage(StorageFlags::empty(), &key, value).unwrap(); + assert!(value[0] == 1u8); +} diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs new file mode 100644 index 000000000000..055760729bd2 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs @@ -0,0 +1,49 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::output; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let mut key = [0u8; 32]; + key[0] = 1u8; + + // Place a value in storage. + let mut value = [0u8; 32]; + let value = &mut &mut value[..]; + value[0] = 1u8; + api::set_storage(StorageFlags::empty(), &key, value); + + // Assert that `value_transferred` is equal to the value + // passed to the `caller` contract: 1337. + output!(value_transferred, [0u8; 8], api::value_transferred,); + let value_transferred = u64::from_le_bytes(value_transferred[..].try_into().unwrap()); + assert_eq!(value_transferred, 1337); + + // Assert that ALICE is the caller of the contract. + output!(caller, [0u8; 32], api::caller,); + assert_eq!(&caller[..], &[1u8; 32]); +} diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs new file mode 100644 index 000000000000..cf3351c52fdc --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs @@ -0,0 +1,36 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(code_hash: [u8; 32],); + + // Delegate call into passed code hash. + let input = [0u8; 0]; + api::delegate_call(uapi::CallFlags::empty(), code_hash, &input, None).unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs new file mode 100644 index 000000000000..8b74493a1e38 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs @@ -0,0 +1,86 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +const ADDRESS_KEY: [u8; 32] = [0u8; 32]; +const VALUE: [u8; 8] = [0, 0, 1u8, 0, 0, 0, 0, 0]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + input!(code_hash: [u8; 32],); + + let input = [0u8; 0]; + let mut address = [0u8; 32]; + let address = &mut &mut address[..]; + let salt = [71u8, 17u8]; + + api::instantiate( + code_hash, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &VALUE, + &input, + Some(address), + None, + &salt, + ) + .unwrap(); + + // Return the deployed contract address. + api::set_storage(StorageFlags::empty(), &ADDRESS_KEY, address); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let mut callee_addr = [0u8; 32]; + let callee_addr = &mut &mut callee_addr[..]; + api::get_storage(StorageFlags::empty(), &ADDRESS_KEY, callee_addr).unwrap(); + + // Calling the destination contract with non-empty input data should fail. + let res = api::call( + uapi::CallFlags::empty(), + callee_addr, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &VALUE, + &[0u8; 1], + None, + ); + assert!(matches!(res, Err(uapi::ReturnErrorCode::CalleeTrapped))); + + // Call the destination contract regularly, forcing it to self-destruct. + api::call( + uapi::CallFlags::empty(), + callee_addr, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &VALUE, + &[0u8; 0], + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/drain.rs b/substrate/frame/revive/fixtures/contracts/drain.rs new file mode 100644 index 000000000000..f5c8681c9382 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/drain.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::output; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + output!(balance, [0u8; 8], api::balance,); + let balance = u64::from_le_bytes(balance[..].try_into().unwrap()); + + output!(minimum_balance, [0u8; 8], api::minimum_balance,); + let minimum_balance = u64::from_le_bytes(minimum_balance[..].try_into().unwrap()); + + // Make the transferred value exceed the balance by adding the minimum balance. + let balance = balance + minimum_balance; + + // Try to self-destruct by sending more balance to the 0 address. + // The call will fail because a contract transfer has a keep alive requirement. + let res = api::transfer(&[0u8; 32], &balance.to_le_bytes()); + assert!(matches!(res, Err(uapi::ReturnErrorCode::TransferFailed))); +} diff --git a/substrate/frame/revive/fixtures/contracts/dummy.rs b/substrate/frame/revive/fixtures/contracts/dummy.rs new file mode 100644 index 000000000000..c7294e99139e --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/dummy.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] +#![no_main] + +extern crate common; + +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +// Export that is never called. We can put code here that should be in the binary +// but is never supposed to be run. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call_never() { + // Make sure the 0xDEADBEEF pattern appears in the binary by + // making it opaque to the optimizer. The benchmarking code will + // just find and replace this pattern to make the code unique when + // necessary. + api::return_value(ReturnFlags::empty(), &[0xDE, 0xAD, 0xBE, 0xEF]); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/fixtures/contracts/ecdsa_recover.rs b/substrate/frame/revive/fixtures/contracts/ecdsa_recover.rs new file mode 100644 index 000000000000..0f28ca2c8198 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/ecdsa_recover.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + signature: [u8; 65], + hash: [u8; 32], + ); + + let mut output = [0u8; 33]; + api::ecdsa_recover( + &signature[..].try_into().unwrap(), + &hash[..].try_into().unwrap(), + &mut output, + ) + .unwrap(); + api::return_value(uapi::ReturnFlags::empty(), &output); +} diff --git a/substrate/frame/revive/fixtures/contracts/event_and_return_on_deploy.rs b/substrate/frame/revive/fixtures/contracts/event_and_return_on_deploy.rs new file mode 100644 index 000000000000..9186835d2911 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/event_and_return_on_deploy.rs @@ -0,0 +1,36 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + let buffer = [1u8, 2, 3, 4]; + api::deposit_event(&[0u8; 0], &buffer); + api::return_value(uapi::ReturnFlags::empty(), &buffer); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + unreachable!() +} diff --git a/substrate/frame/revive/fixtures/contracts/event_size.rs b/substrate/frame/revive/fixtures/contracts/event_size.rs new file mode 100644 index 000000000000..2b56de4bd3fd --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/event_size.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +static BUFFER: [u8; 16 * 1024 + 1] = [0u8; 16 * 1024 + 1]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(len: u32,); + + let data = &BUFFER[..len as usize]; + + api::deposit_event(&[0u8; 0], data); +} diff --git a/substrate/frame/revive/fixtures/contracts/float_instruction.rs b/substrate/frame/revive/fixtures/contracts/float_instruction.rs new file mode 100644 index 000000000000..b1eaaf8543c6 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/float_instruction.rs @@ -0,0 +1,34 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +extern crate common; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} + +#[no_mangle] +pub extern "C" fn add(a: f32, b: f32) -> f32 { + a + b +} diff --git a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs new file mode 100644 index 000000000000..90884f1a2a68 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(buffer, 36, code_hash: [u8; 32],); + let input = &buffer[32..]; + + let err_code = match api::instantiate( + code_hash, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, /* How much proof_size weight to devote for the execution. 0 = + * all. */ + None, // No deposit limit. + &10_000u64.to_le_bytes(), // Value to transfer. + input, + None, + None, + &[0u8; 0], // Empty salt. + ) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + // Exit with success and take transfer return code to the output buffer. + api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/instr_benchmark.rs b/substrate/frame/revive/fixtures/contracts/instr_benchmark.rs new file mode 100644 index 000000000000..c5fb382c3276 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/instr_benchmark.rs @@ -0,0 +1,41 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] +#![no_main] + +extern crate common; + +use common::input; +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(rounds: u32, start: u32, div: u32, mult: u32, add: u32, ); + + let mut acc = start; + + for _ in 0..rounds { + acc = acc / div * mult + add; + } + + api::return_value(ReturnFlags::empty(), start.to_le_bytes().as_ref()); +} diff --git a/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs b/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs new file mode 100644 index 000000000000..3ed886b49489 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs @@ -0,0 +1,68 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This contract tests the behavior of locking / unlocking delegate_dependencies when delegate +//! calling into a contract. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +const ALICE: [u8; 32] = [1u8; 32]; + +/// Load input data and perform the action specified by the input. +/// If `delegate_call` is true, then delegate call into the contract. +fn load_input(delegate_call: bool) { + input!( + action: u32, + code_hash: [u8; 32], + ); + + match action { + // 1 = Lock delegate dependency + 1 => { + api::lock_delegate_dependency(code_hash); + }, + // 2 = Unlock delegate dependency + 2 => { + api::unlock_delegate_dependency(code_hash); + }, + // 3 = Terminate + 3 => { + api::terminate(&ALICE); + }, + // Everything else is a noop + _ => {}, + } + + if delegate_call { + api::delegate_call(uapi::CallFlags::empty(), code_hash, &[], None).unwrap(); + } +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + load_input(false); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + load_input(true); +} diff --git a/substrate/frame/revive/fixtures/contracts/multi_store.rs b/substrate/frame/revive/fixtures/contracts/multi_store.rs new file mode 100644 index 000000000000..079a4548e78d --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/multi_store.rs @@ -0,0 +1,43 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Does two stores to two separate storage items +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static BUFFER: [u8; 512] = [0u8; 512]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + size1: u32, + size2: u32, + ); + + // Place a values in storage sizes are specified in the input buffer. + // We don't care about the contents of the storage item. + api::set_storage(StorageFlags::empty(), &[1u8; 32], &BUFFER[0..size1 as _]); + api::set_storage(StorageFlags::empty(), &[2u8; 32], &BUFFER[0..size2 as _]); +} diff --git a/substrate/frame/revive/fixtures/contracts/new_set_code_hash_contract.rs b/substrate/frame/revive/fixtures/contracts/new_set_code_hash_contract.rs new file mode 100644 index 000000000000..2a59b6e33d89 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/new_set_code_hash_contract.rs @@ -0,0 +1,32 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + api::return_value(uapi::ReturnFlags::empty(), &2u32.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/noop.rs b/substrate/frame/revive/fixtures/contracts/noop.rs new file mode 100644 index 000000000000..48d8a6896d62 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/noop.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] +#![no_main] + +extern crate common; + +use common::input; +use uapi::HostFn; + +#[polkavm_derive::polkavm_import] +extern "C" { + pub fn noop(); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(rounds: u32, ); + + for _ in 0..rounds { + unsafe { + noop(); + } + } +} diff --git a/substrate/frame/revive/fixtures/contracts/ok_trap_revert.rs b/substrate/frame/revive/fixtures/contracts/ok_trap_revert.rs new file mode 100644 index 000000000000..55115f8642f3 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/ok_trap_revert.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + ok_trap_revert(); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + ok_trap_revert(); +} + +#[no_mangle] +fn ok_trap_revert() { + input!(buffer, 4,); + match buffer.first().unwrap_or(&0) { + 1 => api::return_value(uapi::ReturnFlags::REVERT, &[0u8; 0]), + 2 => panic!(), + _ => {}, + }; +} diff --git a/substrate/frame/revive/fixtures/contracts/read_only_call.rs b/substrate/frame/revive/fixtures/contracts/read_only_call.rs new file mode 100644 index 000000000000..a62bb205039f --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/read_only_call.rs @@ -0,0 +1,50 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This fixture tests if read-only call works as expected. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 256, + callee_addr: [u8; 32], + callee_input: [u8], + ); + + // Call the callee + api::call( + uapi::CallFlags::READ_ONLY, + callee_addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. + callee_input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/recurse.rs b/substrate/frame/revive/fixtures/contracts/recurse.rs new file mode 100644 index 000000000000..f4dfd6c965d0 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/recurse.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This fixture calls itself as many times as passed as argument. + +#![no_std] +#![no_main] + +use common::{input, output}; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(calls_left: u32, ); + + // own address + output!(addr, [0u8; 32], api::address,); + + if calls_left == 0 { + return + } + + api::call( + uapi::CallFlags::ALLOW_REENTRY, + addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much deposit_limit to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. + &(calls_left - 1).to_le_bytes(), + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/return_with_data.rs b/substrate/frame/revive/fixtures/contracts/return_with_data.rs new file mode 100644 index 000000000000..47a1cc911192 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/return_with_data.rs @@ -0,0 +1,47 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + call(); +} + +/// Reads the first byte as the exit status and copy all but the first 4 bytes of the input as +/// output data. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + input, 128, + exit_status: [u8; 4], + output: [u8], + ); + + // Burn some PoV, clear_storage consumes some PoV as in order to clear the storage we need to we + // need to read its size first. + api::clear_storage(StorageFlags::empty(), b""); + + let exit_status = uapi::ReturnFlags::from_bits(exit_status[0] as u32).unwrap(); + api::return_value(exit_status, output); +} diff --git a/substrate/frame/revive/fixtures/contracts/run_out_of_gas.rs b/substrate/frame/revive/fixtures/contracts/run_out_of_gas.rs new file mode 100644 index 000000000000..11eaaa7c8624 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/run_out_of_gas.rs @@ -0,0 +1,32 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +extern crate common; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + #[allow(clippy::empty_loop)] + loop {} +} diff --git a/substrate/frame/revive/fixtures/contracts/self_destruct.rs b/substrate/frame/revive/fixtures/contracts/self_destruct.rs new file mode 100644 index 000000000000..10f33226acf2 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/self_destruct.rs @@ -0,0 +1,55 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::{input, output}; +use uapi::{HostFn, HostFnImpl as api}; + +const DJANGO: [u8; 32] = [4u8; 32]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + // If the input data is not empty, then recursively call self with empty input data. + // This should trap instead of self-destructing since a contract cannot be removed, while it's + // in the execution stack. If the recursive call traps, then trap here as well. + input!(input, 4,); + + if !input.is_empty() { + output!(addr, [0u8; 32], api::address,); + api::call( + uapi::CallFlags::ALLOW_REENTRY, + addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value to transfer. + &[0u8; 0], + None, + ) + .unwrap(); + } else { + // Try to terminate and give balance to django. + api::terminate(&DJANGO); + } +} diff --git a/substrate/frame/revive/fixtures/contracts/self_destructing_constructor.rs b/substrate/frame/revive/fixtures/contracts/self_destructing_constructor.rs new file mode 100644 index 000000000000..28bcef978231 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/self_destructing_constructor.rs @@ -0,0 +1,32 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + api::terminate(&[0u8; 32]); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/fixtures/contracts/set_code_hash.rs b/substrate/frame/revive/fixtures/contracts/set_code_hash.rs new file mode 100644 index 000000000000..e3cf4becfb97 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/set_code_hash.rs @@ -0,0 +1,37 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(addr: [u8; 32],); + api::set_code_hash(addr).unwrap(); + + // we return 1 after setting new code_hash + // next `call` will NOT return this value, because contract code has been changed + api::return_value(uapi::ReturnFlags::empty(), &1u32.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/set_empty_storage.rs b/substrate/frame/revive/fixtures/contracts/set_empty_storage.rs new file mode 100644 index 000000000000..f8bbfe3faa5b --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/set_empty_storage.rs @@ -0,0 +1,32 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + api::set_storage(StorageFlags::empty(), &[0u8; 32], &[0u8; 4]); +} diff --git a/substrate/frame/revive/fixtures/contracts/set_transient_storage.rs b/substrate/frame/revive/fixtures/contracts/set_transient_storage.rs new file mode 100644 index 000000000000..a8a1fbd65148 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/set_transient_storage.rs @@ -0,0 +1,41 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static BUFFER: [u8; 512] = [0u8; 512]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(len: u32, ); + + let rounds = len as usize / BUFFER.len(); + let rest = len as usize / BUFFER.len(); + for i in 0..rounds { + api::set_storage(StorageFlags::TRANSIENT, &i.to_le_bytes(), &BUFFER); + } + api::set_storage(StorageFlags::TRANSIENT, &u32::MAX.to_le_bytes(), &BUFFER[..rest]); +} diff --git a/substrate/frame/revive/fixtures/contracts/sr25519_verify.rs b/substrate/frame/revive/fixtures/contracts/sr25519_verify.rs new file mode 100644 index 000000000000..8920ce0d4f6c --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/sr25519_verify.rs @@ -0,0 +1,48 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + signature: [u8; 64], + pub_key: [u8; 32], + msg: [u8; 11], + ); + + let exit_status = match api::sr25519_verify( + &signature.try_into().unwrap(), + msg, + &pub_key.try_into().unwrap(), + ) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + // Exit with success and take transfer return code to the output buffer. + api::return_value(uapi::ReturnFlags::empty(), &exit_status.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/storage.rs b/substrate/frame/revive/fixtures/contracts/storage.rs new file mode 100644 index 000000000000..dc21e322466c --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/storage.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This contract tests the storage APIs. It sets and clears storage values using the different +//! versions of the storage APIs. +#![no_std] +#![no_main] + +use common::unwrap_output; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + const KEY: [u8; 32] = [1u8; 32]; + const VALUE_1: [u8; 4] = [1u8; 4]; + const VALUE_2: [u8; 4] = [2u8; 4]; + const VALUE_3: [u8; 4] = [3u8; 4]; + + api::set_storage(StorageFlags::empty(), &KEY, &VALUE_1); + assert_eq!(api::contains_storage(StorageFlags::empty(), &KEY), Some(VALUE_1.len() as _)); + unwrap_output!(val, [0u8; 4], api::get_storage, StorageFlags::empty(), &KEY); + assert_eq!(**val, VALUE_1); + + let existing = api::set_storage(StorageFlags::empty(), &KEY, &VALUE_2); + assert_eq!(existing, Some(VALUE_1.len() as _)); + unwrap_output!(val, [0u8; 4], api::get_storage, StorageFlags::empty(), &KEY); + assert_eq!(**val, VALUE_2); + + api::clear_storage(StorageFlags::empty(), &KEY); + assert_eq!(api::contains_storage(StorageFlags::empty(), &KEY), None); + + let existing = api::set_storage(StorageFlags::empty(), &KEY, &VALUE_3); + assert_eq!(existing, None); + assert_eq!(api::contains_storage(StorageFlags::empty(), &KEY), Some(VALUE_1.len() as _)); + unwrap_output!(val, [0u8; 32], api::get_storage, StorageFlags::empty(), &KEY); + assert_eq!(**val, VALUE_3); + + api::clear_storage(StorageFlags::empty(), &KEY); + assert_eq!(api::contains_storage(StorageFlags::empty(), &KEY), None); + let existing = api::set_storage(StorageFlags::empty(), &KEY, &VALUE_3); + assert_eq!(existing, None); + unwrap_output!(val, [0u8; 32], api::take_storage, StorageFlags::empty(), &KEY); + assert_eq!(**val, VALUE_3); +} diff --git a/substrate/frame/revive/fixtures/contracts/storage_size.rs b/substrate/frame/revive/fixtures/contracts/storage_size.rs new file mode 100644 index 000000000000..617e8d2ea79f --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/storage_size.rs @@ -0,0 +1,50 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static mut BUFFER: [u8; 16 * 1024 + 1] = [0u8; 16 * 1024 + 1]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(len: u32, ); + + let data = unsafe { + &BUFFER[..len as usize] + }; + + // Place a garbage value in storage, the size of which is specified by the call input. + let mut key = [0u8; 32]; + key[0] = 1; + + api::set_storage(StorageFlags::empty(), &key, data); + + let data = unsafe { + &mut &mut BUFFER[..] + }; + api::get_storage(StorageFlags::empty(), &key, data).unwrap(); + assert_eq!(data.len(), len as usize); +} diff --git a/substrate/frame/revive/fixtures/contracts/store_call.rs b/substrate/frame/revive/fixtures/contracts/store_call.rs new file mode 100644 index 000000000000..b08d445191e5 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/store_call.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static BUFFER: [u8; 512] = [0u8; 512]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(len: u32, ); + + let data = &BUFFER[..len as usize]; + + // Place a garbage value in storage, the size of which is specified by the call input. + let mut key = [0u8; 32]; + key[0] = 1; + + api::set_storage(StorageFlags::empty(), &key, data); +} diff --git a/substrate/frame/revive/fixtures/contracts/store_deploy.rs b/substrate/frame/revive/fixtures/contracts/store_deploy.rs new file mode 100644 index 000000000000..e08c79d78f3b --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/store_deploy.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static BUFFER: [u8; 16 * 1024 + 1] = [0u8; 16 * 1024 + 1]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + input!(len: u32, ); + + let data = &BUFFER[..len as usize]; + + // place a garbage value in storage, the size of which is specified by the call input. + let mut key = [0u8; 32]; + key[0] = 1; + + api::set_storage(StorageFlags::empty(), &key, data); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs new file mode 100644 index 000000000000..d3f6a1dd3a08 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let ret_code = match api::transfer(&[0u8; 32], &100u64.to_le_bytes()) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + // Exit with success and take transfer return code to the output buffer. + api::return_value(uapi::ReturnFlags::empty(), &ret_code.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/transient_storage.rs b/substrate/frame/revive/fixtures/contracts/transient_storage.rs new file mode 100644 index 000000000000..aa0a69435a69 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/transient_storage.rs @@ -0,0 +1,55 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This contract tests the transient storage APIs. +#![no_std] +#![no_main] + +use common::unwrap_output; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + const KEY: [u8; 32] = [1u8; 32]; + const VALUE_1: [u8; 4] = [1u8; 4]; + const VALUE_2: [u8; 5] = [2u8; 5]; + const VALUE_3: [u8; 6] = [3u8; 6]; + + let existing = api::set_storage(StorageFlags::TRANSIENT, &KEY, &VALUE_1); + assert_eq!(existing, None); + assert_eq!(api::contains_storage(StorageFlags::TRANSIENT, &KEY), Some(VALUE_1.len() as _)); + unwrap_output!(val, [0u8; 32], api::get_storage, StorageFlags::TRANSIENT, &KEY); + assert_eq!(**val, VALUE_1); + + let existing = api::set_storage(StorageFlags::TRANSIENT, &KEY, &VALUE_2); + assert_eq!(existing, Some(VALUE_1.len() as _)); + unwrap_output!(val, [0u8; 32], api::get_storage, StorageFlags::TRANSIENT, &KEY); + assert_eq!(**val, VALUE_2); + + assert_eq!(api::clear_storage(StorageFlags::TRANSIENT, &KEY), Some(VALUE_2.len() as _)); + assert_eq!(api::contains_storage(StorageFlags::TRANSIENT, &KEY), None); + + let existing = api::set_storage(StorageFlags::TRANSIENT, &KEY, &VALUE_3); + assert_eq!(existing, None); + unwrap_output!(val, [0u8; 128], api::take_storage, StorageFlags::TRANSIENT, &KEY); + assert_eq!(**val, VALUE_3); +} diff --git a/substrate/frame/revive/fixtures/contracts/xcm_execute.rs b/substrate/frame/revive/fixtures/contracts/xcm_execute.rs new file mode 100644 index 000000000000..1d570ffead71 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/xcm_execute.rs @@ -0,0 +1,40 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(512, msg: [u8],); + + #[allow(deprecated)] + let err_code = match api::xcm_execute(msg) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/xcm_send.rs b/substrate/frame/revive/fixtures/contracts/xcm_send.rs new file mode 100644 index 000000000000..6d4629e748a7 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/xcm_send.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 512, + dest: [u8; 3], + msg: [u8], + ); + + let mut message_id = [0u8; 32]; + + #[allow(deprecated)] + api::xcm_send(dest, msg, &mut message_id).unwrap(); + api::return_value(uapi::ReturnFlags::empty(), &message_id); +} diff --git a/substrate/frame/revive/fixtures/src/lib.rs b/substrate/frame/revive/fixtures/src/lib.rs new file mode 100644 index 000000000000..1b6103f57aee --- /dev/null +++ b/substrate/frame/revive/fixtures/src/lib.rs @@ -0,0 +1,79 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +/// Load a given wasm module and returns a wasm binary contents along with it's hash. +#[cfg(feature = "std")] +pub fn compile_module( + fixture_name: &str, +) -> anyhow::Result<(Vec, ::Output)> +where + T: frame_system::Config, +{ + use sp_runtime::traits::Hash; + let out_dir: std::path::PathBuf = env!("OUT_DIR").into(); + let fixture_path = out_dir.join(format!("{fixture_name}.polkavm")); + let binary = std::fs::read(fixture_path)?; + let code_hash = T::Hashing::hash(&binary); + Ok((binary, code_hash)) +} + +/// Fixtures used in runtime benchmarks. +/// +/// We explicitly include those fixtures into the binary to make them +/// available in no-std environments (runtime benchmarks). +pub mod bench { + use alloc::vec::Vec; + + #[cfg(feature = "riscv")] + macro_rules! fixture { + ($name: literal) => { + include_bytes!(concat!(env!("OUT_DIR"), "/", $name, ".polkavm")) + }; + } + #[cfg(not(feature = "riscv"))] + macro_rules! fixture { + ($name: literal) => { + &[] + }; + } + pub const DUMMY: &[u8] = fixture!("dummy"); + pub const NOOP: &[u8] = fixture!("noop"); + pub const INSTR: &[u8] = fixture!("instr_benchmark"); + + pub fn dummy_unique(replace_with: u32) -> Vec { + let mut dummy = DUMMY.to_vec(); + let idx = dummy + .windows(4) + .position(|w| w == &[0xDE, 0xAD, 0xBE, 0xEF]) + .expect("Benchmark fixture contains this pattern; qed"); + dummy[idx..idx + 4].copy_from_slice(&replace_with.to_le_bytes()); + dummy + } +} + +#[cfg(test)] +mod test { + #[test] + fn out_dir_should_have_compiled_mocks() { + let out_dir: std::path::PathBuf = env!("OUT_DIR").into(); + assert!(out_dir.join("dummy.polkavm").exists()); + } +} diff --git a/substrate/frame/revive/mock-network/Cargo.toml b/substrate/frame/revive/mock-network/Cargo.toml new file mode 100644 index 000000000000..0d597bbdc228 --- /dev/null +++ b/substrate/frame/revive/mock-network/Cargo.toml @@ -0,0 +1,88 @@ +[package] +name = "pallet-revive-mock-network" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "A mock network for testing pallet-revive." + +[lints] +workspace = true + +[dependencies] +codec = { features = ["derive", "max-encoded-len"], workspace = true } + +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-assets = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +pallet-revive = { workspace = true, default-features = true } +pallet-revive-uapi = { workspace = true } +pallet-revive-proc-macro = { workspace = true, default-features = true } +pallet-message-queue = { workspace = true, default-features = true } +pallet-proxy = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } +pallet-xcm = { workspace = true } +polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } +sp-api = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-keystore = { workspace = true, default-features = true } +sp-runtime = { workspace = true } +sp-tracing = { workspace = true, default-features = true } +xcm = { workspace = true } +xcm-builder = { workspace = true, default-features = true } +xcm-executor = { workspace = true } +xcm-simulator = { workspace = true, default-features = true } + +[dev-dependencies] +assert_matches = { workspace = true } +pretty_assertions = { workspace = true } +pallet-revive-fixtures = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "pallet-proxy/std", + "pallet-revive-fixtures/std", + "pallet-revive/std", + "pallet-timestamp/std", + "pallet-utility/std", + "pallet-xcm/std", + "scale-info/std", + "sp-api/std", + "sp-core/std", + "sp-io/std", + "sp-keystore/std", + "sp-runtime/std", + "xcm-executor/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", + "pallet-revive/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-utility/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-primitives/runtime-benchmarks", + "polkadot-runtime-parachains/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] diff --git a/substrate/frame/revive/mock-network/src/lib.rs b/substrate/frame/revive/mock-network/src/lib.rs new file mode 100644 index 000000000000..2e4f273a0103 --- /dev/null +++ b/substrate/frame/revive/mock-network/src/lib.rs @@ -0,0 +1,152 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +pub mod mocks; +pub mod parachain; +pub mod primitives; +pub mod relay_chain; + +#[cfg(all(test, feature = "riscv"))] +mod tests; + +use crate::primitives::{AccountId, UNITS}; +pub use pallet_revive::test_utils::{ALICE, BOB}; +use sp_runtime::BuildStorage; +use xcm::latest::prelude::*; +use xcm_executor::traits::ConvertLocation; +pub use xcm_simulator::TestExt; +use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; + +// Accounts +pub const ADMIN: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]); + +// Balances +pub const INITIAL_BALANCE: u128 = 1_000_000_000 * UNITS; + +decl_test_parachain! { + pub struct ParaA { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(1), + } +} + +decl_test_relay_chain! { + pub struct Relay { + Runtime = relay_chain::Runtime, + RuntimeCall = relay_chain::RuntimeCall, + RuntimeEvent = relay_chain::RuntimeEvent, + XcmConfig = relay_chain::XcmConfig, + MessageQueue = relay_chain::MessageQueue, + System = relay_chain::System, + new_ext = relay_ext(), + } +} + +decl_test_network! { + pub struct MockNet { + relay_chain = Relay, + parachains = vec![ + (1, ParaA), + ], + } +} + +pub fn relay_sovereign_account_id() -> AccountId { + let location: Location = (Parent,).into(); + parachain::SovereignAccountOf::convert_location(&location).unwrap() +} + +pub fn parachain_sovereign_account_id(para: u32) -> AccountId { + let location: Location = (Parachain(para),).into(); + relay_chain::SovereignAccountOf::convert_location(&location).unwrap() +} + +pub fn parachain_account_sovereign_account_id( + para: u32, + who: sp_runtime::AccountId32, +) -> AccountId { + let location: Location = ( + Parachain(para), + AccountId32 { network: Some(relay_chain::RelayNetwork::get()), id: who.into() }, + ) + .into(); + relay_chain::SovereignAccountOf::convert_location(&location).unwrap() +} + +pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { + use parachain::{MsgQueue, Runtime, System}; + + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (relay_sovereign_account_id(), INITIAL_BALANCE), + (BOB, INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + pallet_assets::GenesisConfig:: { + assets: vec![ + (0u128, ADMIN, false, 1u128), // Create derivative asset for relay's native token + ], + metadata: Default::default(), + accounts: vec![ + (0u128, ALICE, INITIAL_BALANCE), + (0u128, relay_sovereign_account_id(), INITIAL_BALANCE), + ], + next_asset_id: None, + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + sp_tracing::try_init_simple(); + System::set_block_number(1); + MsgQueue::set_para_id(para_id.into()); + }); + ext +} + +pub fn relay_ext() -> sp_io::TestExternalities { + use relay_chain::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (parachain_sovereign_account_id(1), INITIAL_BALANCE), + (parachain_account_sovereign_account_id(1, ALICE), INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext +} + +pub type ParachainPalletXcm = pallet_xcm::Pallet; +pub type ParachainBalances = pallet_balances::Pallet; diff --git a/substrate/frame/revive/mock-network/src/mocks.rs b/substrate/frame/revive/mock-network/src/mocks.rs new file mode 100644 index 000000000000..bf3baec7a524 --- /dev/null +++ b/substrate/frame/revive/mock-network/src/mocks.rs @@ -0,0 +1,18 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +pub mod msg_queue; +pub mod relay_message_queue; diff --git a/substrate/frame/revive/mock-network/src/mocks/msg_queue.rs b/substrate/frame/revive/mock-network/src/mocks/msg_queue.rs new file mode 100644 index 000000000000..6e922c16c298 --- /dev/null +++ b/substrate/frame/revive/mock-network/src/mocks/msg_queue.rs @@ -0,0 +1,186 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Parachain runtime mock. + +use codec::{Decode, Encode}; + +use frame_support::weights::Weight; +use polkadot_parachain_primitives::primitives::{ + DmpMessageHandler, Id as ParaId, XcmpMessageFormat, XcmpMessageHandler, +}; +use polkadot_primitives::BlockNumber as RelayBlockNumber; +use sp_runtime::traits::{Get, Hash}; + +use xcm::{latest::prelude::*, VersionedXcm}; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type XcmExecutor: ExecuteXcm; + } + + #[pallet::call] + impl Pallet {} + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery>; + + #[pallet::storage] + /// A queue of received DMP messages + pub(super) type ReceivedDmp = StorageValue<_, Vec>, ValueQuery>; + + impl Get for Pallet { + fn get() -> ParaId { + ParachainId::::get() + } + } + + pub type MessageId = [u8; 32]; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Some XCM was executed OK. + Success(Option), + /// Some XCM failed. + Fail(Option, XcmError), + /// Bad XCM version used. + BadVersion(Option), + /// Bad XCM format used. + BadFormat(Option), + + // DMP + /// Downward message is invalid XCM. + InvalidFormat(MessageId), + /// Downward message is unsupported version of XCM. + UnsupportedVersion(MessageId), + /// Downward message executed with the given outcome. + ExecutedDownward(MessageId, Outcome), + } + + impl Pallet { + pub fn set_para_id(para_id: ParaId) { + ParachainId::::put(para_id); + } + + pub fn parachain_id() -> ParaId { + ParachainId::::get() + } + + pub fn received_dmp() -> Vec> { + ReceivedDmp::::get() + } + + fn handle_xcmp_message( + sender: ParaId, + _sent_at: RelayBlockNumber, + xcm: VersionedXcm, + max_weight: Weight, + ) -> Result { + let hash = Encode::using_encoded(&xcm, T::Hashing::hash); + let mut message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); + let (result, event) = match Xcm::::try_from(xcm) { + Ok(xcm) => { + let location = (Parent, Parachain(sender.into())); + match T::XcmExecutor::prepare_and_execute( + location, + xcm, + &mut message_hash, + max_weight, + Weight::zero(), + ) { + Outcome::Error { error } => (Err(error), Event::Fail(Some(hash), error)), + Outcome::Complete { used } => (Ok(used), Event::Success(Some(hash))), + // As far as the caller is concerned, this was dispatched without error, so + // we just report the weight used. + Outcome::Incomplete { used, error } => + (Ok(used), Event::Fail(Some(hash), error)), + } + }, + Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), + }; + Self::deposit_event(event); + result + } + } + + impl XcmpMessageHandler for Pallet { + fn handle_xcmp_messages<'a, I: Iterator>( + iter: I, + max_weight: Weight, + ) -> Weight { + for (sender, sent_at, data) in iter { + let mut data_ref = data; + let _ = XcmpMessageFormat::decode(&mut data_ref) + .expect("Simulator encodes with versioned xcm format; qed"); + + let mut remaining_fragments = data_ref; + while !remaining_fragments.is_empty() { + if let Ok(xcm) = + VersionedXcm::::decode(&mut remaining_fragments) + { + let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); + } else { + debug_assert!(false, "Invalid incoming XCMP message data"); + } + } + } + max_weight + } + } + + impl DmpMessageHandler for Pallet { + fn handle_dmp_messages( + iter: impl Iterator)>, + limit: Weight, + ) -> Weight { + for (_i, (_sent_at, data)) in iter.enumerate() { + let mut id = sp_io::hashing::blake2_256(&data[..]); + let maybe_versioned = VersionedXcm::::decode(&mut &data[..]); + match maybe_versioned { + Err(_) => { + Self::deposit_event(Event::InvalidFormat(id)); + }, + Ok(versioned) => match Xcm::try_from(versioned) { + Err(()) => Self::deposit_event(Event::UnsupportedVersion(id)), + Ok(x) => { + let outcome = T::XcmExecutor::prepare_and_execute( + Parent, + x.clone(), + &mut id, + limit, + Weight::zero(), + ); + ReceivedDmp::::append(x); + Self::deposit_event(Event::ExecutedDownward(id, outcome)); + }, + }, + } + } + limit + } + } +} diff --git a/substrate/frame/revive/mock-network/src/mocks/relay_message_queue.rs b/substrate/frame/revive/mock-network/src/mocks/relay_message_queue.rs new file mode 100644 index 000000000000..14099965e3f1 --- /dev/null +++ b/substrate/frame/revive/mock-network/src/mocks/relay_message_queue.rs @@ -0,0 +1,52 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use frame_support::{parameter_types, weights::Weight}; +use xcm::latest::prelude::*; +use xcm_simulator::{ + AggregateMessageOrigin, ProcessMessage, ProcessMessageError, UmpQueueId, WeightMeter, +}; + +use crate::relay_chain::{RuntimeCall, XcmConfig}; + +parameter_types! { + /// Amount of weight that can be spent per block to service messages. + pub MessageQueueServiceWeight: Weight = Weight::from_parts(1_000_000_000, 1_000_000); + pub const MessageQueueHeapSize: u32 = 65_536; + pub const MessageQueueMaxStale: u32 = 16; +} + +/// Message processor to handle any messages that were enqueued into the `MessageQueue` pallet. +pub struct MessageProcessor; +impl ProcessMessage for MessageProcessor { + type Origin = AggregateMessageOrigin; + + fn process_message( + message: &[u8], + origin: Self::Origin, + meter: &mut WeightMeter, + id: &mut [u8; 32], + ) -> Result { + let para = match origin { + AggregateMessageOrigin::Ump(UmpQueueId::Para(para)) => para, + }; + xcm_builder::ProcessXcmMessage::< + Junction, + xcm_executor::XcmExecutor, + RuntimeCall, + >::process_message(message, Junction::Parachain(para.into()), meter, id) + } +} diff --git a/substrate/frame/revive/mock-network/src/parachain.rs b/substrate/frame/revive/mock-network/src/parachain.rs new file mode 100644 index 000000000000..3def48cca96b --- /dev/null +++ b/substrate/frame/revive/mock-network/src/parachain.rs @@ -0,0 +1,346 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Parachain runtime mock. + +mod contracts_config; +use crate::{ + mocks::msg_queue::pallet as mock_msg_queue, + primitives::{AccountId, AssetIdForAssets, Balance}, +}; +use core::marker::PhantomData; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{AsEnsureOriginWithArg, Contains, ContainsPair, Everything, EverythingBut, Nothing}, + weights::{ + constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND}, + Weight, + }, +}; +use frame_system::{EnsureRoot, EnsureSigned}; +use pallet_xcm::XcmPassthrough; +use sp_core::{ConstU32, ConstU64, H256}; +use sp_runtime::traits::{Get, IdentityLookup, MaybeEquivalence}; + +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, + ConvertedConcreteId, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsConcrete, NativeAsset, + NoChecking, ParentAsSuperuser, ParentIsPreset, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, WithComputedOrigin, +}; +use xcm_executor::{traits::JustTry, Config, XcmExecutor}; + +pub type SovereignAccountOf = + (AccountId32Aliases, ParentIsPreset); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Block = Block; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type AccountStore = System; + type Balance = Balance; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type FreezeIdentifier = (); + type MaxFreezes = ConstU32<0>; + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type WeightInfo = (); +} + +parameter_types! { + pub const AssetDeposit: u128 = 1_000_000; + pub const MetadataDepositBase: u128 = 1_000_000; + pub const MetadataDepositPerByte: u128 = 100_000; + pub const AssetAccountDeposit: u128 = 1_000_000; + pub const ApprovalDeposit: u128 = 1_000_000; + pub const AssetsStringLimit: u32 = 50; + pub const RemoveItemsLimit: u32 = 50; +} + +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetIdForAssets; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type AssetAccountDeposit = AssetAccountDeposit; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = AssetsStringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type RemoveItemsLimit = RemoveItemsLimit; + type AssetIdParameter = AssetIdForAssets; + type CallbackHandle = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); + pub const ReservedDmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); +} + +parameter_types! { + pub const KsmLocation: Location = Location::parent(); + pub const TokenLocation: Location = Here.into_location(); + pub const RelayNetwork: NetworkId = ByGenesis([0; 32]); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(MsgQueue::parachain_id().into())].into(); +} + +pub type XcmOriginToCallOrigin = ( + SovereignSignedViaLocation, + ParentAsSuperuser, + SignedAccountId32AsNative, + XcmPassthrough, +); + +parameter_types! { + pub const XcmInstructionWeight: Weight = Weight::from_parts(1_000, 1_000); + pub TokensPerSecondPerMegabyte: (AssetId, u128, u128) = (AssetId(Parent.into()), 1_000_000_000_000, 1024 * 1024); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub ForeignPrefix: Location = (Parent,).into(); + pub CheckingAccount: AccountId = PolkadotXcm::check_account(); + pub TrustedLockPairs: (Location, AssetFilter) = + (Parent.into(), Wild(AllOf { id: AssetId(Parent.into()), fun: WildFungible })); +} + +pub fn estimate_message_fee(number_of_instructions: u64) -> u128 { + let weight = estimate_weight(number_of_instructions); + + estimate_fee_for_weight(weight) +} + +pub fn estimate_weight(number_of_instructions: u64) -> Weight { + XcmInstructionWeight::get().saturating_mul(number_of_instructions) +} + +pub fn estimate_fee_for_weight(weight: Weight) -> u128 { + let (_, units_per_second, units_per_mb) = TokensPerSecondPerMegabyte::get(); + + units_per_second * (weight.ref_time() as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128) + + units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128) +} + +pub type LocalBalancesTransactor = + FungibleAdapter, SovereignAccountOf, AccountId, ()>; + +pub struct FromLocationToAsset(PhantomData<(Location, AssetId)>); +impl MaybeEquivalence + for FromLocationToAsset +{ + fn convert(value: &Location) -> Option { + match value.unpack() { + (1, []) => Some(0 as AssetIdForAssets), + (1, [Parachain(para_id)]) => Some(*para_id as AssetIdForAssets), + _ => None, + } + } + + fn convert_back(_id: &AssetIdForAssets) -> Option { + None + } +} + +pub type ForeignAssetsTransactor = FungiblesAdapter< + Assets, + ConvertedConcreteId< + AssetIdForAssets, + Balance, + FromLocationToAsset, + JustTry, + >, + SovereignAccountOf, + AccountId, + NoChecking, + CheckingAccount, +>; + +/// Means for transacting assets on this chain +pub type AssetTransactors = (LocalBalancesTransactor, ForeignAssetsTransactor); + +pub struct ParentRelay; +impl Contains for ParentRelay { + fn contains(location: &Location) -> bool { + location.contains_parents_only(1) + } +} +pub struct ThisParachain; +impl Contains for ThisParachain { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (0, [Junction::AccountId32 { .. }])) + } +} + +pub type XcmRouter = crate::ParachainXcmRouter; + +pub type Barrier = ( + xcm_builder::AllowUnpaidExecutionFrom, + WithComputedOrigin< + (AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom), + UniversalLocation, + ConstU32<1>, + >, +); + +parameter_types! { + pub NftCollectionOne: AssetFilter + = Wild(AllOf { fun: WildNonFungible, id: AssetId((Parent, GeneralIndex(1)).into()) }); + pub NftCollectionOneForRelay: (AssetFilter, Location) + = (NftCollectionOne::get(), Parent.into()); + pub RelayNativeAsset: AssetFilter = Wild(AllOf { fun: WildFungible, id: AssetId((Parent, Here).into()) }); + pub RelayNativeAssetForRelay: (AssetFilter, Location) = (RelayNativeAsset::get(), Parent.into()); +} +pub type TrustedTeleporters = + (xcm_builder::Case, xcm_builder::Case); +pub type TrustedReserves = EverythingBut>; + +pub struct XcmConfig; +impl Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = AssetTransactors; + type OriginConverter = XcmOriginToCallOrigin; + type IsReserve = (NativeAsset, TrustedReserves); + type IsTeleporter = TrustedTeleporters; + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; + type ResponseHandler = PolkadotXcm; + type AssetTrap = PolkadotXcm; + type AssetLocker = PolkadotXcm; + type AssetExchanger = (); + type AssetClaims = PolkadotXcm; + type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = AllPalletsWithSystem; + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; + type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; +} + +impl mock_msg_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +pub struct TrustedLockerCase(PhantomData); +impl> ContainsPair for TrustedLockerCase { + fn contains(origin: &Location, asset: &Asset) -> bool { + let (o, a) = T::get(); + a.matches(asset) && &o == origin + } +} + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Nothing; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = IsConcrete; + type TrustedLockers = TrustedLockerCase; + type SovereignAccountOf = SovereignAccountOf; + type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); + type WeightInfo = pallet_xcm::TestWeightInfo; + type AdminOrigin = EnsureRoot; +} + +type Block = frame_system::mocking::MockBlock; + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1>; + type WeightInfo = (); +} + +construct_runtime!( + pub enum Runtime + { + System: frame_system, + Balances: pallet_balances, + Timestamp: pallet_timestamp, + MsgQueue: mock_msg_queue, + PolkadotXcm: pallet_xcm, + Contracts: pallet_revive, + Assets: pallet_assets, + } +); diff --git a/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs b/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs new file mode 100644 index 000000000000..49f53ae5bc3d --- /dev/null +++ b/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs @@ -0,0 +1,27 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use super::{Balances, Runtime, RuntimeCall, RuntimeEvent}; +use crate::parachain::RuntimeHoldReason; +use frame_support::derive_impl; + +#[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)] +impl pallet_revive::Config for Runtime { + type AddressGenerator = pallet_revive::DefaultAddressGenerator; + type Currency = Balances; + type Time = super::Timestamp; + type Xcm = pallet_xcm::Pallet; +} diff --git a/substrate/frame/revive/mock-network/src/primitives.rs b/substrate/frame/revive/mock-network/src/primitives.rs new file mode 100644 index 000000000000..efc42772f88a --- /dev/null +++ b/substrate/frame/revive/mock-network/src/primitives.rs @@ -0,0 +1,23 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +pub type Balance = u128; + +pub const UNITS: Balance = 10_000_000_000; +pub const CENTS: Balance = UNITS / 100; // 100_000_000 + +pub type AccountId = sp_runtime::AccountId32; +pub type AssetIdForAssets = u128; diff --git a/substrate/frame/revive/mock-network/src/relay_chain.rs b/substrate/frame/revive/mock-network/src/relay_chain.rs new file mode 100644 index 000000000000..8829fff3d043 --- /dev/null +++ b/substrate/frame/revive/mock-network/src/relay_chain.rs @@ -0,0 +1,239 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Relay chain runtime mock. + +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{Contains, Everything, Nothing}, + weights::Weight, +}; + +use frame_system::EnsureRoot; +use sp_core::{ConstU32, H256}; +use sp_runtime::traits::IdentityLookup; + +use polkadot_parachain_primitives::primitives::Id as ParaId; +use polkadot_runtime_parachains::{configuration, origin, shared}; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowSubscriptionsFrom, + AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, + ChildSystemParachainAsSuperuser, DescribeAllTerminal, DescribeFamily, FixedRateOfFungible, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, WithComputedOrigin, +}; +use xcm_executor::{Config, XcmExecutor}; + +use super::{ + mocks::relay_message_queue::*, + primitives::{AccountId, Balance}, +}; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Block = Block; + type Nonce = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = ConstU32<0>; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; +} + +impl shared::Config for Runtime { + type DisabledValidators = (); +} + +impl configuration::Config for Runtime { + type WeightInfo = configuration::TestWeightInfo; +} + +parameter_types! { + pub RelayNetwork: NetworkId = ByGenesis([0; 32]); + pub const TokenLocation: Location = Here.into_location(); + pub UniversalLocation: InteriorLocation = RelayNetwork::get().into(); + pub UnitWeightCost: u64 = 1_000; +} + +pub type SovereignAccountOf = ( + HashedDescription>, + AccountId32Aliases, + ChildParachainConvertsVia, +); + +pub type LocalBalancesTransactor = + FungibleAdapter, SovereignAccountOf, AccountId, ()>; + +pub type AssetTransactors = LocalBalancesTransactor; + +type LocalOriginConverter = ( + SovereignSignedViaLocation, + ChildParachainAsNative, + SignedAccountId32AsNative, + ChildSystemParachainAsSuperuser, +); + +parameter_types! { + pub const XcmInstructionWeight: Weight = Weight::from_parts(1_000, 1_000); + pub TokensPerSecondPerMegabyte: (AssetId, u128, u128) = + (AssetId(TokenLocation::get()), 1_000_000_000_000, 1024 * 1024); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; +} + +pub struct ChildrenParachains; +impl Contains for ChildrenParachains { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (0, [Parachain(_)])) + } +} + +pub type XcmRouter = crate::RelayChainXcmRouter; +pub type Barrier = WithComputedOrigin< + ( + AllowExplicitUnpaidExecutionFrom, + AllowTopLevelPaidExecutionFrom, + AllowSubscriptionsFrom, + ), + UniversalLocation, + ConstU32<1>, +>; + +pub struct XcmConfig; +impl Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = AssetTransactors; + type OriginConverter = LocalOriginConverter; + type IsReserve = (); + type IsTeleporter = (); + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; + type ResponseHandler = XcmPallet; + type AssetTrap = XcmPallet; + type AssetLocker = XcmPallet; + type AssetExchanger = (); + type AssetClaims = XcmPallet; + type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; + type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); + type XcmRecorder = XcmPallet; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = IsConcrete; + type TrustedLockers = (); + type SovereignAccountOf = SovereignAccountOf; + type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); + type WeightInfo = pallet_xcm::TestWeightInfo; + type AdminOrigin = EnsureRoot; +} + +impl origin::Config for Runtime {} + +type Block = frame_system::mocking::MockBlock; + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Size = u32; + type HeapSize = MessageQueueHeapSize; + type MaxStale = MessageQueueMaxStale; + type ServiceWeight = MessageQueueServiceWeight; + type IdleMaxServiceWeight = (); + type MessageProcessor = MessageProcessor; + type QueueChangeHandler = (); + type WeightInfo = (); + type QueuePausedQuery = (); +} + +construct_runtime!( + pub enum Runtime { + System: frame_system, + Balances: pallet_balances, + ParasOrigin: origin, + XcmPallet: pallet_xcm, + MessageQueue: pallet_message_queue, + } +); diff --git a/substrate/frame/revive/mock-network/src/tests.rs b/substrate/frame/revive/mock-network/src/tests.rs new file mode 100644 index 000000000000..9259dd6f169e --- /dev/null +++ b/substrate/frame/revive/mock-network/src/tests.rs @@ -0,0 +1,207 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + parachain::{self, Runtime}, + parachain_account_sovereign_account_id, + primitives::{AccountId, CENTS}, + relay_chain, MockNet, ParaA, ParachainBalances, Relay, ALICE, BOB, INITIAL_BALANCE, +}; +use codec::{Decode, Encode}; +use frame_support::traits::{fungibles::Mutate, Currency}; +use frame_system::RawOrigin; +use pallet_revive::{ + test_utils::{self, builder::*}, + Code, +}; +use pallet_revive_fixtures::compile_module; +use pallet_revive_uapi::ReturnErrorCode; +use xcm::{v4::prelude::*, VersionedLocation, VersionedXcm}; +use xcm_simulator::TestExt; + +macro_rules! assert_return_code { + ( $x:expr , $y:expr $(,)? ) => {{ + assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); + }}; +} + +fn bare_call(dest: sp_runtime::AccountId32) -> BareCallBuilder { + BareCallBuilder::::bare_call(RawOrigin::Signed(ALICE).into(), dest) +} + +/// Instantiate the tests contract, and fund it with some balance and assets. +fn instantiate_test_contract(name: &str) -> AccountId { + let (wasm, _) = compile_module::(name).unwrap(); + + // Instantiate contract. + let contract_addr = ParaA::execute_with(|| { + BareInstantiateBuilder::::bare_instantiate( + RawOrigin::Signed(ALICE).into(), + Code::Upload(wasm), + ) + .build_and_unwrap_account_id() + }); + + // Funds contract account with some balance and assets. + ParaA::execute_with(|| { + parachain::Balances::make_free_balance_be(&contract_addr, INITIAL_BALANCE); + parachain::Assets::mint_into(0u32.into(), &contract_addr, INITIAL_BALANCE).unwrap(); + }); + Relay::execute_with(|| { + let sovereign_account = parachain_account_sovereign_account_id(1u32, contract_addr.clone()); + relay_chain::Balances::make_free_balance_be(&sovereign_account, INITIAL_BALANCE); + }); + + contract_addr +} + +#[test] +fn test_xcm_execute() { + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + + // Execute XCM instructions through the contract. + ParaA::execute_with(|| { + let amount: u128 = 10 * CENTS; + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: BOB.clone().into() }; + + // The XCM used to transfer funds to Bob. + let message: Xcm<()> = Xcm::builder_unsafe() + .withdraw_asset(assets.clone()) + .deposit_asset(assets, beneficiary) + .build(); + + let result = bare_call(contract_addr.clone()) + .data(VersionedXcm::V4(message).encode()) + .build(); + + assert_eq!(result.gas_consumed, result.gas_required); + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::Success); + + // Check if the funds are subtracted from the account of Alice and added to the account of + // Bob. + let initial = INITIAL_BALANCE; + assert_eq!(ParachainBalances::free_balance(BOB), initial + amount); + assert_eq!(ParachainBalances::free_balance(&contract_addr), initial - amount); + }); +} + +#[test] +fn test_xcm_execute_incomplete() { + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + let amount = 10 * CENTS; + + // Execute XCM instructions through the contract. + ParaA::execute_with(|| { + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: BOB.clone().into() }; + + // The XCM used to transfer funds to Bob. + let message: Xcm<()> = Xcm::builder_unsafe() + .withdraw_asset(assets.clone()) + // This will fail as the contract does not have enough balance to complete both + // withdrawals. + .withdraw_asset((Here, INITIAL_BALANCE)) + .buy_execution(assets.clone(), Unlimited) + .deposit_asset(assets, beneficiary) + .build(); + + let result = bare_call(contract_addr.clone()) + .data(VersionedXcm::V4(message).encode()) + .build(); + + assert_eq!(result.gas_consumed, result.gas_required); + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); + + assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); + assert_eq!(ParachainBalances::free_balance(&contract_addr), INITIAL_BALANCE - amount); + }); +} + +#[test] +fn test_xcm_execute_reentrant_call() { + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + + ParaA::execute_with(|| { + let transact_call = parachain::RuntimeCall::Contracts(pallet_revive::Call::call { + dest: contract_addr.clone(), + gas_limit: 1_000_000.into(), + storage_deposit_limit: test_utils::deposit_limit::(), + data: vec![], + value: 0u128, + }); + + // The XCM used to transfer funds to Bob. + let message: Xcm = Xcm::builder_unsafe() + .transact(OriginKind::Native, 1_000_000_000, transact_call.encode()) + .expect_transact_status(MaybeErrorCode::Success) + .build(); + + let result = bare_call(contract_addr.clone()) + .data(VersionedXcm::V4(message).encode()) + .build_and_unwrap_result(); + + assert_return_code!(&result, ReturnErrorCode::XcmExecutionFailed); + + // Funds should not change hands as the XCM transact failed. + assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); + }); +} + +#[test] +fn test_xcm_send() { + MockNet::reset(); + let contract_addr = instantiate_test_contract("xcm_send"); + let amount = 1_000 * CENTS; + let fee = parachain::estimate_message_fee(4); // Accounts for the `DescendOrigin` instruction added by `send_xcm` + + // Send XCM instructions through the contract, to transfer some funds from the contract + // derivative account to Alice on the relay chain. + ParaA::execute_with(|| { + let dest = VersionedLocation::V4(Parent.into()); + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: ALICE.clone().into() }; + + let message: Xcm<()> = Xcm::builder() + .withdraw_asset(assets.clone()) + .buy_execution((Here, fee), Unlimited) + .deposit_asset(assets, beneficiary) + .build(); + + let result = bare_call(contract_addr.clone()) + .data((dest, VersionedXcm::V4(message)).encode()) + .build_and_unwrap_result(); + + let mut data = &result.data[..]; + XcmHash::decode(&mut data).expect("Failed to decode xcm_send message_id"); + }); + + Relay::execute_with(|| { + let derived_contract_addr = ¶chain_account_sovereign_account_id(1, contract_addr); + assert_eq!( + INITIAL_BALANCE - amount, + relay_chain::Balances::free_balance(derived_contract_addr) + ); + assert_eq!(INITIAL_BALANCE + amount - fee, relay_chain::Balances::free_balance(ALICE)); + }); +} diff --git a/substrate/frame/revive/proc-macro/Cargo.toml b/substrate/frame/revive/proc-macro/Cargo.toml new file mode 100644 index 000000000000..7b47d6053504 --- /dev/null +++ b/substrate/frame/revive/proc-macro/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "pallet-revive-proc-macro" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "Procedural macros used in pallet_revive" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { features = ["full"], workspace = true } diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs new file mode 100644 index 000000000000..95f4110a2d76 --- /dev/null +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -0,0 +1,616 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Procedural macros used in the contracts module. +//! +//! Most likely you should use the [`#[define_env]`][`macro@define_env`] attribute macro which hides +//! boilerplate of defining external environment for a wasm module. + +use proc_macro::TokenStream; +use proc_macro2::{Literal, Span, TokenStream as TokenStream2}; +use quote::{quote, ToTokens}; +use syn::{parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, FnArg, Ident}; + +/// Defines a host functions set that can be imported by contract wasm code. +/// +/// **NB**: Be advised that all functions defined by this macro +/// will panic if called with unexpected arguments. +/// +/// It's up to you as the user of this macro to check signatures of wasm code to be executed +/// and reject the code if any imported function has a mismatched signature. +/// +/// ## Example +/// +/// ```nocompile +/// #[define_env] +/// pub mod some_env { +/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<(), TrapReason> { +/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) +/// } +/// } +/// ``` +/// This example will expand to the `foo()` defined in the wasm module named `seal0`. This is +/// because the module `seal0` is the default when no module is specified. +/// +/// To define a host function in `seal2` and `seal3` modules, it should be annotated with the +/// appropriate attribute as follows: +/// +/// ## Example +/// +/// ```nocompile +/// #[define_env] +/// pub mod some_env { +/// #[version(2)] +/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { +/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) +/// } +/// +/// #[version(3)] +/// #[unstable] +/// fn bar(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { +/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) +/// } +/// } +/// ``` +/// The function `bar` is additionally annotated with `unstable` which removes it from the stable +/// interface. Check out the README to learn about unstable functions. +/// +/// +/// In this example, the following host functions will be generated by the macro: +/// - `foo()` in module `seal1`, +/// - `seal_foo()` in module `seal1`, +/// - `bar()` in module `seal42`. +/// +/// Only following return types are allowed for the host functions defined with the macro: +/// - `Result<(), TrapReason>`, +/// - `Result`, +/// - `Result`. +/// +/// The macro expands to `pub struct Env` declaration, with the following traits implementations: +/// - `pallet_revive::wasm::Environment> where E: Ext` +/// - `pallet_revive::wasm::Environment<()>` +/// +/// The implementation on `()` can be used in places where no `Ext` exists, yet. This is useful +/// when only checking whether a code can be instantiated without actually executing any code. +/// +/// +/// To build up these docs, run: +/// +/// ```nocompile +/// cargo doc +/// ``` +#[proc_macro_attribute] +pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream { + if !attr.is_empty() { + let msg = r#"Invalid `define_env` attribute macro: expected no attributes: + - `#[define_env]`"#; + let span = TokenStream2::from(attr).span(); + return syn::Error::new(span, msg).to_compile_error().into() + } + + let item = syn::parse_macro_input!(item as syn::ItemMod); + + match EnvDef::try_from(item) { + Ok(mut def) => expand_env(&mut def).into(), + Err(e) => e.to_compile_error().into(), + } +} + +/// Parsed environment definition. +struct EnvDef { + host_funcs: Vec, +} + +/// Parsed host function definition. +struct HostFn { + item: syn::ItemFn, + api_version: Option, + name: String, + returns: HostFnReturn, + cfg: Option, +} + +enum HostFnReturn { + Unit, + U32, + ReturnCode, +} + +impl HostFnReturn { + fn map_output(&self) -> TokenStream2 { + match self { + Self::Unit => quote! { |_| None }, + Self::U32 => quote! { |ret_val| Some(ret_val) }, + Self::ReturnCode => quote! { |ret_code| Some(ret_code.into()) }, + } + } + + fn success_type(&self) -> syn::ReturnType { + match self { + Self::Unit => syn::ReturnType::Default, + Self::U32 => parse_quote! { -> u32 }, + Self::ReturnCode => parse_quote! { -> ReturnErrorCode }, + } + } +} + +impl EnvDef { + pub fn try_from(item: syn::ItemMod) -> syn::Result { + let span = item.span(); + let err = |msg| syn::Error::new(span, msg); + let items = &item + .content + .as_ref() + .ok_or(err("Invalid environment definition, expected `mod` to be inlined."))? + .1; + + let extract_fn = |i: &syn::Item| match i { + syn::Item::Fn(i_fn) => Some(i_fn.clone()), + _ => None, + }; + + let host_funcs = items + .iter() + .filter_map(extract_fn) + .map(HostFn::try_from) + .collect::, _>>()?; + + Ok(Self { host_funcs }) + } +} + +impl HostFn { + pub fn try_from(mut item: syn::ItemFn) -> syn::Result { + let err = |span, msg| { + let msg = format!("Invalid host function definition.\n{}", msg); + syn::Error::new(span, msg) + }; + + // process attributes + let msg = "Only #[api_version()], #[cfg] and #[mutating] attributes are allowed."; + let span = item.span(); + let mut attrs = item.attrs.clone(); + attrs.retain(|a| !a.path().is_ident("doc")); + let mut api_version = None; + let mut mutating = false; + let mut cfg = None; + while let Some(attr) = attrs.pop() { + let ident = attr.path().get_ident().ok_or(err(span, msg))?.to_string(); + match ident.as_str() { + "api_version" => { + if api_version.is_some() { + return Err(err(span, "#[api_version] can only be specified once")) + } + api_version = + Some(attr.parse_args::().and_then(|lit| lit.base10_parse())?); + }, + "mutating" => { + if mutating { + return Err(err(span, "#[mutating] can only be specified once")) + } + mutating = true; + }, + "cfg" => { + if cfg.is_some() { + return Err(err(span, "#[cfg] can only be specified once")) + } + cfg = Some(attr); + }, + id => return Err(err(span, &format!("Unsupported attribute \"{id}\". {msg}"))), + } + } + + if mutating { + let stmt = syn::parse_quote! { + if self.ext().is_read_only() { + return Err(Error::::StateChangeDenied.into()); + } + }; + item.block.stmts.insert(0, stmt); + } + + let name = item.sig.ident.to_string(); + + let msg = "Every function must start with these two parameters: &mut self, memory: &mut M"; + let special_args = item + .sig + .inputs + .iter() + .take(2) + .enumerate() + .map(|(i, arg)| is_valid_special_arg(i, arg)) + .fold(0u32, |acc, valid| if valid { acc + 1 } else { acc }); + + if special_args != 2 { + return Err(err(span, msg)) + } + + // process return type + let msg = r#"Should return one of the following: + - Result<(), TrapReason>, + - Result, + - Result"#; + let ret_ty = match item.clone().sig.output { + syn::ReturnType::Type(_, ty) => Ok(ty.clone()), + _ => Err(err(span, &msg)), + }?; + match *ret_ty { + syn::Type::Path(tp) => { + let result = &tp.path.segments.last().ok_or(err(span, &msg))?; + let (id, span) = (result.ident.to_string(), result.ident.span()); + id.eq(&"Result".to_string()).then_some(()).ok_or(err(span, &msg))?; + + match &result.arguments { + syn::PathArguments::AngleBracketed(group) => { + if group.args.len() != 2 { + return Err(err(span, &msg)) + }; + + let arg2 = group.args.last().ok_or(err(span, &msg))?; + + let err_ty = match arg2 { + syn::GenericArgument::Type(ty) => Ok(ty.clone()), + _ => Err(err(arg2.span(), &msg)), + }?; + + match err_ty { + syn::Type::Path(tp) => Ok(tp + .path + .segments + .first() + .ok_or(err(arg2.span(), &msg))? + .ident + .to_string()), + _ => Err(err(tp.span(), &msg)), + }? + .eq("TrapReason") + .then_some(()) + .ok_or(err(span, &msg))?; + + let arg1 = group.args.first().ok_or(err(span, &msg))?; + let ok_ty = match arg1 { + syn::GenericArgument::Type(ty) => Ok(ty.clone()), + _ => Err(err(arg1.span(), &msg)), + }?; + let ok_ty_str = match ok_ty { + syn::Type::Path(tp) => Ok(tp + .path + .segments + .first() + .ok_or(err(arg1.span(), &msg))? + .ident + .to_string()), + syn::Type::Tuple(tt) => { + if !tt.elems.is_empty() { + return Err(err(arg1.span(), &msg)) + }; + Ok("()".to_string()) + }, + _ => Err(err(ok_ty.span(), &msg)), + }?; + let returns = match ok_ty_str.as_str() { + "()" => Ok(HostFnReturn::Unit), + "u32" => Ok(HostFnReturn::U32), + "ReturnErrorCode" => Ok(HostFnReturn::ReturnCode), + _ => Err(err(arg1.span(), &msg)), + }?; + + Ok(Self { item, api_version, name, returns, cfg }) + }, + _ => Err(err(span, &msg)), + } + }, + _ => Err(err(span, &msg)), + } + } +} + +fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool { + match (idx, arg) { + (0, FnArg::Receiver(rec)) => rec.reference.is_some() && rec.mutability.is_some(), + (1, FnArg::Typed(pat)) => { + let ident = + if let syn::Pat::Ident(ref ident) = *pat.pat { &ident.ident } else { return false }; + if !(ident == "memory" || ident == "_memory") { + return false + } + matches!(*pat.ty, syn::Type::Reference(_)) + }, + _ => false, + } +} + +fn arg_decoder<'a, P, I>(param_names: P, param_types: I) -> TokenStream2 +where + P: Iterator> + Clone, + I: Iterator> + Clone, +{ + const ALLOWED_REGISTERS: u32 = 6; + let mut registers_used = 0; + let mut bindings = vec![]; + for (idx, (name, ty)) in param_names.clone().zip(param_types.clone()).enumerate() { + let syn::Type::Path(path) = &**ty else { + panic!("Type needs to be path"); + }; + let Some(ident) = path.path.get_ident() else { + panic!("Type needs to be ident"); + }; + let size = + if ident == "i8" || + ident == "i16" || ident == "i32" || + ident == "u8" || ident == "u16" || + ident == "u32" + { + 1 + } else if ident == "i64" || ident == "u64" { + 2 + } else { + panic!("Pass by value only supports primitives"); + }; + registers_used += size; + if registers_used > ALLOWED_REGISTERS { + return quote! { + let (#( #param_names, )*): (#( #param_types, )*) = memory.read_as(__a0__)?; + } + } + let this_reg = quote::format_ident!("__a{}__", idx); + let next_reg = quote::format_ident!("__a{}__", idx + 1); + let binding = if size == 1 { + quote! { + let #name = #this_reg as #ty; + } + } else { + quote! { + let #name = (#this_reg as #ty) | ((#next_reg as #ty) << 32); + } + }; + bindings.push(binding); + } + quote! { + #( #bindings )* + } +} + +/// Expands environment definition. +/// Should generate source code for: +/// - implementations of the host functions to be added to the wasm runtime environment (see +/// `expand_impls()`). +fn expand_env(def: &EnvDef) -> TokenStream2 { + let impls = expand_functions(def); + let bench_impls = expand_bench_functions(def); + let docs = expand_func_doc(def); + let highest_api_version = + def.host_funcs.iter().filter_map(|f| f.api_version).max().unwrap_or_default(); + + quote! { + #[cfg(test)] + pub const HIGHEST_API_VERSION: u16 = #highest_api_version; + + impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { + fn handle_ecall( + &mut self, + memory: &mut M, + __syscall_symbol__: &[u8], + __available_api_version__: ApiVersion, + ) -> Result, TrapReason> + { + #impls + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { + #bench_impls + } + + /// Documentation of the syscalls (host functions) available to contracts. + /// + /// Each of the functions in this trait represent a function that is callable + /// by the contract. Guests use the function name as the import symbol. + /// + /// # Note + /// + /// This module is not meant to be used by any code. Rather, it is meant to be + /// consumed by humans through rustdoc. + #[cfg(doc)] + pub trait SyscallDoc { + #docs + } + } +} + +fn expand_functions(def: &EnvDef) -> TokenStream2 { + let impls = def.host_funcs.iter().map(|f| { + // skip the self and memory argument + let params = f.item.sig.inputs.iter().skip(2); + let param_names = params.clone().filter_map(|arg| { + let FnArg::Typed(arg) = arg else { + return None; + }; + Some(&arg.pat) + }); + let param_types = params.clone().filter_map(|arg| { + let FnArg::Typed(arg) = arg else { + return None; + }; + Some(&arg.ty) + }); + let arg_decoder = arg_decoder(param_names, param_types); + let cfg = &f.cfg; + let name = &f.name; + let syscall_symbol = Literal::byte_string(name.as_bytes()); + let body = &f.item.block; + let map_output = f.returns.map_output(); + let output = &f.item.sig.output; + let api_version = match f.api_version { + Some(version) => quote! { Some(#version) }, + None => quote! { None }, + }; + + // wrapped host function body call with host function traces + // see https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/contracts#host-function-tracing + let wrapped_body_with_trace = { + let trace_fmt_args = params.clone().filter_map(|arg| match arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(p) => match *p.pat.clone() { + syn::Pat::Ident(ref pat_ident) => Some(pat_ident.ident.clone()), + _ => None, + }, + }); + + let params_fmt_str = trace_fmt_args + .clone() + .map(|s| format!("{s}: {{:?}}")) + .collect::>() + .join(", "); + let trace_fmt_str = format!("{}({}) = {{:?}}\n", name, params_fmt_str); + + quote! { + // wrap body in closure to make sure the tracing is always executed + let result = (|| #body)(); + if ::log::log_enabled!(target: "runtime::revive::strace", ::log::Level::Trace) { + use core::fmt::Write; + let mut w = sp_std::Writer::default(); + let _ = core::write!(&mut w, #trace_fmt_str, #( #trace_fmt_args, )* result); + let msg = core::str::from_utf8(&w.inner()).unwrap_or_default(); + self.ext().append_debug_buffer(msg); + } + result + } + }; + + quote! { + #cfg + #syscall_symbol if __is_available__(#api_version) => { + // closure is needed so that "?" can infere the correct type + (|| #output { + #arg_decoder + #wrapped_body_with_trace + })().map(#map_output) + }, + } + }); + + quote! { + // Write gas from polkavm into pallet-revive before entering the host function. + let __gas_left_before__ = self + .ext + .gas_meter_mut() + .sync_from_executor(memory.gas()) + .map_err(TrapReason::from)?; + + // This is the overhead to call an empty syscall that always needs to be charged. + self.charge_gas(crate::wasm::RuntimeCosts::HostFn).map_err(TrapReason::from)?; + + // Not all APIs are available depending on configuration or when the code was deployed. + // This closure will be used by syscall specific code to perform this check. + let __is_available__ = |syscall_version: Option| { + match __available_api_version__ { + ApiVersion::UnsafeNewest => true, + ApiVersion::Versioned(max_available_version) => + syscall_version + .map(|required_version| max_available_version >= required_version) + .unwrap_or(false), + } + }; + + // They will be mapped to variable names by the syscall specific code. + let (__a0__, __a1__, __a2__, __a3__, __a4__, __a5__) = memory.read_input_regs(); + + // Execute the syscall specific logic in a closure so that the gas metering code is always executed. + let result = (|| match __syscall_symbol__ { + #( #impls )* + _ => Err(TrapReason::SupervisorError(Error::::InvalidSyscall.into())) + })(); + + // Write gas from pallet-revive into polkavm after leaving the host function. + let gas = self.ext.gas_meter_mut().sync_to_executor(__gas_left_before__).map_err(TrapReason::from)?; + memory.set_gas(gas.into()); + result + } +} + +fn expand_bench_functions(def: &EnvDef) -> TokenStream2 { + let impls = def.host_funcs.iter().map(|f| { + // skip the context and memory argument + let params = f.item.sig.inputs.iter().skip(2); + let cfg = &f.cfg; + let name = &f.name; + let body = &f.item.block; + let output = &f.item.sig.output; + + let name = Ident::new(&format!("bench_{name}"), Span::call_site()); + quote! { + #cfg + pub fn #name(&mut self, memory: &mut M, #(#params),*) #output { + #body + } + } + }); + + quote! { + #( #impls )* + } +} + +fn expand_func_doc(def: &EnvDef) -> TokenStream2 { + let docs = def.host_funcs.iter().map(|func| { + // Remove auxiliary args: `ctx: _` and `memory: _` + let func_decl = { + let mut sig = func.item.sig.clone(); + sig.inputs = sig + .inputs + .iter() + .skip(2) + .map(|p| p.clone()) + .collect::>(); + sig.output = func.returns.success_type(); + sig.to_token_stream() + }; + let func_doc = { + let func_docs = { + let docs = func.item.attrs.iter().filter(|a| a.path().is_ident("doc")).map(|d| { + let docs = d.to_token_stream(); + quote! { #docs } + }); + quote! { #( #docs )* } + }; + let availability = if let Some(version) = func.api_version { + let info = format!( + "\n# Required API version\nThis API was added in version **{version}**.", + ); + quote! { #[doc = #info] } + } else { + let info = + "\n# Unstable API\nThis API is not standardized and only available for testing."; + quote! { #[doc = #info] } + }; + quote! { + #func_docs + #availability + } + }; + quote! { + #func_doc + #func_decl; + } + }); + + quote! { + #( #docs )* + } +} diff --git a/substrate/frame/revive/src/address.rs b/substrate/frame/revive/src/address.rs new file mode 100644 index 000000000000..5758daf7b1ff --- /dev/null +++ b/substrate/frame/revive/src/address.rs @@ -0,0 +1,68 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Functions that deal with address derivation. + +use crate::{CodeHash, Config}; +use codec::{Decode, Encode}; +use sp_runtime::traits::{Hash, TrailingZeroInput}; + +/// Provides the contract address generation method. +/// +/// See [`DefaultAddressGenerator`] for the default implementation. +/// +/// # Note for implementors +/// +/// 1. Make sure that there are no collisions, different inputs never lead to the same output. +/// 2. Make sure that the same inputs lead to the same output. +pub trait AddressGenerator { + /// The address of a contract based on the given instantiate parameters. + /// + /// Changing the formular for an already deployed chain is fine as long as no collisions + /// with the old formular. Changes only affect existing contracts. + fn contract_address( + deploying_address: &T::AccountId, + code_hash: &CodeHash, + input_data: &[u8], + salt: &[u8], + ) -> T::AccountId; +} + +/// Default address generator. +/// +/// This is the default address generator used by contract instantiation. Its result +/// is only dependent on its inputs. It can therefore be used to reliably predict the +/// address of a contract. This is akin to the formula of eth's CREATE2 opcode. There +/// is no CREATE equivalent because CREATE2 is strictly more powerful. +/// Formula: +/// `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)` +pub struct DefaultAddressGenerator; + +impl AddressGenerator for DefaultAddressGenerator { + /// Formula: `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)` + fn contract_address( + deploying_address: &T::AccountId, + code_hash: &CodeHash, + input_data: &[u8], + salt: &[u8], + ) -> T::AccountId { + let entropy = (b"contract_addr_v1", deploying_address, code_hash, input_data, salt) + .using_encoded(T::Hashing::hash); + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") + } +} diff --git a/substrate/frame/revive/src/benchmarking/call_builder.rs b/substrate/frame/revive/src/benchmarking/call_builder.rs new file mode 100644 index 000000000000..654ba3de4f72 --- /dev/null +++ b/substrate/frame/revive/src/benchmarking/call_builder.rs @@ -0,0 +1,217 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + benchmarking::{default_deposit_limit, Contract, WasmModule}, + exec::{ExportedFunction, Ext, Key, Stack}, + storage::meter::Meter, + transient_storage::MeterEntry, + wasm::{ApiVersion, PreparedCall, Runtime}, + BalanceOf, Config, DebugBuffer, Error, GasMeter, Origin, TypeInfo, WasmBlob, Weight, +}; +use alloc::{vec, vec::Vec}; +use codec::{Encode, HasCompact}; +use core::fmt::Debug; +use frame_benchmarking::benchmarking; + +type StackExt<'a, T> = Stack<'a, T, WasmBlob>; + +/// A builder used to prepare a contract call. +pub struct CallSetup { + contract: Contract, + dest: T::AccountId, + origin: Origin, + gas_meter: GasMeter, + storage_meter: Meter, + value: BalanceOf, + debug_message: Option, + data: Vec, + transient_storage_size: u32, +} + +impl Default for CallSetup +where + T: Config + pallet_balances::Config, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, +{ + fn default() -> Self { + Self::new(WasmModule::dummy()) + } +} + +impl CallSetup +where + T: Config + pallet_balances::Config, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, +{ + /// Setup a new call for the given module. + pub fn new(module: WasmModule) -> Self { + let contract = Contract::::new(module.clone(), vec![]).unwrap(); + let dest = contract.account_id.clone(); + let origin = Origin::from_account_id(contract.caller.clone()); + + let storage_meter = Meter::new(&origin, default_deposit_limit::(), 0u32.into()).unwrap(); + + // Whitelist contract account, as it is already accounted for in the call benchmark + benchmarking::add_to_whitelist( + frame_system::Account::::hashed_key_for(&contract.account_id).into(), + ); + + // Whitelist the contract's contractInfo as it is already accounted for in the call + // benchmark + benchmarking::add_to_whitelist( + crate::ContractInfoOf::::hashed_key_for(&contract.account_id).into(), + ); + + Self { + contract, + dest, + origin, + gas_meter: GasMeter::new(Weight::MAX), + storage_meter, + value: 0u32.into(), + debug_message: None, + data: vec![], + transient_storage_size: 0, + } + } + + /// Set the meter's storage deposit limit. + pub fn set_storage_deposit_limit(&mut self, balance: BalanceOf) { + self.storage_meter = Meter::new(&self.origin, balance, 0u32.into()).unwrap(); + } + + /// Set the call's origin. + pub fn set_origin(&mut self, origin: Origin) { + self.origin = origin; + } + + /// Set the contract's balance. + pub fn set_balance(&mut self, value: BalanceOf) { + self.contract.set_balance(value); + } + + /// Set the call's input data. + pub fn set_data(&mut self, value: Vec) { + self.data = value; + } + + /// Set the transient storage size. + pub fn set_transient_storage_size(&mut self, size: u32) { + self.transient_storage_size = size; + } + + /// Set the debug message. + pub fn enable_debug_message(&mut self) { + self.debug_message = Some(Default::default()); + } + + /// Get the debug message. + pub fn debug_message(&self) -> Option { + self.debug_message.clone() + } + + /// Get the call's input data. + pub fn data(&self) -> Vec { + self.data.clone() + } + + /// Get the call's contract. + pub fn contract(&self) -> Contract { + self.contract.clone() + } + + /// Build the call stack. + pub fn ext(&mut self) -> (StackExt<'_, T>, WasmBlob) { + let mut ext = StackExt::bench_new_call( + self.dest.clone(), + self.origin.clone(), + &mut self.gas_meter, + &mut self.storage_meter, + self.value, + self.debug_message.as_mut(), + ); + if self.transient_storage_size > 0 { + Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap(); + } + ext + } + + /// Prepare a call to the module. + pub fn prepare_call<'a>( + ext: &'a mut StackExt<'a, T>, + module: WasmBlob, + input: Vec, + ) -> PreparedCall<'a, StackExt<'a, T>> { + module + .prepare_call( + Runtime::new(ext, input), + ExportedFunction::Call, + ApiVersion::UnsafeNewest, + ) + .unwrap() + } + + /// Add transient_storage + fn with_transient_storage(ext: &mut StackExt, size: u32) -> Result<(), &'static str> { + let &MeterEntry { amount, limit } = ext.transient_storage().meter().current(); + ext.transient_storage().meter().current_mut().limit = size; + for i in 1u32.. { + let mut key_data = i.to_le_bytes().to_vec(); + while key_data.last() == Some(&0) { + key_data.pop(); + } + let key = Key::try_from_var(key_data).unwrap(); + if let Err(e) = ext.set_transient_storage(&key, Some(Vec::new()), false) { + // Restore previous settings. + ext.transient_storage().meter().current_mut().limit = limit; + ext.transient_storage().meter().current_mut().amount = amount; + if e == Error::::OutOfTransientStorage.into() { + break; + } else { + return Err("Initialization of the transient storage failed"); + } + } + } + Ok(()) + } +} + +#[macro_export] +macro_rules! memory( + ($($bytes:expr,)*) => {{ + vec![].iter()$(.chain($bytes.iter()))*.cloned().collect::>() + }}; +); + +#[macro_export] +macro_rules! build_runtime( + ($runtime:ident, $memory:ident: [$($segment:expr,)*]) => { + $crate::build_runtime!($runtime, _contract, $memory: [$($segment,)*]); + }; + ($runtime:ident, $contract:ident, $memory:ident: [$($bytes:expr,)*]) => { + $crate::build_runtime!($runtime, $contract); + let mut $memory = $crate::memory!($($bytes,)*); + }; + ($runtime:ident, $contract:ident) => { + let mut setup = CallSetup::::default(); + let $contract = setup.contract(); + let input = setup.data(); + let (mut ext, _) = setup.ext(); + let mut $runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, input); + }; +); diff --git a/substrate/frame/revive/src/benchmarking/code.rs b/substrate/frame/revive/src/benchmarking/code.rs new file mode 100644 index 000000000000..eba4710d8a2c --- /dev/null +++ b/substrate/frame/revive/src/benchmarking/code.rs @@ -0,0 +1,69 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Functions to procedurally construct contract code used for benchmarking. +//! +//! In order to be able to benchmark events that are triggered by contract execution +//! (API calls into seal, individual instructions), we need to generate contracts that +//! perform those events. Because those contracts can get very big we cannot simply define +//! them as text (.wat) as this will be too slow and consume too much memory. Therefore +//! we define this simple definition of a contract that can be passed to `create_code` that +//! compiles it down into a `WasmModule` that can be used as a contract's code. + +use crate::Config; +use alloc::vec::Vec; +use pallet_revive_fixtures::bench as bench_fixtures; +use sp_runtime::traits::Hash; + +/// A wasm module ready to be put on chain. +#[derive(Clone)] +pub struct WasmModule { + pub code: Vec, + pub hash: ::Output, +} + +impl WasmModule { + /// Return a contract code that does nothing. + pub fn dummy() -> Self { + Self::new(bench_fixtures::DUMMY.to_vec()) + } + + /// Same as [`Self::dummy`] but uses `replace_with` to make the code unique. + pub fn dummy_unique(replace_with: u32) -> Self { + Self::new(bench_fixtures::dummy_unique(replace_with)) + } + + /// A contract code of specified sizte that does nothing. + pub fn sized(_size: u32) -> Self { + Self::dummy() + } + + /// A contract code that calls the "noop" host function in a loop depending in the input. + pub fn noop() -> Self { + Self::new(bench_fixtures::NOOP.to_vec()) + } + + /// A contract code that executes some ALU instructions in a loop. + pub fn instr() -> Self { + Self::new(bench_fixtures::INSTR.to_vec()) + } + + fn new(code: Vec) -> Self { + let hash = T::Hashing::hash(&code); + Self { code, hash } + } +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs new file mode 100644 index 000000000000..b4bd028d6f03 --- /dev/null +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -0,0 +1,1815 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarks for the contracts pallet + +#![cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] + +mod call_builder; +mod code; +use self::{call_builder::CallSetup, code::WasmModule}; +use crate::{ + exec::{Key, MomentOf}, + limits, + migration::codegen::LATEST_MIGRATION_VERSION, + storage::WriteOutcome, + Pallet as Contracts, *, +}; +use alloc::{vec, vec::Vec}; +use codec::{Encode, MaxEncodedLen}; +use frame_benchmarking::v2::*; +use frame_support::{ + self, assert_ok, + pallet_prelude::StorageVersion, + storage::child, + traits::{fungible::InspectHold, Currency}, + weights::{Weight, WeightMeter}, +}; +use frame_system::RawOrigin; +use pallet_balances; +use pallet_revive_uapi::{CallFlags, ReturnErrorCode, StorageFlags}; +use sp_runtime::traits::{Bounded, Hash}; + +/// How many runs we do per API benchmark. +/// +/// This is picked more or less arbitrary. We experimented with different numbers until +/// the results appeared to be stable. Reducing the number would speed up the benchmarks +/// but might make the results less precise. +const API_BENCHMARK_RUNS: u32 = 1600; + +/// How many runs we do per instruction benchmark. +/// +/// Same rationale as for [`API_BENCHMARK_RUNS`]. The number is bigger because instruction +/// benchmarks are faster. +const INSTR_BENCHMARK_RUNS: u32 = 5000; + +/// Number of layers in a Radix16 unbalanced trie. +const UNBALANCED_TRIE_LAYERS: u32 = 20; + +/// An instantiated and deployed contract. +#[derive(Clone)] +struct Contract { + caller: T::AccountId, + account_id: T::AccountId, + addr: AccountIdLookupOf, +} + +impl Contract +where + T: Config + pallet_balances::Config, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, +{ + /// Create new contract and use a default account id as instantiator. + fn new(module: WasmModule, data: Vec) -> Result, &'static str> { + Self::with_index(0, module, data) + } + + /// Create new contract and use an account id derived from the supplied index as instantiator. + fn with_index( + index: u32, + module: WasmModule, + data: Vec, + ) -> Result, &'static str> { + Self::with_caller(account("instantiator", index, 0), module, data) + } + + /// Create new contract and use the supplied `caller` as instantiator. + fn with_caller( + caller: T::AccountId, + module: WasmModule, + data: Vec, + ) -> Result, &'static str> { + T::Currency::set_balance(&caller, caller_funding::()); + let salt = vec![0xff]; + + let outcome = Contracts::::bare_instantiate( + RawOrigin::Signed(caller.clone()).into(), + 0u32.into(), + Weight::MAX, + default_deposit_limit::(), + Code::Upload(module.code), + data, + salt, + DebugInfo::Skip, + CollectEvents::Skip, + ); + + let addr = outcome.result?.account_id; + let result = Contract { caller, account_id: addr.clone(), addr: T::Lookup::unlookup(addr) }; + + ContractInfoOf::::insert(&result.account_id, result.info()?); + + Ok(result) + } + + /// Create a new contract with the supplied storage item count and size each. + fn with_storage( + code: WasmModule, + stor_num: u32, + stor_size: u32, + ) -> Result { + let contract = Contract::::new(code, vec![])?; + let storage_items = (0..stor_num) + .map(|i| { + let hash = T::Hashing::hash_of(&i) + .as_ref() + .try_into() + .map_err(|_| "Hash too big for storage key")?; + Ok((hash, vec![42u8; stor_size as usize])) + }) + .collect::, &'static str>>()?; + contract.store(&storage_items)?; + Ok(contract) + } + + /// Store the supplied storage items into this contracts storage. + fn store(&self, items: &Vec<([u8; 32], Vec)>) -> Result<(), &'static str> { + let info = self.info()?; + for item in items { + info.write(&Key::Fix(item.0), Some(item.1.clone()), None, false) + .map_err(|_| "Failed to write storage to restoration dest")?; + } + >::insert(&self.account_id, info); + Ok(()) + } + + /// Create a new contract with the specified unbalanced storage trie. + fn with_unbalanced_storage_trie(code: WasmModule, key: &[u8]) -> Result { + if (key.len() as u32) < (UNBALANCED_TRIE_LAYERS + 1) / 2 { + return Err("Key size too small to create the specified trie"); + } + + let value = vec![16u8; limits::PAYLOAD_BYTES as usize]; + let contract = Contract::::new(code, vec![])?; + let info = contract.info()?; + let child_trie_info = info.child_trie_info(); + child::put_raw(&child_trie_info, &key, &value); + for l in 0..UNBALANCED_TRIE_LAYERS { + let pos = l as usize / 2; + let mut key_new = key.to_vec(); + for i in 0u8..16 { + key_new[pos] = if l % 2 == 0 { + (key_new[pos] & 0xF0) | i + } else { + (key_new[pos] & 0x0F) | (i << 4) + }; + + if key == &key_new { + continue + } + child::put_raw(&child_trie_info, &key_new, &value); + } + } + Ok(contract) + } + + /// Get the `ContractInfo` of the `addr` or an error if it no longer exists. + fn address_info(addr: &T::AccountId) -> Result, &'static str> { + ContractInfoOf::::get(addr).ok_or("Expected contract to exist at this point.") + } + + /// Get the `ContractInfo` of this contract or an error if it no longer exists. + fn info(&self) -> Result, &'static str> { + Self::address_info(&self.account_id) + } + + /// Set the balance of the contract to the supplied amount. + fn set_balance(&self, balance: BalanceOf) { + T::Currency::set_balance(&self.account_id, balance); + } + + /// Returns `true` iff all storage entries related to code storage exist. + fn code_exists(hash: &CodeHash) -> bool { + >::contains_key(hash) && >::contains_key(&hash) + } + + /// Returns `true` iff no storage entry related to code storage exist. + fn code_removed(hash: &CodeHash) -> bool { + !>::contains_key(hash) && !>::contains_key(&hash) + } +} + +/// The funding that each account that either calls or instantiates contracts is funded with. +fn caller_funding() -> BalanceOf { + // Minting can overflow, so we can't abuse of the funding. This value happens to be big enough, + // but not too big to make the total supply overflow. + BalanceOf::::max_value() / 10_000u32.into() +} + +/// The deposit limit we use for benchmarks. +fn default_deposit_limit() -> BalanceOf { + (T::DepositPerByte::get() * 1024u32.into() * 1024u32.into()) + + T::DepositPerItem::get() * 1024u32.into() +} + +#[benchmarks( + where + as codec::HasCompact>::Type: Clone + Eq + PartialEq + core::fmt::Debug + scale_info::TypeInfo + codec::Encode, + T: Config + pallet_balances::Config, + BalanceOf: From< as Currency>::Balance>, + as Currency>::Balance: From>, +)] +mod benchmarks { + use super::*; + + // The base weight consumed on processing contracts deletion queue. + #[benchmark(pov_mode = Measured)] + fn on_process_deletion_queue_batch() { + #[block] + { + ContractInfo::::process_deletion_queue_batch(&mut WeightMeter::new()) + } + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn on_initialize_per_trie_key(k: Linear<0, 1024>) -> Result<(), BenchmarkError> { + let instance = Contract::::with_storage(WasmModule::dummy(), k, limits::PAYLOAD_BYTES)?; + instance.info()?.queue_trie_for_deletion(); + + #[block] + { + ContractInfo::::process_deletion_queue_batch(&mut WeightMeter::new()) + } + + Ok(()) + } + + // This benchmarks the weight of executing Migration::migrate to execute a noop migration. + #[benchmark(pov_mode = Measured)] + fn migration_noop() { + let version = LATEST_MIGRATION_VERSION; + StorageVersion::new(version).put::>(); + #[block] + { + Migration::::migrate(&mut WeightMeter::new()); + } + assert_eq!(StorageVersion::get::>(), version); + } + + // This benchmarks the weight of dispatching migrate to execute 1 `NoopMigration` + #[benchmark(pov_mode = Measured)] + fn migrate() { + let latest_version = LATEST_MIGRATION_VERSION; + StorageVersion::new(latest_version - 2).put::>(); + as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); + + #[extrinsic_call] + _(RawOrigin::Signed(whitelisted_caller()), Weight::MAX); + + assert_eq!(StorageVersion::get::>(), latest_version - 1); + } + + // This benchmarks the weight of running on_runtime_upgrade when there are no migration in + // progress. + #[benchmark(pov_mode = Measured)] + fn on_runtime_upgrade_noop() { + let latest_version = LATEST_MIGRATION_VERSION; + StorageVersion::new(latest_version).put::>(); + #[block] + { + as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); + } + assert!(MigrationInProgress::::get().is_none()); + } + + // This benchmarks the weight of running on_runtime_upgrade when there is a migration in + // progress. + #[benchmark(pov_mode = Measured)] + fn on_runtime_upgrade_in_progress() { + let latest_version = LATEST_MIGRATION_VERSION; + StorageVersion::new(latest_version - 2).put::>(); + let v = vec![42u8].try_into().ok(); + MigrationInProgress::::set(v.clone()); + #[block] + { + as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); + } + assert!(MigrationInProgress::::get().is_some()); + assert_eq!(MigrationInProgress::::get(), v); + } + + // This benchmarks the weight of running on_runtime_upgrade when there is a migration to + // process. + #[benchmark(pov_mode = Measured)] + fn on_runtime_upgrade() { + let latest_version = LATEST_MIGRATION_VERSION; + StorageVersion::new(latest_version - 2).put::>(); + #[block] + { + as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); + } + assert!(MigrationInProgress::::get().is_some()); + } + + // This benchmarks the overhead of loading a code of size `c` byte from storage and into + // the execution engine. This does **not** include the actual execution for which the gas meter + // is responsible. This is achieved by generating all code to the `deploy` function + // which is in the wasm module but not executed on `call`. + // The results are supposed to be used as `call_with_code_per_byte(c) - + // call_with_code_per_byte(0)`. + #[benchmark(pov_mode = Measured)] + fn call_with_code_per_byte( + c: Linear<0, { T::MaxCodeLen::get() }>, + ) -> Result<(), BenchmarkError> { + let instance = + Contract::::with_caller(whitelisted_caller(), WasmModule::sized(c), vec![])?; + let value = Pallet::::min_balance(); + let callee = instance.addr; + let storage_deposit = default_deposit_limit::(); + + #[extrinsic_call] + call( + RawOrigin::Signed(instance.caller.clone()), + callee, + value, + Weight::MAX, + storage_deposit, + vec![], + ); + + Ok(()) + } + + // `c`: Size of the code in bytes. + // `i`: Size of the input in bytes. + // `s`: Size of the salt in bytes. + #[benchmark(pov_mode = Measured)] + fn instantiate_with_code( + c: Linear<0, { T::MaxCodeLen::get() }>, + i: Linear<0, { limits::MEMORY_BYTES }>, + s: Linear<0, { limits::MEMORY_BYTES }>, + ) { + let input = vec![42u8; i as usize]; + let salt = vec![42u8; s as usize]; + let value = Pallet::::min_balance(); + let caller = whitelisted_caller(); + T::Currency::set_balance(&caller, caller_funding::()); + let WasmModule { code, hash, .. } = WasmModule::::sized(c); + let origin = RawOrigin::Signed(caller.clone()); + let addr = Contracts::::contract_address(&caller, &hash, &input, &salt); + let storage_deposit = default_deposit_limit::(); + #[extrinsic_call] + _(origin, value, Weight::MAX, storage_deposit, code, input, salt); + + let deposit = + T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &addr); + // uploading the code reserves some balance in the callers account + let code_deposit = + T::Currency::balance_on_hold(&HoldReason::CodeUploadDepositReserve.into(), &caller); + assert_eq!( + T::Currency::balance(&caller), + caller_funding::() - value - deposit - code_deposit - Pallet::::min_balance(), + ); + // contract has the full value + assert_eq!(T::Currency::balance(&addr), value + Pallet::::min_balance()); + } + + // `i`: Size of the input in bytes. + // `s`: Size of the salt in bytes. + #[benchmark(pov_mode = Measured)] + fn instantiate( + i: Linear<0, { limits::MEMORY_BYTES }>, + s: Linear<0, { limits::MEMORY_BYTES }>, + ) -> Result<(), BenchmarkError> { + let input = vec![42u8; i as usize]; + let salt = vec![42u8; s as usize]; + let value = Pallet::::min_balance(); + let caller = whitelisted_caller(); + T::Currency::set_balance(&caller, caller_funding::()); + let origin = RawOrigin::Signed(caller.clone()); + let WasmModule { code, .. } = WasmModule::::dummy(); + let storage_deposit = default_deposit_limit::(); + let hash = + Contracts::::bare_upload_code(origin.into(), code, storage_deposit)?.code_hash; + let addr = Contracts::::contract_address(&caller, &hash, &input, &salt); + + #[extrinsic_call] + _( + RawOrigin::Signed(caller.clone()), + value, + Weight::MAX, + storage_deposit, + hash, + input, + salt, + ); + + let deposit = + T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &addr); + let code_deposit = + T::Currency::balance_on_hold(&HoldReason::CodeUploadDepositReserve.into(), &caller); + // value was removed from the caller + assert_eq!( + T::Currency::balance(&caller), + caller_funding::() - value - deposit - code_deposit - Pallet::::min_balance(), + ); + // contract has the full value + assert_eq!(T::Currency::balance(&addr), value + Pallet::::min_balance()); + + Ok(()) + } + + // We just call a dummy contract to measure the overhead of the call extrinsic. + // The size of the data has no influence on the costs of this extrinsic as long as the contract + // won't call `seal_input` in its constructor to copy the data to contract memory. + // The dummy contract used here does not do this. The costs for the data copy is billed as + // part of `seal_input`. The costs for invoking a contract of a specific size are not part + // of this benchmark because we cannot know the size of the contract when issuing a call + // transaction. See `call_with_code_per_byte` for this. + #[benchmark(pov_mode = Measured)] + fn call() -> Result<(), BenchmarkError> { + let data = vec![42u8; 1024]; + let instance = + Contract::::with_caller(whitelisted_caller(), WasmModule::dummy(), vec![])?; + let value = Pallet::::min_balance(); + let origin = RawOrigin::Signed(instance.caller.clone()); + let callee = instance.addr.clone(); + let before = T::Currency::balance(&instance.account_id); + let storage_deposit = default_deposit_limit::(); + #[extrinsic_call] + _(origin, callee, value, Weight::MAX, storage_deposit, data); + let deposit = T::Currency::balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &instance.account_id, + ); + let code_deposit = T::Currency::balance_on_hold( + &HoldReason::CodeUploadDepositReserve.into(), + &instance.caller, + ); + // value and value transferred via call should be removed from the caller + assert_eq!( + T::Currency::balance(&instance.caller), + caller_funding::() - value - deposit - code_deposit - Pallet::::min_balance(), + ); + // contract should have received the value + assert_eq!(T::Currency::balance(&instance.account_id), before + value); + // contract should still exist + instance.info()?; + + Ok(()) + } + + // This constructs a contract that is maximal expensive to instrument. + // It creates a maximum number of metering blocks per byte. + // `c`: Size of the code in bytes. + #[benchmark(pov_mode = Measured)] + fn upload_code(c: Linear<0, { T::MaxCodeLen::get() }>) { + let caller = whitelisted_caller(); + T::Currency::set_balance(&caller, caller_funding::()); + let WasmModule { code, hash, .. } = WasmModule::::sized(c); + let origin = RawOrigin::Signed(caller.clone()); + let storage_deposit = default_deposit_limit::(); + #[extrinsic_call] + _(origin, code, storage_deposit); + // uploading the code reserves some balance in the callers account + assert!(T::Currency::total_balance_on_hold(&caller) > 0u32.into()); + assert!(>::code_exists(&hash)); + } + + // Removing code does not depend on the size of the contract because all the information + // needed to verify the removal claim (refcount, owner) is stored in a separate storage + // item (`CodeInfoOf`). + #[benchmark(pov_mode = Measured)] + fn remove_code() -> Result<(), BenchmarkError> { + let caller = whitelisted_caller(); + T::Currency::set_balance(&caller, caller_funding::()); + let WasmModule { code, hash, .. } = WasmModule::::dummy(); + let origin = RawOrigin::Signed(caller.clone()); + let storage_deposit = default_deposit_limit::(); + let uploaded = + >::bare_upload_code(origin.clone().into(), code, storage_deposit)?; + assert_eq!(uploaded.code_hash, hash); + assert_eq!(uploaded.deposit, T::Currency::total_balance_on_hold(&caller)); + assert!(>::code_exists(&hash)); + #[extrinsic_call] + _(origin, hash); + // removing the code should have unreserved the deposit + assert_eq!(T::Currency::total_balance_on_hold(&caller), 0u32.into()); + assert!(>::code_removed(&hash)); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn set_code() -> Result<(), BenchmarkError> { + let instance = + >::with_caller(whitelisted_caller(), WasmModule::dummy(), vec![])?; + // we just add some bytes so that the code hash is different + let WasmModule { code, .. } = >::dummy_unique(128); + let origin = RawOrigin::Signed(instance.caller.clone()); + let storage_deposit = default_deposit_limit::(); + let hash = + >::bare_upload_code(origin.into(), code, storage_deposit)?.code_hash; + let callee = instance.addr.clone(); + assert_ne!(instance.info()?.code_hash, hash); + #[extrinsic_call] + _(RawOrigin::Root, callee, hash); + assert_eq!(instance.info()?.code_hash, hash); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn noop_host_fn(r: Linear<0, API_BENCHMARK_RUNS>) { + let mut setup = CallSetup::::new(WasmModule::noop()); + let (mut ext, module) = setup.ext(); + let prepared = CallSetup::::prepare_call(&mut ext, module, r.encode()); + #[block] + { + prepared.call().unwrap(); + } + } + + #[benchmark(pov_mode = Measured)] + fn seal_caller() { + let len = ::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + + let result; + #[block] + { + result = runtime.bench_caller(memory.as_mut_slice(), 4, 0); + } + + assert_ok!(result); + assert_eq!( + &::decode(&mut &memory[4..]).unwrap(), + runtime.ext().caller().account_id().unwrap() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_is_contract() { + let Contract { account_id, .. } = + Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); + + build_runtime!(runtime, memory: [account_id.encode(), ]); + + let result; + #[block] + { + result = runtime.bench_is_contract(memory.as_mut_slice(), 0); + } + + assert_eq!(result.unwrap(), 1); + } + + #[benchmark(pov_mode = Measured)] + fn seal_code_hash() { + let contract = Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); + let len = as MaxEncodedLen>::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], contract.account_id.encode(), ]); + + let result; + #[block] + { + result = runtime.bench_code_hash(memory.as_mut_slice(), 4 + len, 4, 0); + } + + assert_ok!(result); + assert_eq!( + as Decode>::decode(&mut &memory[4..]).unwrap(), + contract.info().unwrap().code_hash + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_own_code_hash() { + let len = as MaxEncodedLen>::max_encoded_len() as u32; + build_runtime!(runtime, contract, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let result; + #[block] + { + result = runtime.bench_own_code_hash(memory.as_mut_slice(), 4, 0); + } + + assert_ok!(result); + assert_eq!( + as Decode>::decode(&mut &memory[4..]).unwrap(), + contract.info().unwrap().code_hash + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_caller_is_origin() { + build_runtime!(runtime, memory: []); + + let result; + #[block] + { + result = runtime.bench_caller_is_origin(memory.as_mut_slice()); + } + assert_eq!(result.unwrap(), 1u32); + } + + #[benchmark(pov_mode = Measured)] + fn seal_caller_is_root() { + let mut setup = CallSetup::::default(); + setup.set_origin(Origin::Root); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![]); + + let result; + #[block] + { + result = runtime.bench_caller_is_root([0u8; 0].as_mut_slice()); + } + assert_eq!(result.unwrap(), 1u32); + } + + #[benchmark(pov_mode = Measured)] + fn seal_address() { + let len = as MaxEncodedLen>::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + + let result; + #[block] + { + result = runtime.bench_address(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!( + &::decode(&mut &memory[4..]).unwrap(), + runtime.ext().address() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_weight_left() { + // use correct max_encoded_len when new version of parity-scale-codec is released + let len = 18u32; + assert!(::max_encoded_len() as u32 != len); + build_runtime!(runtime, memory: [32u32.to_le_bytes(), vec![0u8; len as _], ]); + + let result; + #[block] + { + result = runtime.bench_weight_left(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!( + ::decode(&mut &memory[4..]).unwrap(), + runtime.ext().gas_meter().gas_left() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_balance() { + let len = ::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let result; + #[block] + { + result = runtime.bench_balance(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!( + ::decode(&mut &memory[4..]).unwrap(), + runtime.ext().balance().into() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_value_transferred() { + let len = ::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let result; + #[block] + { + result = runtime.bench_value_transferred(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!( + ::decode(&mut &memory[4..]).unwrap(), + runtime.ext().value_transferred().into() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_minimum_balance() { + let len = ::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let result; + #[block] + { + result = runtime.bench_minimum_balance(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!( + ::decode(&mut &memory[4..]).unwrap(), + runtime.ext().minimum_balance().into() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_block_number() { + let len = as MaxEncodedLen>::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let result; + #[block] + { + result = runtime.bench_block_number(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!( + >::decode(&mut &memory[4..]).unwrap(), + runtime.ext().block_number() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_now() { + let len = as MaxEncodedLen>::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let result; + #[block] + { + result = runtime.bench_now(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!(>::decode(&mut &memory[4..]).unwrap(), *runtime.ext().now()); + } + + #[benchmark(pov_mode = Measured)] + fn seal_weight_to_fee() { + let len = ::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let weight = Weight::from_parts(500_000, 300_000); + let result; + #[block] + { + result = runtime.bench_weight_to_fee( + memory.as_mut_slice(), + weight.ref_time(), + weight.proof_size(), + 4, + 0, + ); + } + assert_ok!(result); + assert_eq!( + >::decode(&mut &memory[4..]).unwrap(), + runtime.ext().get_weight_price(weight) + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_input(n: Linear<0, { limits::MEMORY_BYTES - 4 }>) { + let mut setup = CallSetup::::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![42u8; n as usize]); + let mut memory = memory!(n.to_le_bytes(), vec![0u8; n as usize],); + let result; + #[block] + { + result = runtime.bench_input(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!(&memory[4..], &vec![42u8; n as usize]); + } + + #[benchmark(pov_mode = Measured)] + fn seal_return(n: Linear<0, { limits::MEMORY_BYTES - 4 }>) { + build_runtime!(runtime, memory: [n.to_le_bytes(), vec![42u8; n as usize], ]); + + let result; + #[block] + { + result = runtime.bench_seal_return(memory.as_mut_slice(), 0, 0, n); + } + + assert!(matches!( + result, + Err(crate::wasm::TrapReason::Return(crate::wasm::ReturnData { .. })) + )); + } + + #[benchmark(pov_mode = Measured)] + fn seal_terminate( + n: Linear<0, { limits::DELEGATE_DEPENDENCIES }>, + ) -> Result<(), BenchmarkError> { + let beneficiary = account::("beneficiary", 0, 0); + let caller = whitelisted_caller(); + T::Currency::set_balance(&caller, caller_funding::()); + let origin = RawOrigin::Signed(caller); + let storage_deposit = default_deposit_limit::(); + + build_runtime!(runtime, memory: [beneficiary.encode(),]); + + (0..n).for_each(|i| { + let new_code = WasmModule::::dummy_unique(65 + i); + Contracts::::bare_upload_code(origin.clone().into(), new_code.code, storage_deposit) + .unwrap(); + runtime.ext().lock_delegate_dependency(new_code.hash).unwrap(); + }); + + let result; + #[block] + { + result = runtime.bench_terminate(memory.as_mut_slice(), 0); + } + + assert!(matches!(result, Err(crate::wasm::TrapReason::Termination))); + + Ok(()) + } + + // Benchmark the overhead that topics generate. + // `t`: Number of topics + // `n`: Size of event payload in bytes + #[benchmark(pov_mode = Measured)] + fn seal_deposit_event( + t: Linear<0, { limits::NUM_EVENT_TOPICS as u32 }>, + n: Linear<0, { limits::PAYLOAD_BYTES }>, + ) { + let topics = (0..t).map(|i| T::Hashing::hash_of(&i)).collect::>().encode(); + let topics_len = topics.len() as u32; + + build_runtime!(runtime, memory: [ + n.to_le_bytes(), + topics, + vec![0u8; n as _], + ]); + + let result; + #[block] + { + result = runtime.bench_deposit_event( + memory.as_mut_slice(), + 4, // topics_ptr + topics_len, // topics_len + 4 + topics_len, // data_ptr + 0, // data_len + ); + } + + assert_ok!(result); + } + + // Benchmark debug_message call + // Whereas this function is used in RPC mode only, it still should be secured + // against an excessive use. + // + // i: size of input in bytes up to maximum allowed contract memory or maximum allowed debug + // buffer size, whichever is less. + #[benchmark] + fn seal_debug_message( + i: Linear<0, { (limits::MEMORY_BYTES).min(limits::DEBUG_BUFFER_BYTES) }>, + ) { + let mut setup = CallSetup::::default(); + setup.enable_debug_message(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + // Fill memory with printable ASCII bytes. + let mut memory = (0..i).zip((32..127).cycle()).map(|i| i.1).collect::>(); + + let result; + #[block] + { + result = runtime.bench_debug_message(memory.as_mut_slice(), 0, i); + } + assert_ok!(result); + assert_eq!(setup.debug_message().unwrap().len() as u32, i); + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn get_storage_empty() -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = vec![0u8; max_key_len as usize]; + let max_value_len = limits::PAYLOAD_BYTES as usize; + let value = vec![1u8; max_value_len]; + + let instance = Contract::::new(WasmModule::dummy(), vec![])?; + let info = instance.info()?; + let child_trie_info = info.child_trie_info(); + info.bench_write_raw(&key, Some(value.clone()), false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let result; + #[block] + { + result = child::get_raw(&child_trie_info, &key); + } + + assert_eq!(result, Some(value)); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn get_storage_full() -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = vec![0u8; max_key_len as usize]; + let max_value_len = limits::PAYLOAD_BYTES; + let value = vec![1u8; max_value_len as usize]; + + let instance = Contract::::with_unbalanced_storage_trie(WasmModule::dummy(), &key)?; + let info = instance.info()?; + let child_trie_info = info.child_trie_info(); + info.bench_write_raw(&key, Some(value.clone()), false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let result; + #[block] + { + result = child::get_raw(&child_trie_info, &key); + } + + assert_eq!(result, Some(value)); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn set_storage_empty() -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = vec![0u8; max_key_len as usize]; + let max_value_len = limits::PAYLOAD_BYTES as usize; + let value = vec![1u8; max_value_len]; + + let instance = Contract::::new(WasmModule::dummy(), vec![])?; + let info = instance.info()?; + let child_trie_info = info.child_trie_info(); + info.bench_write_raw(&key, Some(vec![42u8; max_value_len]), false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let val = Some(value.clone()); + let result; + #[block] + { + result = info.bench_write_raw(&key, val, true); + } + + assert_ok!(result); + assert_eq!(child::get_raw(&child_trie_info, &key).unwrap(), value); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn set_storage_full() -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = vec![0u8; max_key_len as usize]; + let max_value_len = limits::PAYLOAD_BYTES; + let value = vec![1u8; max_value_len as usize]; + + let instance = Contract::::with_unbalanced_storage_trie(WasmModule::dummy(), &key)?; + let info = instance.info()?; + let child_trie_info = info.child_trie_info(); + info.bench_write_raw(&key, Some(vec![42u8; max_value_len as usize]), false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let val = Some(value.clone()); + let result; + #[block] + { + result = info.bench_write_raw(&key, val, true); + } + + assert_ok!(result); + assert_eq!(child::get_raw(&child_trie_info, &key).unwrap(), value); + Ok(()) + } + + // n: new byte size + // o: old byte size + #[benchmark(skip_meta, pov_mode = Measured)] + fn seal_set_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + o: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + let value = vec![1u8; n as usize]; + + build_runtime!(runtime, instance, memory: [ key.unhashed(), value.clone(), ]); + let info = instance.info()?; + + info.write(&key, Some(vec![42u8; o as usize]), None, false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let result; + #[block] + { + result = runtime.bench_set_storage( + memory.as_mut_slice(), + StorageFlags::empty().bits(), + 0, // key_ptr + max_key_len, // key_len + max_key_len, // value_ptr + n, // value_len + ); + } + + assert_ok!(result); + assert_eq!(info.read(&key).unwrap(), value); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn seal_clear_storage(n: Linear<0, { limits::PAYLOAD_BYTES }>) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, instance, memory: [ key.unhashed(), ]); + let info = instance.info()?; + + info.write(&key, Some(vec![42u8; n as usize]), None, false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let result; + #[block] + { + result = runtime.bench_clear_storage( + memory.as_mut_slice(), + StorageFlags::empty().bits(), + 0, + max_key_len, + ); + } + + assert_ok!(result); + assert!(info.read(&key).is_none()); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn seal_get_storage(n: Linear<0, { limits::PAYLOAD_BYTES }>) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]); + let info = instance.info()?; + + info.write(&key, Some(vec![42u8; n as usize]), None, false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let out_ptr = max_key_len + 4; + let result; + #[block] + { + result = runtime.bench_get_storage( + memory.as_mut_slice(), + StorageFlags::empty().bits(), + 0, // key_ptr + max_key_len, // key_len + out_ptr, // out_ptr + max_key_len, // out_len_ptr + ); + } + + assert_ok!(result); + assert_eq!(&info.read(&key).unwrap(), &memory[out_ptr as usize..]); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn seal_contains_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, instance, memory: [ key.unhashed(), ]); + let info = instance.info()?; + + info.write(&key, Some(vec![42u8; n as usize]), None, false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let result; + #[block] + { + result = runtime.bench_contains_storage( + memory.as_mut_slice(), + StorageFlags::empty().bits(), + 0, + max_key_len, + ); + } + + assert_eq!(result.unwrap(), n); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn seal_take_storage(n: Linear<0, { limits::PAYLOAD_BYTES }>) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]); + let info = instance.info()?; + + let value = vec![42u8; n as usize]; + info.write(&key, Some(value.clone()), None, false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let out_ptr = max_key_len + 4; + let result; + #[block] + { + result = runtime.bench_take_storage( + memory.as_mut_slice(), + StorageFlags::empty().bits(), + 0, // key_ptr + max_key_len, // key_len + out_ptr, // out_ptr + max_key_len, // out_len_ptr + ); + } + + assert_ok!(result); + assert!(&info.read(&key).is_none()); + assert_eq!(&value, &memory[out_ptr as usize..]); + Ok(()) + } + + // We use both full and empty benchmarks here instead of benchmarking transient_storage + // (BTreeMap) directly. This approach is necessary because benchmarking this BTreeMap is very + // slow. Additionally, we use linear regression for our benchmarks, and the BTreeMap's log(n) + // complexity can introduce approximation errors. + #[benchmark(pov_mode = Ignored)] + fn set_transient_storage_empty() -> Result<(), BenchmarkError> { + let max_value_len = limits::PAYLOAD_BYTES; + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + let value = Some(vec![42u8; max_value_len as _]); + let mut setup = CallSetup::::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + let result; + #[block] + { + result = runtime.ext().set_transient_storage(&key, value, false); + } + + assert_eq!(result, Ok(WriteOutcome::New)); + assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _])); + Ok(()) + } + + #[benchmark(pov_mode = Ignored)] + fn set_transient_storage_full() -> Result<(), BenchmarkError> { + let max_value_len = limits::PAYLOAD_BYTES; + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + let value = Some(vec![42u8; max_value_len as _]); + let mut setup = CallSetup::::default(); + setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + let result; + #[block] + { + result = runtime.ext().set_transient_storage(&key, value, false); + } + + assert_eq!(result, Ok(WriteOutcome::New)); + assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _])); + Ok(()) + } + + #[benchmark(pov_mode = Ignored)] + fn get_transient_storage_empty() -> Result<(), BenchmarkError> { + let max_value_len = limits::PAYLOAD_BYTES; + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + + let mut setup = CallSetup::::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + let result; + #[block] + { + result = runtime.ext().get_transient_storage(&key); + } + + assert_eq!(result, Some(vec![42u8; max_value_len as _])); + Ok(()) + } + + #[benchmark(pov_mode = Ignored)] + fn get_transient_storage_full() -> Result<(), BenchmarkError> { + let max_value_len = limits::PAYLOAD_BYTES; + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + + let mut setup = CallSetup::::default(); + setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + let result; + #[block] + { + result = runtime.ext().get_transient_storage(&key); + } + + assert_eq!(result, Some(vec![42u8; max_value_len as _])); + Ok(()) + } + + // The weight of journal rollbacks should be taken into account when setting storage. + #[benchmark(pov_mode = Ignored)] + fn rollback_transient_storage() -> Result<(), BenchmarkError> { + let max_value_len = limits::PAYLOAD_BYTES; + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + + let mut setup = CallSetup::::default(); + setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime.ext().transient_storage().start_transaction(); + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + #[block] + { + runtime.ext().transient_storage().rollback_transaction(); + } + + assert_eq!(runtime.ext().get_transient_storage(&key), None); + Ok(()) + } + + // n: new byte size + // o: old byte size + #[benchmark(pov_mode = Measured)] + fn seal_set_transient_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + o: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + let value = vec![1u8; n as usize]; + build_runtime!(runtime, memory: [ key.unhashed(), value.clone(), ]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; o as usize]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + + let result; + #[block] + { + result = runtime.bench_set_storage( + memory.as_mut_slice(), + StorageFlags::TRANSIENT.bits(), + 0, // key_ptr + max_key_len, // key_len + max_key_len, // value_ptr + n, // value_len + ); + } + + assert_ok!(result); + assert_eq!(runtime.ext().get_transient_storage(&key).unwrap(), value); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn seal_clear_transient_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, memory: [ key.unhashed(), ]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; n as usize]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + + let result; + #[block] + { + result = runtime.bench_clear_storage( + memory.as_mut_slice(), + StorageFlags::TRANSIENT.bits(), + 0, + max_key_len, + ); + } + + assert_ok!(result); + assert!(runtime.ext().get_transient_storage(&key).is_none()); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn seal_get_transient_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; n as usize]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + + let out_ptr = max_key_len + 4; + let result; + #[block] + { + result = runtime.bench_get_storage( + memory.as_mut_slice(), + StorageFlags::TRANSIENT.bits(), + 0, // key_ptr + max_key_len, // key_len + out_ptr, // out_ptr + max_key_len, // out_len_ptr + ); + } + + assert_ok!(result); + assert_eq!( + &runtime.ext().get_transient_storage(&key).unwrap(), + &memory[out_ptr as usize..] + ); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn seal_contains_transient_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, memory: [ key.unhashed(), ]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; n as usize]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + + let result; + #[block] + { + result = runtime.bench_contains_storage( + memory.as_mut_slice(), + StorageFlags::TRANSIENT.bits(), + 0, + max_key_len, + ); + } + + assert_eq!(result.unwrap(), n); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn seal_take_transient_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let n = limits::PAYLOAD_BYTES; + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + let value = vec![42u8; n as usize]; + runtime + .ext() + .set_transient_storage(&key, Some(value.clone()), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + + let out_ptr = max_key_len + 4; + let result; + #[block] + { + result = runtime.bench_take_storage( + memory.as_mut_slice(), + StorageFlags::TRANSIENT.bits(), + 0, // key_ptr + max_key_len, // key_len + out_ptr, // out_ptr + max_key_len, // out_len_ptr + ); + } + + assert_ok!(result); + assert!(&runtime.ext().get_transient_storage(&key).is_none()); + assert_eq!(&value, &memory[out_ptr as usize..]); + Ok(()) + } + + // We transfer to unique accounts. + #[benchmark(pov_mode = Measured)] + fn seal_transfer() { + let account = account::("receiver", 0, 0); + let value = Pallet::::min_balance(); + assert!(value > 0u32.into()); + + let mut setup = CallSetup::::default(); + setup.set_balance(value); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + + let account_bytes = account.encode(); + let account_len = account_bytes.len() as u32; + let value_bytes = value.encode(); + let mut memory = memory!(account_bytes, value_bytes,); + + let result; + #[block] + { + result = runtime.bench_transfer( + memory.as_mut_slice(), + 0, // account_ptr + account_len, // value_ptr + ); + } + + assert_ok!(result); + } + + // t: with or without some value to transfer + // i: size of the input data + #[benchmark(pov_mode = Measured)] + fn seal_call(t: Linear<0, 1>, i: Linear<0, { limits::MEMORY_BYTES }>) { + let Contract { account_id: callee, .. } = + Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); + let callee_bytes = callee.encode(); + let callee_len = callee_bytes.len() as u32; + + let value: BalanceOf = t.into(); + let value_bytes = value.encode(); + + let deposit: BalanceOf = (u32::MAX - 100).into(); + let deposit_bytes = deposit.encode(); + let deposit_len = deposit_bytes.len() as u32; + + let mut setup = CallSetup::::default(); + setup.set_storage_deposit_limit(deposit); + setup.set_data(vec![42; i as usize]); + setup.set_origin(Origin::from_account_id(setup.contract().account_id.clone())); + + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes,); + + let result; + #[block] + { + result = runtime.bench_call( + memory.as_mut_slice(), + CallFlags::CLONE_INPUT.bits(), // flags + 0, // callee_ptr + 0, // ref_time_limit + 0, // proof_size_limit + callee_len, // deposit_ptr + callee_len + deposit_len, // value_ptr + 0, // input_data_ptr + 0, // input_data_len + SENTINEL, // output_ptr + 0, // output_len_ptr + ); + } + + assert_ok!(result); + } + + #[benchmark(pov_mode = Measured)] + fn seal_delegate_call() -> Result<(), BenchmarkError> { + let hash = Contract::::with_index(1, WasmModule::dummy(), vec![])?.info()?.code_hash; + + let mut setup = CallSetup::::default(); + setup.set_origin(Origin::from_account_id(setup.contract().account_id.clone())); + + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut memory = memory!(hash.encode(),); + + let result; + #[block] + { + result = runtime.bench_delegate_call( + memory.as_mut_slice(), + 0, // flags + 0, // code_hash_ptr + 0, // input_data_ptr + 0, // input_data_len + SENTINEL, // output_ptr + 0, + ); + } + + assert_ok!(result); + Ok(()) + } + + // t: value to transfer + // i: size of input in bytes + // s: size of salt in bytes + #[benchmark(pov_mode = Measured)] + fn seal_instantiate( + i: Linear<0, { limits::MEMORY_BYTES }>, + s: Linear<0, { limits::MEMORY_BYTES }>, + ) -> Result<(), BenchmarkError> { + let hash = Contract::::with_index(1, WasmModule::dummy(), vec![])?.info()?.code_hash; + let hash_bytes = hash.encode(); + let hash_len = hash_bytes.len() as u32; + + let value: BalanceOf = 1u32.into(); + let value_bytes = value.encode(); + let value_len = value_bytes.len() as u32; + + let deposit: BalanceOf = 0u32.into(); + let deposit_bytes = deposit.encode(); + let deposit_len = deposit_bytes.len() as u32; + + let mut setup = CallSetup::::default(); + setup.set_origin(Origin::from_account_id(setup.contract().account_id.clone())); + setup.set_balance(value + (Pallet::::min_balance() * 2u32.into())); + + let account_id = &setup.contract().account_id.clone(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + + let input = vec![42u8; i as _]; + let salt = vec![42u8; s as _]; + let addr = Contracts::::contract_address(&account_id, &hash, &input, &salt); + let mut memory = memory!(hash_bytes, deposit_bytes, value_bytes, input, salt,); + + let mut offset = { + let mut current = 0u32; + move |after: u32| { + current += after; + current + } + }; + + assert!(ContractInfoOf::::get(&addr).is_none()); + + let result; + #[block] + { + result = runtime.bench_instantiate( + memory.as_mut_slice(), + 0, // code_hash_ptr + 0, // ref_time_limit + 0, // proof_size_limit + offset(hash_len), // deposit_ptr + offset(deposit_len), // value_ptr + offset(value_len), // input_data_ptr + i, // input_data_len + SENTINEL, // address_ptr + 0, // address_len_ptr + SENTINEL, // output_ptr + 0, // output_len_ptr + offset(i), // salt_ptr + s, // salt_len + ); + } + + assert_ok!(result); + assert!(ContractInfoOf::::get(&addr).is_some()); + assert_eq!(T::Currency::balance(&addr), Pallet::::min_balance() + value); + Ok(()) + } + + // `n`: Input to hash in bytes + #[benchmark(pov_mode = Measured)] + fn seal_hash_sha2_256(n: Linear<0, { limits::MEMORY_BYTES }>) { + build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]); + + let result; + #[block] + { + result = runtime.bench_hash_sha2_256(memory.as_mut_slice(), 32, n, 0); + } + assert_eq!(sp_io::hashing::sha2_256(&memory[32..]), &memory[0..32]); + assert_ok!(result); + } + + // `n`: Input to hash in bytes + #[benchmark(pov_mode = Measured)] + fn seal_hash_keccak_256(n: Linear<0, { limits::MEMORY_BYTES }>) { + build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]); + + let result; + #[block] + { + result = runtime.bench_hash_keccak_256(memory.as_mut_slice(), 32, n, 0); + } + assert_eq!(sp_io::hashing::keccak_256(&memory[32..]), &memory[0..32]); + assert_ok!(result); + } + + // `n`: Input to hash in bytes + #[benchmark(pov_mode = Measured)] + fn seal_hash_blake2_256(n: Linear<0, { limits::MEMORY_BYTES }>) { + build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]); + + let result; + #[block] + { + result = runtime.bench_hash_blake2_256(memory.as_mut_slice(), 32, n, 0); + } + assert_eq!(sp_io::hashing::blake2_256(&memory[32..]), &memory[0..32]); + assert_ok!(result); + } + + // `n`: Input to hash in bytes + #[benchmark(pov_mode = Measured)] + fn seal_hash_blake2_128(n: Linear<0, { limits::MEMORY_BYTES }>) { + build_runtime!(runtime, memory: [[0u8; 16], vec![0u8; n as usize], ]); + + let result; + #[block] + { + result = runtime.bench_hash_blake2_128(memory.as_mut_slice(), 16, n, 0); + } + assert_eq!(sp_io::hashing::blake2_128(&memory[16..]), &memory[0..16]); + assert_ok!(result); + } + + // `n`: Message input length to verify in bytes. + // need some buffer so the code size does not exceed the max code size. + #[benchmark(pov_mode = Measured)] + fn seal_sr25519_verify(n: Linear<0, { T::MaxCodeLen::get() - 255 }>) { + let message = (0..n).zip((32u8..127u8).cycle()).map(|(_, c)| c).collect::>(); + let message_len = message.len() as u32; + + let key_type = sp_core::crypto::KeyTypeId(*b"code"); + let pub_key = sp_io::crypto::sr25519_generate(key_type, None); + let sig = + sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature"); + let sig = AsRef::<[u8; 64]>::as_ref(&sig).to_vec(); + let sig_len = sig.len() as u32; + + build_runtime!(runtime, memory: [sig, pub_key.to_vec(), message, ]); + + let result; + #[block] + { + result = runtime.bench_sr25519_verify( + memory.as_mut_slice(), + 0, // signature_ptr + sig_len, // pub_key_ptr + message_len, // message_len + sig_len + pub_key.len() as u32, // message_ptr + ); + } + + assert_eq!(result.unwrap(), ReturnErrorCode::Success); + } + + #[benchmark(pov_mode = Measured)] + fn seal_ecdsa_recover() { + let message_hash = sp_io::hashing::blake2_256("Hello world".as_bytes()); + let key_type = sp_core::crypto::KeyTypeId(*b"code"); + let signature = { + let pub_key = sp_io::crypto::ecdsa_generate(key_type, None); + let sig = sp_io::crypto::ecdsa_sign_prehashed(key_type, &pub_key, &message_hash) + .expect("Generates signature"); + AsRef::<[u8; 65]>::as_ref(&sig).to_vec() + }; + + build_runtime!(runtime, memory: [signature, message_hash, [0u8; 33], ]); + + let result; + #[block] + { + result = runtime.bench_ecdsa_recover( + memory.as_mut_slice(), + 0, // signature_ptr + 65, // message_hash_ptr + 65 + 32, // output_ptr + ); + } + + assert_eq!(result.unwrap(), ReturnErrorCode::Success); + } + + // Only calling the function itself for the list of + // generated different ECDSA keys. + // This is a slow call: We reduce the number of runs. + #[benchmark(pov_mode = Measured)] + fn seal_ecdsa_to_eth_address() { + let key_type = sp_core::crypto::KeyTypeId(*b"code"); + let pub_key_bytes = sp_io::crypto::ecdsa_generate(key_type, None).0; + build_runtime!(runtime, memory: [[0u8; 20], pub_key_bytes,]); + + let result; + #[block] + { + result = runtime.bench_ecdsa_to_eth_address( + memory.as_mut_slice(), + 20, // key_ptr + 0, // output_ptr + ); + } + + assert_ok!(result); + assert_eq!(&memory[..20], runtime.ext().ecdsa_to_eth_address(&pub_key_bytes).unwrap()); + } + + #[benchmark(pov_mode = Measured)] + fn seal_set_code_hash() -> Result<(), BenchmarkError> { + let code_hash = + Contract::::with_index(1, WasmModule::dummy(), vec![])?.info()?.code_hash; + + build_runtime!(runtime, memory: [ code_hash.encode(),]); + + let result; + #[block] + { + result = runtime.bench_set_code_hash(memory.as_mut_slice(), 0); + } + + assert_ok!(result); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn lock_delegate_dependency() -> Result<(), BenchmarkError> { + let code_hash = Contract::::with_index(1, WasmModule::dummy_unique(1), vec![])? + .info()? + .code_hash; + + build_runtime!(runtime, memory: [ code_hash.encode(),]); + + let result; + #[block] + { + result = runtime.bench_lock_delegate_dependency(memory.as_mut_slice(), 0); + } + + assert_ok!(result); + Ok(()) + } + + #[benchmark] + fn unlock_delegate_dependency() -> Result<(), BenchmarkError> { + let code_hash = Contract::::with_index(1, WasmModule::dummy_unique(1), vec![])? + .info()? + .code_hash; + + build_runtime!(runtime, memory: [ code_hash.encode(),]); + runtime.bench_lock_delegate_dependency(memory.as_mut_slice(), 0).unwrap(); + + let result; + #[block] + { + result = runtime.bench_unlock_delegate_dependency(memory.as_mut_slice(), 0); + } + + assert_ok!(result); + Ok(()) + } + + // Benchmark the execution of instructions. + #[benchmark(pov_mode = Ignored)] + fn instr(r: Linear<0, INSTR_BENCHMARK_RUNS>) { + // (round, start, div, mult, add) + let input = (r, 1_000u32, 2u32, 3u32, 100u32).encode(); + let mut setup = CallSetup::::new(WasmModule::instr()); + let (mut ext, module) = setup.ext(); + let prepared = CallSetup::::prepare_call(&mut ext, module, input); + #[block] + { + prepared.call().unwrap(); + } + } + + impl_benchmark_test_suite!( + Contracts, + crate::tests::ExtBuilder::default().build(), + crate::tests::Test, + ); +} diff --git a/substrate/frame/revive/src/benchmarking_dummy.rs b/substrate/frame/revive/src/benchmarking_dummy.rs new file mode 100644 index 000000000000..6bb467911272 --- /dev/null +++ b/substrate/frame/revive/src/benchmarking_dummy.rs @@ -0,0 +1,37 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +//! Defines a dummy benchmarking suite so that the build doesn't fail in case +//! no RISC-V toolchain is available. + +#![cfg(feature = "runtime-benchmarks")] +#![cfg(not(feature = "riscv"))] + +use crate::{Config, *}; +use frame_benchmarking::v2::*; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark(pov_mode = Ignored)] + fn enable_riscv_feature_to_unlock_benchmarks() { + #[block] + {} + } +} diff --git a/substrate/frame/revive/src/chain_extension.rs b/substrate/frame/revive/src/chain_extension.rs new file mode 100644 index 000000000000..ccea12945054 --- /dev/null +++ b/substrate/frame/revive/src/chain_extension.rs @@ -0,0 +1,358 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A mechanism for runtime authors to augment the functionality of contracts. +//! +//! The runtime is able to call into any contract and retrieve the result using +//! [`bare_call`](crate::Pallet::bare_call). This already allows customization of runtime +//! behaviour by user generated code (contracts). However, often it is more straightforward +//! to allow the reverse behaviour: The contract calls into the runtime. We call the latter +//! one a "chain extension" because it allows the chain to extend the set of functions that are +//! callable by a contract. +//! +//! In order to create a chain extension the runtime author implements the [`ChainExtension`] +//! trait and declares it in this pallet's [configuration Trait](Config). All types +//! required for this endeavour are defined or re-exported in this module. There is an +//! implementation on `()` which can be used to signal that no chain extension is available. +//! +//! # Using multiple chain extensions +//! +//! Often there is a need for having multiple chain extensions. This is often the case when +//! some generally useful off-the-shelf extensions should be included. To have multiple chain +//! extensions they can be put into a tuple which is then passed to [`Config::ChainExtension`] like +//! this `type Extensions = (ExtensionA, ExtensionB)`. +//! +//! However, only extensions implementing [`RegisteredChainExtension`] can be put into a tuple. +//! This is because the [`RegisteredChainExtension::ID`] is used to decide which of those extensions +//! should be used when the contract calls a chain extensions. Extensions which are generally +//! useful should claim their `ID` with [the registry](https://github.com/paritytech/chainextension-registry) +//! so that no collisions with other vendors will occur. +//! +//! **Chain specific extensions must use the reserved `ID = 0` so that they can't be registered with +//! the registry.** +//! +//! # Security +//! +//! The chain author alone is responsible for the security of the chain extension. +//! This includes avoiding the exposure of exploitable functions and charging the +//! appropriate amount of weight. In order to do so benchmarks must be written and the +//! [`charge_weight`](Environment::charge_weight) function must be called **before** +//! carrying out any action that causes the consumption of the chargeable weight. +//! It cannot be overstated how delicate of a process the creation of a chain extension +//! is. Check whether using [`bare_call`](crate::Pallet::bare_call) suffices for the +//! use case at hand. +//! +//! # Benchmarking +//! +//! The builtin contract callable functions that pallet-revive provides all have +//! benchmarks that determine the correct weight that an invocation of these functions +//! induces. In order to be able to charge the correct weight for the functions defined +//! by a chain extension benchmarks must be written, too. In the near future this crate +//! will provide the means for easier creation of those specialized benchmarks. +//! +//! # Example +//! +//! The ink-examples repository maintains an +//! [end-to-end example](https://github.com/paritytech/ink-examples/tree/main/rand-extension) +//! on how to use a chain extension in order to provide new features to ink! contracts. + +use crate::{ + wasm::{Memory, Runtime, RuntimeCosts}, + Error, +}; +use alloc::vec::Vec; +use codec::{Decode, MaxEncodedLen}; +use frame_support::weights::Weight; +use sp_runtime::DispatchError; + +pub use crate::{exec::Ext, gas::ChargedAmount, storage::meter::Diff, Config}; +pub use frame_system::Config as SysConfig; +pub use pallet_revive_uapi::ReturnFlags; + +/// Result that returns a [`DispatchError`] on error. +pub type Result = core::result::Result; + +/// A trait used to extend the set of contract callable functions. +/// +/// In order to create a custom chain extension this trait must be implemented and supplied +/// to the pallet contracts configuration trait as the associated type of the same name. +/// Consult the [module documentation](self) for a general explanation of chain extensions. +/// +/// # Lifetime +/// +/// The extension will be [`Default`] initialized at the beginning of each call +/// (**not** per call stack) and dropped afterwards. Hence any value held inside the extension +/// can be used as a per-call scratch buffer. +pub trait ChainExtension { + /// Call the chain extension logic. + /// + /// This is the only function that needs to be implemented in order to write a + /// chain extensions. It is called whenever a contract calls the `seal_call_chain_extension` + /// imported wasm function. + /// + /// # Parameters + /// - `env`: Access to the remaining arguments and the execution environment. + /// + /// # Return + /// + /// In case of `Err` the contract execution is immediately suspended and the passed error + /// is returned to the caller. Otherwise the value of [`RetVal`] determines the exit + /// behaviour. + /// + /// # Note + /// + /// The [`Self::call`] can be invoked within a read-only context, where any state-changing calls + /// are disallowed. This information can be obtained using `env.ext().is_read_only()`. It is + /// crucial for the implementer to handle this scenario appropriately. + fn call, M: ?Sized + Memory>( + &mut self, + env: Environment, + ) -> Result; + + /// Determines whether chain extensions are enabled for this chain. + /// + /// The default implementation returns `true`. Therefore it is not necessary to overwrite + /// this function when implementing a chain extension. In case of `false` the deployment of + /// a contract that references `seal_call_chain_extension` will be denied and calling this + /// function will return [`NoChainExtension`](Error::NoChainExtension) without first calling + /// into [`call`](Self::call). + fn enabled() -> bool { + true + } +} + +/// A [`ChainExtension`] that can be composed with other extensions using a tuple. +/// +/// An extension that implements this trait can be put in a tuple in order to have multiple +/// extensions available. The tuple implementation routes requests based on the first two +/// most significant bytes of the `id` passed to `call`. +/// +/// If this extensions is to be used by multiple runtimes consider +/// [registering it](https://github.com/paritytech/chainextension-registry) to ensure that there +/// are no collisions with other vendors. +/// +/// # Note +/// +/// Currently, we support tuples of up to ten registered chain extensions. If more chain extensions +/// are needed consider opening an issue. +pub trait RegisteredChainExtension: ChainExtension { + /// The extensions globally unique identifier. + const ID: u16; +} + +#[impl_trait_for_tuples::impl_for_tuples(10)] +#[tuple_types_custom_trait_bound(RegisteredChainExtension)] +impl ChainExtension for Tuple { + fn call, M: ?Sized + Memory>( + &mut self, + mut env: Environment, + ) -> Result { + for_tuples!( + #( + if (Tuple::ID == env.ext_id()) && Tuple::enabled() { + return Tuple.call(env); + } + )* + ); + Err(Error::::NoChainExtension.into()) + } + + fn enabled() -> bool { + for_tuples!( + #( + if Tuple::enabled() { + return true; + } + )* + ); + false + } +} + +/// Determines the exit behaviour and return value of a chain extension. +pub enum RetVal { + /// The chain extensions returns the supplied value to its calling contract. + Converging(u32), + /// The control does **not** return to the calling contract. + /// + /// Use this to stop the execution of the contract when the chain extension returns. + /// The semantic is the same as for calling `seal_return`: The control returns to + /// the caller of the currently executing contract yielding the supplied buffer and + /// flags. + Diverging { flags: ReturnFlags, data: Vec }, +} + +/// Grants the chain extension access to its parameters and execution environment. +pub struct Environment<'a, 'b, E: Ext, M: ?Sized> { + /// The runtime contains all necessary functions to interact with the running contract. + runtime: &'a mut Runtime<'b, E, M>, + /// Reference to the contract's memory. + memory: &'a mut M, + /// Verbatim argument passed to `seal_call_chain_extension`. + id: u32, + /// Verbatim argument passed to `seal_call_chain_extension`. + input_ptr: u32, + /// Verbatim argument passed to `seal_call_chain_extension`. + input_len: u32, + /// Verbatim argument passed to `seal_call_chain_extension`. + output_ptr: u32, + /// Verbatim argument passed to `seal_call_chain_extension`. + output_len_ptr: u32, +} + +/// Functions that are available in every state of this type. +impl<'a, 'b, E: Ext, M: ?Sized + Memory> Environment<'a, 'b, E, M> { + /// Creates a new environment for consumption by a chain extension. + pub fn new( + runtime: &'a mut Runtime<'b, E, M>, + memory: &'a mut M, + id: u32, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Self { + Self { runtime, memory, id, input_ptr, input_len, output_ptr, output_len_ptr } + } + + /// The function id within the `id` passed by a contract. + /// + /// It returns the two least significant bytes of the `id` passed by a contract as the other + /// two bytes represent the chain extension itself (the code which is calling this function). + pub fn func_id(&self) -> u16 { + (self.id & 0x0000FFFF) as u16 + } + + /// The chain extension id within the `id` passed by a contract. + /// + /// It returns the two most significant bytes of the `id` passed by a contract which represent + /// the chain extension itself (the code which is calling this function). + pub fn ext_id(&self) -> u16 { + (self.id >> 16) as u16 + } + + /// Charge the passed `amount` of weight from the overall limit. + /// + /// It returns `Ok` when there the remaining weight budget is larger than the passed + /// `weight`. It returns `Err` otherwise. In this case the chain extension should + /// abort the execution and pass through the error. + /// + /// The returned value can be used to with [`Self::adjust_weight`]. Other than that + /// it has no purpose. + /// + /// # Note + /// + /// Weight is synonymous with gas in substrate. + pub fn charge_weight(&mut self, amount: Weight) -> Result { + self.runtime.charge_gas(RuntimeCosts::ChainExtension(amount)) + } + + /// Adjust a previously charged amount down to its actual amount. + /// + /// This is when a maximum a priori amount was charged and then should be partially + /// refunded to match the actual amount. + pub fn adjust_weight(&mut self, charged: ChargedAmount, actual_weight: Weight) { + self.runtime.adjust_gas(charged, RuntimeCosts::ChainExtension(actual_weight)) + } + + /// Grants access to the execution environment of the current contract call. + /// + /// Consult the functions on the returned type before re-implementing those functions. + pub fn ext(&mut self) -> &mut E { + self.runtime.ext() + } + + /// Reads `min(max_len, in_len)` from contract memory. + /// + /// This does **not** charge any weight. The caller must make sure that the an + /// appropriate amount of weight is charged **before** reading from contract memory. + /// The reason for that is that usually the costs for reading data and processing + /// said data cannot be separated in a benchmark. Therefore a chain extension would + /// charge the overall costs either using `max_len` (worst case approximation) or using + /// [`in_len()`](Self::in_len). + pub fn read(&self, max_len: u32) -> Result> { + self.memory.read(self.input_ptr, self.input_len.min(max_len)) + } + + /// Reads `min(buffer.len(), in_len) from contract memory. + /// + /// This takes a mutable pointer to a buffer fills it with data and shrinks it to + /// the size of the actual data. Apart from supporting pre-allocated buffers it is + /// equivalent to to [`read()`](Self::read). + pub fn read_into(&self, buffer: &mut &mut [u8]) -> Result<()> { + let len = buffer.len(); + let sliced = { + let buffer = core::mem::take(buffer); + &mut buffer[..len.min(self.input_len as usize)] + }; + self.memory.read_into_buf(self.input_ptr, sliced)?; + *buffer = sliced; + Ok(()) + } + + /// Reads and decodes a type with a size fixed at compile time from contract memory. + /// + /// This function is secure and recommended for all input types of fixed size + /// as long as the cost of reading the memory is included in the overall already charged + /// weight of the chain extension. This should usually be the case when fixed input types + /// are used. + pub fn read_as(&mut self) -> Result { + self.memory.read_as(self.input_ptr) + } + + /// Reads and decodes a type with a dynamic size from contract memory. + /// + /// Make sure to include `len` in your weight calculations. + pub fn read_as_unbounded(&mut self, len: u32) -> Result { + self.memory.read_as_unbounded(self.input_ptr, len) + } + + /// The length of the input as passed in as `input_len`. + /// + /// A chain extension would use this value to calculate the dynamic part of its + /// weight. For example a chain extension that calculates the hash of some passed in + /// bytes would use `in_len` to charge the costs of hashing that amount of bytes. + /// This also subsumes the act of copying those bytes as a benchmarks measures both. + pub fn in_len(&self) -> u32 { + self.input_len + } + + /// Write the supplied buffer to contract memory. + /// + /// If the contract supplied buffer is smaller than the passed `buffer` an `Err` is returned. + /// If `allow_skip` is set to true the contract is allowed to skip the copying of the buffer + /// by supplying the guard value of `pallet-revive::SENTINEL` as `out_ptr`. The + /// `weight_per_byte` is only charged when the write actually happens and is not skipped or + /// failed due to a too small output buffer. + pub fn write( + &mut self, + buffer: &[u8], + allow_skip: bool, + weight_per_byte: Option, + ) -> Result<()> { + self.runtime.write_sandbox_output( + self.memory, + self.output_ptr, + self.output_len_ptr, + buffer, + allow_skip, + |len| { + weight_per_byte.map(|w| RuntimeCosts::ChainExtension(w.saturating_mul(len.into()))) + }, + ) + } +} diff --git a/substrate/frame/revive/src/debug.rs b/substrate/frame/revive/src/debug.rs new file mode 100644 index 000000000000..467f4e1ad491 --- /dev/null +++ b/substrate/frame/revive/src/debug.rs @@ -0,0 +1,112 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub use crate::{ + exec::{ExecResult, ExportedFunction}, + primitives::ExecReturnValue, +}; +use crate::{Config, LOG_TARGET}; + +/// Umbrella trait for all interfaces that serves for debugging. +pub trait Debugger: Tracing + CallInterceptor {} + +impl Debugger for V where V: Tracing + CallInterceptor {} + +/// Defines methods to capture contract calls, enabling external observers to +/// measure, trace, and react to contract interactions. +pub trait Tracing { + /// The type of [`CallSpan`] that is created by this trait. + type CallSpan: CallSpan; + + /// Creates a new call span to encompass the upcoming contract execution. + /// + /// This method should be invoked just before the execution of a contract and + /// marks the beginning of a traceable span of execution. + /// + /// # Arguments + /// + /// * `contract_address` - The address of the contract that is about to be executed. + /// * `entry_point` - Describes whether the call is the constructor or a regular call. + /// * `input_data` - The raw input data of the call. + fn new_call_span( + contract_address: &T::AccountId, + entry_point: ExportedFunction, + input_data: &[u8], + ) -> Self::CallSpan; +} + +/// Defines a span of execution for a contract call. +pub trait CallSpan { + /// Called just after the execution of a contract. + /// + /// # Arguments + /// + /// * `output` - The raw output of the call. + fn after_call(self, output: &ExecReturnValue); +} + +impl Tracing for () { + type CallSpan = (); + + fn new_call_span( + contract_address: &T::AccountId, + entry_point: ExportedFunction, + input_data: &[u8], + ) { + log::trace!(target: LOG_TARGET, "call {entry_point:?} account: {contract_address:?}, input_data: {input_data:?}") + } +} + +impl CallSpan for () { + fn after_call(self, output: &ExecReturnValue) { + log::trace!(target: LOG_TARGET, "call result {output:?}") + } +} + +/// Provides an interface for intercepting contract calls. +pub trait CallInterceptor { + /// Allows to intercept contract calls and decide whether they should be executed or not. + /// If the call is intercepted, the mocked result of the call is returned. + /// + /// # Arguments + /// + /// * `contract_address` - The address of the contract that is about to be executed. + /// * `entry_point` - Describes whether the call is the constructor or a regular call. + /// * `input_data` - The raw input data of the call. + /// + /// # Expected behavior + /// + /// This method should return: + /// * `Some(ExecResult)` - if the call should be intercepted and the mocked result of the call + /// is returned. + /// * `None` - otherwise, i.e. the call should be executed normally. + fn intercept_call( + contract_address: &T::AccountId, + entry_point: ExportedFunction, + input_data: &[u8], + ) -> Option; +} + +impl CallInterceptor for () { + fn intercept_call( + _contract_address: &T::AccountId, + _entry_point: ExportedFunction, + _input_data: &[u8], + ) -> Option { + None + } +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs new file mode 100644 index 000000000000..9740707ae706 --- /dev/null +++ b/substrate/frame/revive/src/exec.rs @@ -0,0 +1,3948 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + debug::{CallInterceptor, CallSpan, Tracing}, + gas::GasMeter, + limits, + primitives::{ExecReturnValue, StorageDeposit}, + runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, + storage::{self, meter::Diff, WriteOutcome}, + transient_storage::TransientStorage, + BalanceOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, DebugBuffer, + Error, Event, Pallet as Contracts, LOG_TARGET, +}; +use alloc::vec::Vec; +use core::{fmt::Debug, marker::PhantomData, mem}; +use frame_support::{ + crypto::ecdsa::ECDSAExt, + dispatch::{DispatchResult, DispatchResultWithPostInfo}, + ensure, + storage::{with_transaction, TransactionOutcome}, + traits::{ + fungible::{Inspect, Mutate}, + tokens::{Fortitude, Preservation}, + Contains, OriginTrait, Time, + }, + weights::Weight, + Blake2_128Concat, BoundedVec, StorageHasher, +}; +use frame_system::{ + pallet_prelude::{BlockNumberFor, OriginFor}, + Pallet as System, RawOrigin, +}; +use sp_core::{ + ecdsa::Public as ECDSAPublic, + sr25519::{Public as SR25519Public, Signature as SR25519Signature}, + ConstU32, Get, +}; +use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; +use sp_runtime::{ + traits::{BadOrigin, Convert, Dispatchable, Zero}, + DispatchError, +}; + +pub type AccountIdOf = ::AccountId; +pub type MomentOf = <::Time as Time>::Moment; +pub type ExecResult = Result; + +/// A type that represents a topic of an event. At the moment a hash is used. +pub type TopicOf = ::Hash; + +/// Type for variable sized storage key. Used for transparent hashing. +type VarSizedKey = BoundedVec>; + +/// Combined key type for both fixed and variable sized storage keys. +pub enum Key { + /// Variant for fixed sized keys. + Fix([u8; 32]), + /// Variant for variable sized keys. + Var(VarSizedKey), +} + +impl Key { + /// Reference to the raw unhashed key. + /// + /// # Note + /// + /// Only used by benchmarking in order to generate storage collisions on purpose. + #[cfg(feature = "runtime-benchmarks")] + pub fn unhashed(&self) -> &[u8] { + match self { + Key::Fix(v) => v.as_ref(), + Key::Var(v) => v.as_ref(), + } + } + + /// The hashed key that has be used as actual key to the storage trie. + pub fn hash(&self) -> Vec { + match self { + Key::Fix(v) => blake2_256(v.as_slice()).to_vec(), + Key::Var(v) => Blake2_128Concat::hash(v.as_slice()), + } + } + + pub fn from_fixed(v: [u8; 32]) -> Self { + Self::Fix(v) + } + + pub fn try_from_var(v: Vec) -> Result { + VarSizedKey::try_from(v).map(Self::Var).map_err(|_| ()) + } +} + +/// Origin of the error. +/// +/// Call or instantiate both called into other contracts and pass through errors happening +/// in those to the caller. This enum is for the caller to distinguish whether the error +/// happened during the execution of the callee or in the current execution context. +#[derive(Copy, Clone, PartialEq, Eq, Debug, codec::Decode, codec::Encode)] +pub enum ErrorOrigin { + /// Caller error origin. + /// + /// The error happened in the current execution context rather than in the one + /// of the contract that is called into. + Caller, + /// The error happened during execution of the called contract. + Callee, +} + +/// Error returned by contract execution. +#[derive(Copy, Clone, PartialEq, Eq, Debug, codec::Decode, codec::Encode)] +pub struct ExecError { + /// The reason why the execution failed. + pub error: DispatchError, + /// Origin of the error. + pub origin: ErrorOrigin, +} + +impl> From for ExecError { + fn from(error: T) -> Self { + Self { error: error.into(), origin: ErrorOrigin::Caller } + } +} + +/// The type of origins supported by the contracts pallet. +#[derive(Clone, Encode, Decode, PartialEq, TypeInfo, RuntimeDebugNoBound)] +pub enum Origin { + Root, + Signed(T::AccountId), +} + +impl Origin { + /// Creates a new Signed Caller from an AccountId. + pub fn from_account_id(account_id: T::AccountId) -> Self { + Origin::Signed(account_id) + } + /// Creates a new Origin from a `RuntimeOrigin`. + pub fn from_runtime_origin(o: OriginFor) -> Result { + match o.into() { + Ok(RawOrigin::Root) => Ok(Self::Root), + Ok(RawOrigin::Signed(t)) => Ok(Self::Signed(t)), + _ => Err(BadOrigin.into()), + } + } + /// Returns the AccountId of a Signed Origin or an error if the origin is Root. + pub fn account_id(&self) -> Result<&T::AccountId, DispatchError> { + match self { + Origin::Signed(id) => Ok(id), + Origin::Root => Err(DispatchError::RootNotAllowed), + } + } +} + +/// An interface that provides access to the external environment in which the +/// smart-contract is executed. +/// +/// This interface is specialized to an account of the executing code, so all +/// operations are implicitly performed on that account. +/// +/// # Note +/// +/// This trait is sealed and cannot be implemented by downstream crates. +pub trait Ext: sealing::Sealed { + type T: Config; + + /// Call (possibly transferring some amount of funds) into the specified account. + /// + /// Returns the code size of the called contract. + fn call( + &mut self, + gas_limit: Weight, + deposit_limit: BalanceOf, + to: AccountIdOf, + value: BalanceOf, + input_data: Vec, + allows_reentry: bool, + read_only: bool, + ) -> Result; + + /// Execute code in the current frame. + /// + /// Returns the code size of the called contract. + fn delegate_call( + &mut self, + code: CodeHash, + input_data: Vec, + ) -> Result; + + /// Instantiate a contract from the given code. + /// + /// Returns the original code size of the called contract. + /// The newly created account will be associated with `code`. `value` specifies the amount of + /// value transferred from the caller to the newly created account. + fn instantiate( + &mut self, + gas_limit: Weight, + deposit_limit: BalanceOf, + code: CodeHash, + value: BalanceOf, + input_data: Vec, + salt: &[u8], + ) -> Result<(AccountIdOf, ExecReturnValue), ExecError>; + + /// Transfer all funds to `beneficiary` and delete the contract. + /// + /// Since this function removes the self contract eagerly, if succeeded, no further actions + /// should be performed on this `Ext` instance. + /// + /// This function will fail if the same contract is present on the contract + /// call stack. + fn terminate(&mut self, beneficiary: &AccountIdOf) -> DispatchResult; + + /// Transfer some amount of funds into the specified account. + fn transfer(&mut self, to: &AccountIdOf, value: BalanceOf) -> DispatchResult; + + /// Returns the storage entry of the executing account by the given `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + fn get_storage(&mut self, key: &Key) -> Option>; + + /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + fn get_storage_size(&mut self, key: &Key) -> Option; + + /// Sets the storage entry by the given key to the specified value. If `value` is `None` then + /// the storage entry is deleted. + fn set_storage( + &mut self, + key: &Key, + value: Option>, + take_old: bool, + ) -> Result; + + /// Returns the transient storage entry of the executing account for the given `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_transient_storage` or + /// was deleted. + fn get_transient_storage(&self, key: &Key) -> Option>; + + /// Returns `Some(len)` (in bytes) if a transient storage item exists at `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_transient_storage` or + /// was deleted. + fn get_transient_storage_size(&self, key: &Key) -> Option; + + /// Sets the transient storage entry for the given key to the specified value. If `value` is + /// `None` then the storage entry is deleted. + fn set_transient_storage( + &mut self, + key: &Key, + value: Option>, + take_old: bool, + ) -> Result; + + /// Returns the caller. + fn caller(&self) -> Origin; + + /// Check if a contract lives at the specified `address`. + fn is_contract(&self, address: &AccountIdOf) -> bool; + + /// Returns the code hash of the contract for the given `address`. + /// + /// Returns `None` if the `address` does not belong to a contract. + fn code_hash(&self, address: &AccountIdOf) -> Option>; + + /// Returns the code hash of the contract being executed. + fn own_code_hash(&mut self) -> &CodeHash; + + /// Check if the caller of the current contract is the origin of the whole call stack. + /// + /// This can be checked with `is_contract(self.caller())` as well. + /// However, this function does not require any storage lookup and therefore uses less weight. + fn caller_is_origin(&self) -> bool; + + /// Check if the caller is origin, and this origin is root. + fn caller_is_root(&self) -> bool; + + /// Returns a reference to the account id of the current contract. + fn address(&self) -> &AccountIdOf; + + /// Returns the balance of the current contract. + /// + /// The `value_transferred` is already added. + fn balance(&self) -> BalanceOf; + + /// Returns the value transferred along with this call. + fn value_transferred(&self) -> BalanceOf; + + /// Returns a reference to the timestamp of the current block + fn now(&self) -> &MomentOf; + + /// Returns the minimum balance that is required for creating an account. + fn minimum_balance(&self) -> BalanceOf; + + /// Deposit an event with the given topics. + /// + /// There should not be any duplicates in `topics`. + fn deposit_event(&mut self, topics: Vec>, data: Vec); + + /// Returns the current block number. + fn block_number(&self) -> BlockNumberFor; + + /// Returns the maximum allowed size of a storage item. + fn max_value_size(&self) -> u32; + + /// Returns the price for the specified amount of weight. + fn get_weight_price(&self, weight: Weight) -> BalanceOf; + + /// Get an immutable reference to the nested gas meter. + fn gas_meter(&self) -> &GasMeter; + + /// Get a mutable reference to the nested gas meter. + fn gas_meter_mut(&mut self) -> &mut GasMeter; + + /// Charges `diff` from the meter. + fn charge_storage(&mut self, diff: &Diff); + + /// Append a string to the debug buffer. + /// + /// It is added as-is without any additional new line. + /// + /// This is a no-op if debug message recording is disabled which is always the case + /// when the code is executing on-chain. + /// + /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. + fn append_debug_buffer(&mut self, msg: &str) -> bool; + + /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. + fn debug_buffer_enabled(&self) -> bool; + + /// Call some dispatchable and return the result. + fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; + + /// Recovers ECDSA compressed public key based on signature and message hash. + fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>; + + /// Verify a sr25519 signature. + fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool; + + /// Returns Ethereum address from the ECDSA compressed public key. + fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()>; + + /// Tests sometimes need to modify and inspect the contract info directly. + #[cfg(any(test, feature = "runtime-benchmarks"))] + fn contract_info(&mut self) -> &mut ContractInfo; + + /// Get a mutable reference to the transient storage. + /// Useful in benchmarks when it is sometimes necessary to modify and inspect the transient + /// storage directly. + #[cfg(feature = "runtime-benchmarks")] + fn transient_storage(&mut self) -> &mut TransientStorage; + + /// Sets new code hash for existing contract. + fn set_code_hash(&mut self, hash: CodeHash) -> DispatchResult; + + /// Returns the number of times the specified contract exists on the call stack. Delegated calls + /// Increment the reference count of a of a stored code by one. + /// + /// # Errors + /// + /// [`Error::CodeNotFound`] is returned if no stored code found having the specified + /// `code_hash`. + fn increment_refcount(code_hash: CodeHash) -> DispatchResult; + + /// Decrement the reference count of a stored code by one. + /// + /// # Note + /// + /// A contract whose reference count dropped to zero isn't automatically removed. A + /// `remove_code` transaction must be submitted by the original uploader to do so. + fn decrement_refcount(code_hash: CodeHash); + + /// Adds a delegate dependency to [`ContractInfo`]'s `delegate_dependencies` field. + /// + /// This ensures that the delegated contract is not removed while it is still in use. It + /// increases the reference count of the code hash and charges a fraction (see + /// [`Config::CodeHashLockupDepositPercent`]) of the code deposit. + /// + /// # Errors + /// + /// - [`Error::MaxDelegateDependenciesReached`] + /// - [`Error::CannotAddSelfAsDelegateDependency`] + /// - [`Error::DelegateDependencyAlreadyExists`] + fn lock_delegate_dependency(&mut self, code_hash: CodeHash) -> DispatchResult; + + /// Removes a delegate dependency from [`ContractInfo`]'s `delegate_dependencies` field. + /// + /// This is the counterpart of [`Self::lock_delegate_dependency`]. It decreases the reference + /// count and refunds the deposit that was charged by [`Self::lock_delegate_dependency`]. + /// + /// # Errors + /// + /// - [`Error::DelegateDependencyNotFound`] + fn unlock_delegate_dependency(&mut self, code_hash: &CodeHash) -> DispatchResult; + + /// Returns the number of locked delegate dependencies. + /// + /// Note: Requires &mut self to access the contract info. + fn locked_delegate_dependencies_count(&mut self) -> usize; + + /// Check if running in read-only context. + fn is_read_only(&self) -> bool; +} + +/// Describes the different functions that can be exported by an [`Executable`]. +#[derive( + Copy, + Clone, + PartialEq, + Eq, + sp_core::RuntimeDebug, + codec::Decode, + codec::Encode, + codec::MaxEncodedLen, + scale_info::TypeInfo, +)] +pub enum ExportedFunction { + /// The constructor function which is executed on deployment of a contract. + Constructor, + /// The function which is executed when a contract is called. + Call, +} + +/// A trait that represents something that can be executed. +/// +/// In the on-chain environment this would be represented by a wasm module. This trait exists in +/// order to be able to mock the wasm logic for testing. +pub trait Executable: Sized { + /// Load the executable from storage. + /// + /// # Note + /// Charges size base load weight from the gas meter. + fn from_storage( + code_hash: CodeHash, + gas_meter: &mut GasMeter, + ) -> Result; + + /// Execute the specified exported function and return the result. + /// + /// When the specified function is `Constructor` the executable is stored and its + /// refcount incremented. + /// + /// # Note + /// + /// This functions expects to be executed in a storage transaction that rolls back + /// all of its emitted storage changes. + fn execute>( + self, + ext: &mut E, + function: ExportedFunction, + input_data: Vec, + ) -> ExecResult; + + /// The code info of the executable. + fn code_info(&self) -> &CodeInfo; + + /// The code hash of the executable. + fn code_hash(&self) -> &CodeHash; +} + +/// The complete call stack of a contract execution. +/// +/// The call stack is initiated by either a signed origin or one of the contract RPC calls. +/// This type implements `Ext` and by that exposes the business logic of contract execution to +/// the runtime module which interfaces with the contract (the wasm blob) itself. +pub struct Stack<'a, T: Config, E> { + /// The origin that initiated the call stack. It could either be a Signed plain account that + /// holds an account id or Root. + /// + /// # Note + /// + /// Please note that it is possible that the id of a Signed origin belongs to a contract rather + /// than a plain account when being called through one of the contract RPCs where the + /// client can freely choose the origin. This usually makes no sense but is still possible. + origin: Origin, + /// The gas meter where costs are charged to. + gas_meter: &'a mut GasMeter, + /// The storage meter makes sure that the storage deposit limit is obeyed. + storage_meter: &'a mut storage::meter::Meter, + /// The timestamp at the point of call stack instantiation. + timestamp: MomentOf, + /// The block number at the time of call stack instantiation. + block_number: BlockNumberFor, + /// The actual call stack. One entry per nested contract called/instantiated. + /// This does **not** include the [`Self::first_frame`]. + frames: BoundedVec, ConstU32<{ limits::CALL_STACK_DEPTH }>>, + /// Statically guarantee that each call stack has at least one frame. + first_frame: Frame, + /// A text buffer used to output human readable information. + /// + /// All the bytes added to this field should be valid UTF-8. The buffer has no defined + /// structure and is intended to be shown to users as-is for debugging purposes. + debug_message: Option<&'a mut DebugBuffer>, + /// Transient storage used to store data, which is kept for the duration of a transaction. + transient_storage: TransientStorage, + /// No executable is held by the struct but influences its behaviour. + _phantom: PhantomData, +} + +/// Represents one entry in the call stack. +/// +/// For each nested contract call or instantiate one frame is created. It holds specific +/// information for the said call and caches the in-storage `ContractInfo` data structure. +struct Frame { + /// The account id of the executing contract. + account_id: T::AccountId, + /// The cached in-storage data of the contract. + contract_info: CachedContract, + /// The amount of balance transferred by the caller as part of the call. + value_transferred: BalanceOf, + /// Determines whether this is a call or instantiate frame. + entry_point: ExportedFunction, + /// The gas meter capped to the supplied gas limit. + nested_gas: GasMeter, + /// The storage meter for the individual call. + nested_storage: storage::meter::NestedMeter, + /// If `false` the contract enabled its defense against reentrance attacks. + allows_reentry: bool, + /// If `true` subsequent calls cannot modify storage. + read_only: bool, + /// The caller of the currently executing frame which was spawned by `delegate_call`. + delegate_caller: Option>, +} + +/// Used in a delegate call frame arguments in order to override the executable and caller. +struct DelegatedCall { + /// The executable which is run instead of the contracts own `executable`. + executable: E, + /// The caller of the contract. + caller: Origin, +} + +/// Parameter passed in when creating a new `Frame`. +/// +/// It determines whether the new frame is for a call or an instantiate. +enum FrameArgs<'a, T: Config, E> { + Call { + /// The account id of the contract that is to be called. + dest: T::AccountId, + /// If `None` the contract info needs to be reloaded from storage. + cached_info: Option>, + /// This frame was created by `seal_delegate_call` and hence uses different code than + /// what is stored at [`Self::Call::dest`]. Its caller ([`DelegatedCall::caller`]) is the + /// account which called the caller contract + delegated_call: Option>, + }, + Instantiate { + /// The contract or signed origin which instantiates the new contract. + sender: T::AccountId, + /// The executable whose `deploy` function is run. + executable: E, + /// A salt used in the contract address derivation of the new contract. + salt: &'a [u8], + /// The input data is used in the contract address derivation of the new contract. + input_data: &'a [u8], + }, +} + +/// Describes the different states of a contract as contained in a `Frame`. +enum CachedContract { + /// The cached contract is up to date with the in-storage value. + Cached(ContractInfo), + /// A recursive call into the same contract did write to the contract info. + /// + /// In this case the cached contract is stale and needs to be reloaded from storage. + Invalidated, + /// The current contract executed `terminate` and removed the contract. + /// + /// In this case a reload is neither allowed nor possible. Please note that recursive + /// calls cannot remove a contract as this is checked and denied. + Terminated, +} + +impl CachedContract { + /// Return `Some(ContractInfo)` if the contract is in cached state. `None` otherwise. + fn into_contract(self) -> Option> { + if let CachedContract::Cached(contract) = self { + Some(contract) + } else { + None + } + } + + /// Return `Some(&mut ContractInfo)` if the contract is in cached state. `None` otherwise. + fn as_contract(&mut self) -> Option<&mut ContractInfo> { + if let CachedContract::Cached(contract) = self { + Some(contract) + } else { + None + } + } +} + +impl Frame { + /// Return the `contract_info` of the current contract. + fn contract_info(&mut self) -> &mut ContractInfo { + self.contract_info.get(&self.account_id) + } + + /// Terminate and return the `contract_info` of the current contract. + /// + /// # Note + /// + /// Under no circumstances the contract is allowed to access the `contract_info` after + /// a call to this function. This would constitute a programming error in the exec module. + fn terminate(&mut self) -> ContractInfo { + self.contract_info.terminate(&self.account_id) + } +} + +/// Extract the contract info after loading it from storage. +/// +/// This assumes that `load` was executed before calling this macro. +macro_rules! get_cached_or_panic_after_load { + ($c:expr) => {{ + if let CachedContract::Cached(contract) = $c { + contract + } else { + panic!( + "It is impossible to remove a contract that is on the call stack;\ + See implementations of terminate;\ + Therefore fetching a contract will never fail while using an account id + that is currently active on the call stack;\ + qed" + ); + } + }}; +} + +/// Same as [`Stack::top_frame`]. +/// +/// We need this access as a macro because sometimes hiding the lifetimes behind +/// a function won't work out. +macro_rules! top_frame { + ($stack:expr) => { + $stack.frames.last().unwrap_or(&$stack.first_frame) + }; +} + +/// Same as [`Stack::top_frame_mut`]. +/// +/// We need this access as a macro because sometimes hiding the lifetimes behind +/// a function won't work out. +macro_rules! top_frame_mut { + ($stack:expr) => { + $stack.frames.last_mut().unwrap_or(&mut $stack.first_frame) + }; +} + +impl CachedContract { + /// Load the `contract_info` from storage if necessary. + fn load(&mut self, account_id: &T::AccountId) { + if let CachedContract::Invalidated = self { + let contract = >::get(&account_id); + if let Some(contract) = contract { + *self = CachedContract::Cached(contract); + } + } + } + + /// Return the cached contract_info. + fn get(&mut self, account_id: &T::AccountId) -> &mut ContractInfo { + self.load(account_id); + get_cached_or_panic_after_load!(self) + } + + /// Terminate and return the contract info. + fn terminate(&mut self, account_id: &T::AccountId) -> ContractInfo { + self.load(account_id); + get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated)) + } +} + +impl<'a, T, E> Stack<'a, T, E> +where + T: Config, + E: Executable, +{ + /// Create and run a new call stack by calling into `dest`. + /// + /// # Note + /// + /// `debug_message` should only ever be set to `Some` when executing as an RPC because + /// it adds allocations and could be abused to drive the runtime into an OOM panic. + /// + /// # Return Value + /// + /// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)> + pub fn run_call( + origin: Origin, + dest: T::AccountId, + gas_meter: &'a mut GasMeter, + storage_meter: &'a mut storage::meter::Meter, + value: BalanceOf, + input_data: Vec, + debug_message: Option<&'a mut DebugBuffer>, + ) -> Result { + let (mut stack, executable) = Self::new( + FrameArgs::Call { dest, cached_info: None, delegated_call: None }, + origin, + gas_meter, + storage_meter, + value, + debug_message, + )?; + stack.run(executable, input_data) + } + + /// Create and run a new call stack by instantiating a new contract. + /// + /// # Note + /// + /// `debug_message` should only ever be set to `Some` when executing as an RPC because + /// it adds allocations and could be abused to drive the runtime into an OOM panic. + /// + /// # Return Value + /// + /// Result<(NewContractAccountId, ExecReturnValue), ExecError)> + pub fn run_instantiate( + origin: T::AccountId, + executable: E, + gas_meter: &'a mut GasMeter, + storage_meter: &'a mut storage::meter::Meter, + value: BalanceOf, + input_data: Vec, + salt: &[u8], + debug_message: Option<&'a mut DebugBuffer>, + ) -> Result<(T::AccountId, ExecReturnValue), ExecError> { + let (mut stack, executable) = Self::new( + FrameArgs::Instantiate { + sender: origin.clone(), + executable, + salt, + input_data: input_data.as_ref(), + }, + Origin::from_account_id(origin), + gas_meter, + storage_meter, + value, + debug_message, + )?; + let account_id = stack.top_frame().account_id.clone(); + stack.run(executable, input_data).map(|ret| (account_id, ret)) + } + + #[cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] + pub fn bench_new_call( + dest: T::AccountId, + origin: Origin, + gas_meter: &'a mut GasMeter, + storage_meter: &'a mut storage::meter::Meter, + value: BalanceOf, + debug_message: Option<&'a mut DebugBuffer>, + ) -> (Self, E) { + Self::new( + FrameArgs::Call { dest, cached_info: None, delegated_call: None }, + origin, + gas_meter, + storage_meter, + value, + debug_message, + ) + .unwrap() + } + + /// Create a new call stack. + fn new( + args: FrameArgs, + origin: Origin, + gas_meter: &'a mut GasMeter, + storage_meter: &'a mut storage::meter::Meter, + value: BalanceOf, + debug_message: Option<&'a mut DebugBuffer>, + ) -> Result<(Self, E), ExecError> { + let (first_frame, executable) = Self::new_frame( + args, + value, + gas_meter, + Weight::zero(), + storage_meter, + BalanceOf::::zero(), + false, + )?; + + let stack = Self { + origin, + gas_meter, + storage_meter, + timestamp: T::Time::now(), + block_number: >::block_number(), + first_frame, + frames: Default::default(), + debug_message, + transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES), + _phantom: Default::default(), + }; + + Ok((stack, executable)) + } + + /// Construct a new frame. + /// + /// This does not take `self` because when constructing the first frame `self` is + /// not initialized, yet. + fn new_frame( + frame_args: FrameArgs, + value_transferred: BalanceOf, + gas_meter: &mut GasMeter, + gas_limit: Weight, + storage_meter: &mut storage::meter::GenericMeter, + deposit_limit: BalanceOf, + read_only: bool, + ) -> Result<(Frame, E), ExecError> { + let (account_id, contract_info, executable, delegate_caller, entry_point) = match frame_args + { + FrameArgs::Call { dest, cached_info, delegated_call } => { + let contract = if let Some(contract) = cached_info { + contract + } else { + >::get(&dest).ok_or(>::ContractNotFound)? + }; + + let (executable, delegate_caller) = + if let Some(DelegatedCall { executable, caller }) = delegated_call { + (executable, Some(caller)) + } else { + (E::from_storage(contract.code_hash, gas_meter)?, None) + }; + + (dest, contract, executable, delegate_caller, ExportedFunction::Call) + }, + FrameArgs::Instantiate { sender, executable, salt, input_data } => { + let account_id = Contracts::::contract_address( + &sender, + &executable.code_hash(), + input_data, + salt, + ); + let contract = ContractInfo::new( + &account_id, + >::account_nonce(&sender), + *executable.code_hash(), + )?; + (account_id, contract, executable, None, ExportedFunction::Constructor) + }, + }; + + let frame = Frame { + delegate_caller, + value_transferred, + contract_info: CachedContract::Cached(contract_info), + account_id, + entry_point, + nested_gas: gas_meter.nested(gas_limit), + nested_storage: storage_meter.nested(deposit_limit), + allows_reentry: true, + read_only, + }; + + Ok((frame, executable)) + } + + /// Create a subsequent nested frame. + fn push_frame( + &mut self, + frame_args: FrameArgs, + value_transferred: BalanceOf, + gas_limit: Weight, + deposit_limit: BalanceOf, + read_only: bool, + ) -> Result { + if self.frames.len() as u32 == limits::CALL_STACK_DEPTH { + return Err(Error::::MaxCallDepthReached.into()) + } + + // We need to make sure that changes made to the contract info are not discarded. + // See the `in_memory_changes_not_discarded` test for more information. + // We do not store on instantiate because we do not allow to call into a contract + // from its own constructor. + let frame = self.top_frame(); + if let (CachedContract::Cached(contract), ExportedFunction::Call) = + (&frame.contract_info, frame.entry_point) + { + >::insert(frame.account_id.clone(), contract.clone()); + } + + let frame = top_frame_mut!(self); + let nested_gas = &mut frame.nested_gas; + let nested_storage = &mut frame.nested_storage; + let (frame, executable) = Self::new_frame( + frame_args, + value_transferred, + nested_gas, + gas_limit, + nested_storage, + deposit_limit, + read_only, + )?; + self.frames.try_push(frame).map_err(|_| Error::::MaxCallDepthReached)?; + Ok(executable) + } + + /// Run the current (top) frame. + /// + /// This can be either a call or an instantiate. + fn run(&mut self, executable: E, input_data: Vec) -> Result { + let frame = self.top_frame(); + let entry_point = frame.entry_point; + let delegated_code_hash = + if frame.delegate_caller.is_some() { Some(*executable.code_hash()) } else { None }; + + self.transient_storage.start_transaction(); + + let do_transaction = || { + // We need to charge the storage deposit before the initial transfer so that + // it can create the account in case the initial transfer is < ed. + if entry_point == ExportedFunction::Constructor { + // Root origin can't be used to instantiate a contract, so it is safe to assume that + // if we reached this point the origin has an associated account. + let origin = &self.origin.account_id()?; + let frame = top_frame_mut!(self); + frame.nested_storage.charge_instantiate( + origin, + &frame.account_id, + frame.contract_info.get(&frame.account_id), + executable.code_info(), + )?; + // Needs to be incremented before calling into the code so that it is visible + // in case of recursion. + >::inc_account_nonce(self.caller().account_id()?); + } + + // Every non delegate call or instantiate also optionally transfers the balance. + self.initial_transfer()?; + + let contract_address = &top_frame!(self).account_id; + + let call_span = T::Debug::new_call_span(contract_address, entry_point, &input_data); + + let output = T::Debug::intercept_call(contract_address, entry_point, &input_data) + .unwrap_or_else(|| { + executable + .execute(self, entry_point, input_data) + .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee }) + })?; + + call_span.after_call(&output); + + // Avoid useless work that would be reverted anyways. + if output.did_revert() { + return Ok(output) + } + + // Storage limit is normally enforced as late as possible (when the last frame returns) + // so that the ordering of storage accesses does not matter. + // (However, if a special limit was set for a sub-call, it should be enforced right + // after the sub-call returned. See below for this case of enforcement). + if self.frames.is_empty() { + let frame = &mut self.first_frame; + frame.contract_info.load(&frame.account_id); + let contract = frame.contract_info.as_contract(); + frame.nested_storage.enforce_limit(contract)?; + } + + let frame = self.top_frame(); + let account_id = &frame.account_id.clone(); + match (entry_point, delegated_code_hash) { + (ExportedFunction::Constructor, _) => { + // It is not allowed to terminate a contract inside its constructor. + if matches!(frame.contract_info, CachedContract::Terminated) { + return Err(Error::::TerminatedInConstructor.into()) + } + + // If a special limit was set for the sub-call, we enforce it here. + // This is needed because contract constructor might write to storage. + // The sub-call will be rolled back in case the limit is exhausted. + let frame = self.top_frame_mut(); + let contract = frame.contract_info.as_contract(); + frame.nested_storage.enforce_subcall_limit(contract)?; + + let caller = self.caller().account_id()?.clone(); + + // Deposit an instantiation event. + Contracts::::deposit_event(Event::Instantiated { + deployer: caller, + contract: account_id.clone(), + }); + }, + (ExportedFunction::Call, Some(code_hash)) => { + Contracts::::deposit_event(Event::DelegateCalled { + contract: account_id.clone(), + code_hash, + }); + }, + (ExportedFunction::Call, None) => { + // If a special limit was set for the sub-call, we enforce it here. + // The sub-call will be rolled back in case the limit is exhausted. + let frame = self.top_frame_mut(); + let contract = frame.contract_info.as_contract(); + frame.nested_storage.enforce_subcall_limit(contract)?; + + let caller = self.caller(); + Contracts::::deposit_event(Event::Called { + caller: caller.clone(), + contract: account_id.clone(), + }); + }, + } + + Ok(output) + }; + + // All changes performed by the contract are executed under a storage transaction. + // This allows for roll back on error. Changes to the cached contract_info are + // committed or rolled back when popping the frame. + // + // `with_transactional` may return an error caused by a limit in the + // transactional storage depth. + let transaction_outcome = + with_transaction(|| -> TransactionOutcome> { + let output = do_transaction(); + match &output { + Ok(result) if !result.did_revert() => + TransactionOutcome::Commit(Ok((true, output))), + _ => TransactionOutcome::Rollback(Ok((false, output))), + } + }); + + let (success, output) = match transaction_outcome { + // `with_transactional` executed successfully, and we have the expected output. + Ok((success, output)) => (success, output), + // `with_transactional` returned an error, and we propagate that error and note no state + // has changed. + Err(error) => (false, Err(error.into())), + }; + + if success { + self.transient_storage.commit_transaction(); + } else { + self.transient_storage.rollback_transaction(); + } + + self.pop_frame(success); + output + } + + /// Remove the current (top) frame from the stack. + /// + /// This is called after running the current frame. It commits cached values to storage + /// and invalidates all stale references to it that might exist further down the call stack. + fn pop_frame(&mut self, persist: bool) { + // Pop the current frame from the stack and return it in case it needs to interact + // with duplicates that might exist on the stack. + // A `None` means that we are returning from the `first_frame`. + let frame = self.frames.pop(); + + // Both branches do essentially the same with the exception. The difference is that + // the else branch does consume the hardcoded `first_frame`. + if let Some(mut frame) = frame { + let account_id = &frame.account_id; + let prev = top_frame_mut!(self); + + prev.nested_gas.absorb_nested(frame.nested_gas); + + // Only gas counter changes are persisted in case of a failure. + if !persist { + return + } + + // Record the storage meter changes of the nested call into the parent meter. + // If the dropped frame's contract wasn't terminated we update the deposit counter + // in its contract info. The load is necessary to pull it from storage in case + // it was invalidated. + frame.contract_info.load(account_id); + let mut contract = frame.contract_info.into_contract(); + prev.nested_storage.absorb(frame.nested_storage, account_id, contract.as_mut()); + + // In case the contract wasn't terminated we need to persist changes made to it. + if let Some(contract) = contract { + // optimization: Predecessor is the same contract. + // We can just copy the contract into the predecessor without a storage write. + // This is possible when there is no other contract in-between that could + // trigger a rollback. + if prev.account_id == *account_id { + prev.contract_info = CachedContract::Cached(contract); + return + } + + // Predecessor is a different contract: We persist the info and invalidate the first + // stale cache we find. This triggers a reload from storage on next use. We skip(1) + // because that case is already handled by the optimization above. Only the first + // cache needs to be invalidated because that one will invalidate the next cache + // when it is popped from the stack. + >::insert(account_id, contract); + if let Some(c) = self.frames_mut().skip(1).find(|f| f.account_id == *account_id) { + c.contract_info = CachedContract::Invalidated; + } + } + } else { + if let Some((msg, false)) = self.debug_message.as_ref().map(|m| (m, m.is_empty())) { + log::debug!( + target: LOG_TARGET, + "Execution finished with debug buffer: {}", + core::str::from_utf8(msg).unwrap_or(""), + ); + } + self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_gas)); + if !persist { + return + } + let mut contract = self.first_frame.contract_info.as_contract(); + self.storage_meter.absorb( + mem::take(&mut self.first_frame.nested_storage), + &self.first_frame.account_id, + contract.as_deref_mut(), + ); + if let Some(contract) = contract { + >::insert(&self.first_frame.account_id, contract); + } + } + } + + /// Transfer some funds from `from` to `to`. + fn transfer( + preservation: Preservation, + from: &T::AccountId, + to: &T::AccountId, + value: BalanceOf, + ) -> DispatchResult { + if !value.is_zero() && from != to { + T::Currency::transfer(from, to, value, preservation) + .map_err(|_| Error::::TransferFailed)?; + } + Ok(()) + } + + // The transfer as performed by a call or instantiate. + fn initial_transfer(&self) -> DispatchResult { + let frame = self.top_frame(); + + // If it is a delegate call, then we've already transferred tokens in the + // last non-delegate frame. + if frame.delegate_caller.is_some() { + return Ok(()) + } + + let value = frame.value_transferred; + + // Get the account id from the caller. + // If the caller is root there is no account to transfer from, and therefore we can't take + // any `value` other than 0. + let caller = match self.caller() { + Origin::Signed(caller) => caller, + Origin::Root if value.is_zero() => return Ok(()), + Origin::Root => return DispatchError::RootNotAllowed.into(), + }; + Self::transfer(Preservation::Preserve, &caller, &frame.account_id, value) + } + + /// Reference to the current (top) frame. + fn top_frame(&self) -> &Frame { + top_frame!(self) + } + + /// Mutable reference to the current (top) frame. + fn top_frame_mut(&mut self) -> &mut Frame { + top_frame_mut!(self) + } + + /// Iterator over all frames. + /// + /// The iterator starts with the top frame and ends with the root frame. + fn frames(&self) -> impl Iterator> { + core::iter::once(&self.first_frame).chain(&self.frames).rev() + } + + /// Same as `frames` but with a mutable reference as iterator item. + fn frames_mut(&mut self) -> impl Iterator> { + core::iter::once(&mut self.first_frame).chain(&mut self.frames).rev() + } + + /// Returns whether the current contract is on the stack multiple times. + fn is_recursive(&self) -> bool { + let account_id = &self.top_frame().account_id; + self.frames().skip(1).any(|f| &f.account_id == account_id) + } + + /// Returns whether the specified contract allows to be reentered right now. + fn allows_reentry(&self, id: &AccountIdOf) -> bool { + !self.frames().any(|f| &f.account_id == id && !f.allows_reentry) + } +} + +impl<'a, T, E> Ext for Stack<'a, T, E> +where + T: Config, + E: Executable, +{ + type T = T; + + fn call( + &mut self, + gas_limit: Weight, + deposit_limit: BalanceOf, + to: T::AccountId, + value: BalanceOf, + input_data: Vec, + allows_reentry: bool, + read_only: bool, + ) -> Result { + // Before pushing the new frame: Protect the caller contract against reentrancy attacks. + // It is important to do this before calling `allows_reentry` so that a direct recursion + // is caught by it. + self.top_frame_mut().allows_reentry = allows_reentry; + + let try_call = || { + if !self.allows_reentry(&to) { + return Err(>::ReentranceDenied.into()) + } + + // We ignore instantiate frames in our search for a cached contract. + // Otherwise it would be possible to recursively call a contract from its own + // constructor: We disallow calling not fully constructed contracts. + let cached_info = self + .frames() + .find(|f| f.entry_point == ExportedFunction::Call && f.account_id == to) + .and_then(|f| match &f.contract_info { + CachedContract::Cached(contract) => Some(contract.clone()), + _ => None, + }); + let executable = self.push_frame( + FrameArgs::Call { dest: to, cached_info, delegated_call: None }, + value, + gas_limit, + deposit_limit, + // Enable read-only access if requested; cannot disable it if already set. + read_only || self.is_read_only(), + )?; + self.run(executable, input_data) + }; + + // We need to make sure to reset `allows_reentry` even on failure. + let result = try_call(); + + // Protection is on a per call basis. + self.top_frame_mut().allows_reentry = true; + + result + } + + fn delegate_call( + &mut self, + code_hash: CodeHash, + input_data: Vec, + ) -> Result { + let executable = E::from_storage(code_hash, self.gas_meter_mut())?; + let top_frame = self.top_frame_mut(); + let contract_info = top_frame.contract_info().clone(); + let account_id = top_frame.account_id.clone(); + let value = top_frame.value_transferred; + let executable = self.push_frame( + FrameArgs::Call { + dest: account_id, + cached_info: Some(contract_info), + delegated_call: Some(DelegatedCall { executable, caller: self.caller().clone() }), + }, + value, + Weight::zero(), + BalanceOf::::zero(), + self.is_read_only(), + )?; + self.run(executable, input_data) + } + + fn instantiate( + &mut self, + gas_limit: Weight, + deposit_limit: BalanceOf, + code_hash: CodeHash, + value: BalanceOf, + input_data: Vec, + salt: &[u8], + ) -> Result<(AccountIdOf, ExecReturnValue), ExecError> { + let executable = E::from_storage(code_hash, self.gas_meter_mut())?; + let sender = &self.top_frame().account_id; + let executable = self.push_frame( + FrameArgs::Instantiate { + sender: sender.clone(), + executable, + salt, + input_data: input_data.as_ref(), + }, + value, + gas_limit, + deposit_limit, + self.is_read_only(), + )?; + let account_id = self.top_frame().account_id.clone(); + self.run(executable, input_data).map(|ret| (account_id, ret)) + } + + fn terminate(&mut self, beneficiary: &AccountIdOf) -> DispatchResult { + if self.is_recursive() { + return Err(Error::::TerminatedWhileReentrant.into()) + } + let frame = self.top_frame_mut(); + let info = frame.terminate(); + frame.nested_storage.terminate(&info, beneficiary.clone()); + + info.queue_trie_for_deletion(); + ContractInfoOf::::remove(&frame.account_id); + Self::decrement_refcount(info.code_hash); + + for (code_hash, deposit) in info.delegate_dependencies() { + Self::decrement_refcount(*code_hash); + frame + .nested_storage + .charge_deposit(frame.account_id.clone(), StorageDeposit::Refund(*deposit)); + } + + Contracts::::deposit_event(Event::Terminated { + contract: frame.account_id.clone(), + beneficiary: beneficiary.clone(), + }); + Ok(()) + } + + fn transfer(&mut self, to: &T::AccountId, value: BalanceOf) -> DispatchResult { + Self::transfer(Preservation::Preserve, &self.top_frame().account_id, to, value) + } + + fn get_storage(&mut self, key: &Key) -> Option> { + self.top_frame_mut().contract_info().read(key) + } + + fn get_storage_size(&mut self, key: &Key) -> Option { + self.top_frame_mut().contract_info().size(key.into()) + } + + fn set_storage( + &mut self, + key: &Key, + value: Option>, + take_old: bool, + ) -> Result { + let frame = self.top_frame_mut(); + frame.contract_info.get(&frame.account_id).write( + key.into(), + value, + Some(&mut frame.nested_storage), + take_old, + ) + } + + fn get_transient_storage(&self, key: &Key) -> Option> { + self.transient_storage.read(self.address(), key) + } + + fn get_transient_storage_size(&self, key: &Key) -> Option { + self.transient_storage.read(self.address(), key).map(|value| value.len() as _) + } + + fn set_transient_storage( + &mut self, + key: &Key, + value: Option>, + take_old: bool, + ) -> Result { + let account_id = self.address().clone(); + self.transient_storage.write(&account_id, key, value, take_old) + } + + fn address(&self) -> &T::AccountId { + &self.top_frame().account_id + } + + fn caller(&self) -> Origin { + if let Some(caller) = &self.top_frame().delegate_caller { + caller.clone() + } else { + self.frames() + .nth(1) + .map(|f| Origin::from_account_id(f.account_id.clone())) + .unwrap_or(self.origin.clone()) + } + } + + fn is_contract(&self, address: &T::AccountId) -> bool { + ContractInfoOf::::contains_key(&address) + } + + fn code_hash(&self, address: &T::AccountId) -> Option> { + >::get(&address).map(|contract| contract.code_hash) + } + + fn own_code_hash(&mut self) -> &CodeHash { + &self.top_frame_mut().contract_info().code_hash + } + + fn caller_is_origin(&self) -> bool { + self.origin == self.caller() + } + + fn caller_is_root(&self) -> bool { + // if the caller isn't origin, then it can't be root. + self.caller_is_origin() && self.origin == Origin::Root + } + + fn balance(&self) -> BalanceOf { + T::Currency::reducible_balance( + &self.top_frame().account_id, + Preservation::Preserve, + Fortitude::Polite, + ) + } + + fn value_transferred(&self) -> BalanceOf { + self.top_frame().value_transferred + } + + fn now(&self) -> &MomentOf { + &self.timestamp + } + + fn minimum_balance(&self) -> BalanceOf { + T::Currency::minimum_balance() + } + + fn deposit_event(&mut self, topics: Vec, data: Vec) { + Contracts::::deposit_indexed_event( + topics, + Event::ContractEmitted { contract: self.top_frame().account_id.clone(), data }, + ); + } + + fn block_number(&self) -> BlockNumberFor { + self.block_number + } + + fn max_value_size(&self) -> u32 { + limits::PAYLOAD_BYTES + } + + fn get_weight_price(&self, weight: Weight) -> BalanceOf { + T::WeightPrice::convert(weight) + } + + fn gas_meter(&self) -> &GasMeter { + &self.top_frame().nested_gas + } + + fn gas_meter_mut(&mut self) -> &mut GasMeter { + &mut self.top_frame_mut().nested_gas + } + + fn charge_storage(&mut self, diff: &Diff) { + self.top_frame_mut().nested_storage.charge(diff) + } + + fn debug_buffer_enabled(&self) -> bool { + self.debug_message.is_some() + } + + fn append_debug_buffer(&mut self, msg: &str) -> bool { + if let Some(buffer) = &mut self.debug_message { + buffer + .try_extend(&mut msg.bytes()) + .map_err(|_| { + log::debug!( + target: LOG_TARGET, + "Debug buffer (of {} bytes) exhausted!", + limits::DEBUG_BUFFER_BYTES, + ) + }) + .ok(); + true + } else { + false + } + } + + fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo { + let mut origin: T::RuntimeOrigin = RawOrigin::Signed(self.address().clone()).into(); + origin.add_filter(T::CallFilter::contains); + call.dispatch(origin) + } + + fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()> { + secp256k1_ecdsa_recover_compressed(signature, message_hash).map_err(|_| ()) + } + + fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool { + sp_io::crypto::sr25519_verify( + &SR25519Signature::from(*signature), + message, + &SR25519Public::from(*pub_key), + ) + } + + fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> { + ECDSAPublic::from(*pk).to_eth_address() + } + + #[cfg(any(test, feature = "runtime-benchmarks"))] + fn contract_info(&mut self) -> &mut ContractInfo { + self.top_frame_mut().contract_info() + } + + #[cfg(feature = "runtime-benchmarks")] + fn transient_storage(&mut self) -> &mut TransientStorage { + &mut self.transient_storage + } + + fn set_code_hash(&mut self, hash: CodeHash) -> DispatchResult { + let frame = top_frame_mut!(self); + + let info = frame.contract_info(); + + let prev_hash = info.code_hash; + info.code_hash = hash; + + let code_info = CodeInfoOf::::get(hash).ok_or(Error::::CodeNotFound)?; + + let old_base_deposit = info.storage_base_deposit(); + let new_base_deposit = info.update_base_deposit(&code_info); + let deposit = StorageDeposit::Charge(new_base_deposit) + .saturating_sub(&StorageDeposit::Charge(old_base_deposit)); + + frame.nested_storage.charge_deposit(frame.account_id.clone(), deposit); + + Self::increment_refcount(hash)?; + Self::decrement_refcount(prev_hash); + Contracts::::deposit_event(Event::ContractCodeUpdated { + contract: frame.account_id.clone(), + new_code_hash: hash, + old_code_hash: prev_hash, + }); + Ok(()) + } + + fn increment_refcount(code_hash: CodeHash) -> DispatchResult { + >::mutate(code_hash, |existing| -> Result<(), DispatchError> { + if let Some(info) = existing { + *info.refcount_mut() = info.refcount().saturating_add(1); + Ok(()) + } else { + Err(Error::::CodeNotFound.into()) + } + }) + } + + fn decrement_refcount(code_hash: CodeHash) { + >::mutate(code_hash, |existing| { + if let Some(info) = existing { + *info.refcount_mut() = info.refcount().saturating_sub(1); + } + }); + } + + fn lock_delegate_dependency(&mut self, code_hash: CodeHash) -> DispatchResult { + let frame = self.top_frame_mut(); + let info = frame.contract_info.get(&frame.account_id); + ensure!(code_hash != info.code_hash, Error::::CannotAddSelfAsDelegateDependency); + + let code_info = CodeInfoOf::::get(code_hash).ok_or(Error::::CodeNotFound)?; + let deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit()); + + info.lock_delegate_dependency(code_hash, deposit)?; + Self::increment_refcount(code_hash)?; + frame + .nested_storage + .charge_deposit(frame.account_id.clone(), StorageDeposit::Charge(deposit)); + Ok(()) + } + + fn unlock_delegate_dependency(&mut self, code_hash: &CodeHash) -> DispatchResult { + let frame = self.top_frame_mut(); + let info = frame.contract_info.get(&frame.account_id); + + let deposit = info.unlock_delegate_dependency(code_hash)?; + Self::decrement_refcount(*code_hash); + frame + .nested_storage + .charge_deposit(frame.account_id.clone(), StorageDeposit::Refund(deposit)); + Ok(()) + } + + fn locked_delegate_dependencies_count(&mut self) -> usize { + self.top_frame_mut().contract_info().delegate_dependencies_count() + } + + fn is_read_only(&self) -> bool { + self.top_frame().read_only + } +} + +mod sealing { + use super::*; + + pub trait Sealed {} + + impl<'a, T: Config, E> Sealed for Stack<'a, T, E> {} +} + +/// These tests exercise the executive layer. +/// +/// In these tests the VM/loader are mocked. Instead of dealing with wasm bytecode they use simple +/// closures. This allows you to tackle executive logic more thoroughly without writing a +/// wasm VM code. +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + exec::ExportedFunction::*, + gas::GasMeter, + test_utils::*, + tests::{ + test_utils::{get_balance, place_contract, set_balance}, + ExtBuilder, RuntimeCall, RuntimeEvent as MetaEvent, Test, TestFilter, + }, + Error, + }; + use assert_matches::assert_matches; + use codec::{Decode, Encode}; + use frame_support::{assert_err, assert_ok, parameter_types}; + use frame_system::{EventRecord, Phase}; + use pallet_revive_uapi::ReturnFlags; + use pretty_assertions::assert_eq; + use sp_runtime::{traits::Hash, DispatchError}; + use std::{cell::RefCell, collections::hash_map::HashMap, rc::Rc}; + + type System = frame_system::Pallet; + + type MockStack<'a> = Stack<'a, Test, MockExecutable>; + + parameter_types! { + static Loader: MockLoader = MockLoader::default(); + } + + fn events() -> Vec> { + System::events() + .into_iter() + .filter_map(|meta| match meta.event { + MetaEvent::Contracts(contract_event) => Some(contract_event), + _ => None, + }) + .collect() + } + + struct MockCtx<'a> { + ext: &'a mut MockStack<'a>, + input_data: Vec, + } + + #[derive(Clone)] + struct MockExecutable { + func: Rc Fn(MockCtx<'a>, &Self) -> ExecResult + 'static>, + func_type: ExportedFunction, + code_hash: CodeHash, + code_info: CodeInfo, + } + + #[derive(Default, Clone)] + pub struct MockLoader { + map: HashMap, MockExecutable>, + counter: u64, + } + + impl MockLoader { + fn code_hashes() -> Vec> { + Loader::get().map.keys().copied().collect() + } + + fn insert( + func_type: ExportedFunction, + f: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static, + ) -> CodeHash { + Loader::mutate(|loader| { + // Generate code hashes as monotonically increasing values. + let hash = ::Hash::from_low_u64_be(loader.counter); + loader.counter += 1; + loader.map.insert( + hash, + MockExecutable { + func: Rc::new(f), + func_type, + code_hash: hash, + code_info: CodeInfo::::new(ALICE), + }, + ); + hash + }) + } + } + + impl Executable for MockExecutable { + fn from_storage( + code_hash: CodeHash, + _gas_meter: &mut GasMeter, + ) -> Result { + Loader::mutate(|loader| { + loader.map.get(&code_hash).cloned().ok_or(Error::::CodeNotFound.into()) + }) + } + + fn execute>( + self, + ext: &mut E, + function: ExportedFunction, + input_data: Vec, + ) -> ExecResult { + if let Constructor = function { + E::increment_refcount(self.code_hash).unwrap(); + } + // # Safety + // + // We know that we **always** call execute with a `MockStack` in this test. + // + // # Note + // + // The transmute is necessary because `execute` has to be generic over all + // `E: Ext`. However, `MockExecutable` can't be generic over `E` as it would + // constitute a cycle. + let ext = unsafe { mem::transmute(ext) }; + if function == self.func_type { + (self.func)(MockCtx { ext, input_data }, &self) + } else { + exec_success() + } + } + + fn code_hash(&self) -> &CodeHash { + &self.code_hash + } + + fn code_info(&self) -> &CodeInfo { + &self.code_info + } + } + + fn exec_success() -> ExecResult { + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + } + + fn exec_trapped() -> ExecResult { + Err(ExecError { error: >::ContractTrapped.into(), origin: ErrorOrigin::Callee }) + } + + #[test] + fn it_works() { + parameter_types! { + static TestData: Vec = vec![0]; + } + + let value = Default::default(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let exec_ch = MockLoader::insert(Call, |_ctx, _executable| { + TestData::mutate(|data| data.push(1)); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, exec_ch); + let mut storage_meter = + storage::meter::Meter::new(&Origin::from_account_id(ALICE), 0, value).unwrap(); + + assert_matches!( + MockStack::run_call( + Origin::from_account_id(ALICE), + BOB, + &mut gas_meter, + &mut storage_meter, + value, + vec![], + None, + ), + Ok(_) + ); + }); + + assert_eq!(TestData::get(), vec![0, 1]); + } + + #[test] + fn transfer_works() { + // This test verifies that a contract is able to transfer + // some funds to another account. + let origin = ALICE; + let dest = BOB; + + ExtBuilder::default().build().execute_with(|| { + set_balance(&origin, 100); + set_balance(&dest, 0); + + MockStack::transfer(Preservation::Preserve, &origin, &dest, 55).unwrap(); + + assert_eq!(get_balance(&origin), 45); + assert_eq!(get_balance(&dest), 55); + }); + } + + #[test] + fn correct_transfer_on_call() { + let origin = ALICE; + let dest = BOB; + let value = 55; + + let success_ch = MockLoader::insert(Call, move |ctx, _| { + assert_eq!(ctx.ext.value_transferred(), value); + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&dest, success_ch); + set_balance(&origin, 100); + let balance = get_balance(&dest); + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, value).unwrap(); + + let _ = MockStack::run_call( + contract_origin.clone(), + dest.clone(), + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + value, + vec![], + None, + ) + .unwrap(); + + assert_eq!(get_balance(&origin), 100 - value); + assert_eq!(get_balance(&dest), balance + value); + }); + } + + #[test] + fn correct_transfer_on_delegate_call() { + let origin = ALICE; + let dest = BOB; + let value = 35; + + let success_ch = MockLoader::insert(Call, move |ctx, _| { + assert_eq!(ctx.ext.value_transferred(), value); + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + }); + + let delegate_ch = MockLoader::insert(Call, move |ctx, _| { + assert_eq!(ctx.ext.value_transferred(), value); + let _ = ctx.ext.delegate_call(success_ch, Vec::new())?; + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&dest, delegate_ch); + set_balance(&origin, 100); + let balance = get_balance(&dest); + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 55).unwrap(); + + let _ = MockStack::run_call( + contract_origin.clone(), + dest.clone(), + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + value, + vec![], + None, + ) + .unwrap(); + + assert_eq!(get_balance(&origin), 100 - value); + assert_eq!(get_balance(&dest), balance + value); + }); + } + + #[test] + fn changes_are_reverted_on_failing_call() { + // This test verifies that changes are reverted on a call which fails (or equally, returns + // a non-zero status code). + let origin = ALICE; + let dest = BOB; + + let return_ch = MockLoader::insert(Call, |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() }) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&dest, return_ch); + set_balance(&origin, 100); + let balance = get_balance(&dest); + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 55).unwrap(); + + let output = MockStack::run_call( + contract_origin.clone(), + dest.clone(), + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 55, + vec![], + None, + ) + .unwrap(); + + assert!(output.did_revert()); + assert_eq!(get_balance(&origin), 100); + assert_eq!(get_balance(&dest), balance); + }); + } + + #[test] + fn balance_too_low() { + // This test verifies that a contract can't send value if it's + // balance is too low. + let origin = ALICE; + let dest = BOB; + + ExtBuilder::default().build().execute_with(|| { + set_balance(&origin, 0); + + let result = MockStack::transfer(Preservation::Preserve, &origin, &dest, 100); + + assert_eq!(result, Err(Error::::TransferFailed.into())); + assert_eq!(get_balance(&origin), 0); + assert_eq!(get_balance(&dest), 0); + }); + } + + #[test] + fn output_is_returned_on_success() { + // Verifies that if a contract returns data with a successful exit status, this data + // is returned from the execution context. + let origin = ALICE; + let dest = BOB; + let return_ch = MockLoader::insert(Call, |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] }) + }); + + ExtBuilder::default().build().execute_with(|| { + let contract_origin = Origin::from_account_id(origin); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + place_contract(&BOB, return_ch); + + let result = MockStack::run_call( + contract_origin, + dest, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + + let output = result.unwrap(); + assert!(!output.did_revert()); + assert_eq!(output.data, vec![1, 2, 3, 4]); + }); + } + + #[test] + fn output_is_returned_on_failure() { + // Verifies that if a contract returns data with a failing exit status, this data + // is returned from the execution context. + let origin = ALICE; + let dest = BOB; + let return_ch = MockLoader::insert(Call, |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1, 2, 3, 4] }) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, return_ch); + let contract_origin = Origin::from_account_id(origin); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin, + dest, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + + let output = result.unwrap(); + assert!(output.did_revert()); + assert_eq!(output.data, vec![1, 2, 3, 4]); + }); + } + + #[test] + fn input_data_to_call() { + let input_data_ch = MockLoader::insert(Call, |ctx, _| { + assert_eq!(ctx.input_data, &[1, 2, 3, 4]); + exec_success() + }); + + // This one tests passing the input data into a contract via call. + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, input_data_ch); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![1, 2, 3, 4], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn input_data_to_instantiate() { + let input_data_ch = MockLoader::insert(Constructor, |ctx, _| { + assert_eq!(ctx.input_data, &[1, 2, 3, 4]); + exec_success() + }); + + // This one tests passing the input data into a contract via instantiate. + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .build() + .execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let executable = + MockExecutable::from_storage(input_data_ch, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 10_000); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new( + &contract_origin, + deposit_limit::(), + min_balance, + ) + .unwrap(); + + let result = MockStack::run_instantiate( + ALICE, + executable, + &mut gas_meter, + &mut storage_meter, + min_balance, + vec![1, 2, 3, 4], + &[], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn max_depth() { + // This test verifies that when we reach the maximal depth creation of an + // yet another context fails. + parameter_types! { + static ReachedBottom: bool = false; + } + let value = Default::default(); + let recurse_ch = MockLoader::insert(Call, |ctx, _| { + // Try to call into yourself. + let r = ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + BOB, + 0, + vec![], + true, + false, + ); + + ReachedBottom::mutate(|reached_bottom| { + if !*reached_bottom { + // We are first time here, it means we just reached bottom. + // Verify that we've got proper error and set `reached_bottom`. + assert_eq!(r, Err(Error::::MaxCallDepthReached.into())); + *reached_bottom = true; + } else { + // We just unwinding stack here. + assert_matches!(r, Ok(_)); + } + }); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + set_balance(&BOB, 1); + place_contract(&BOB, recurse_ch); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, value).unwrap(); + + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + value, + vec![], + None, + ); + + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn caller_returns_proper_values() { + let origin = ALICE; + let dest = BOB; + + parameter_types! { + static WitnessedCallerBob: Option> = None; + static WitnessedCallerCharlie: Option> = None; + } + + let bob_ch = MockLoader::insert(Call, |ctx, _| { + // Record the caller for bob. + WitnessedCallerBob::mutate(|caller| { + *caller = Some(ctx.ext.caller().account_id().unwrap().clone()) + }); + + // Call into CHARLIE contract. + assert_matches!( + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + true, + false + ), + Ok(_) + ); + exec_success() + }); + let charlie_ch = MockLoader::insert(Call, |ctx, _| { + // Record the caller for charlie. + WitnessedCallerCharlie::mutate(|caller| { + *caller = Some(ctx.ext.caller().account_id().unwrap().clone()) + }); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&dest, bob_ch); + place_contract(&CHARLIE, charlie_ch); + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin.clone(), + dest.clone(), + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + + assert_matches!(result, Ok(_)); + }); + + assert_eq!(WitnessedCallerBob::get(), Some(origin)); + assert_eq!(WitnessedCallerCharlie::get(), Some(dest)); + } + + #[test] + fn is_contract_returns_proper_values() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + // Verify that BOB is a contract + assert!(ctx.ext.is_contract(&BOB)); + // Verify that ALICE is not a contract + assert!(!ctx.ext.is_contract(&ALICE)); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, bob_ch); + + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn code_hash_returns_proper_values() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + // ALICE is not a contract and hence they do not have a code_hash + assert!(ctx.ext.code_hash(&ALICE).is_none()); + // BOB is a contract and hence it has a code_hash + assert!(ctx.ext.code_hash(&BOB).is_some()); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + // ALICE (not contract) -> BOB (contract) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn own_code_hash_returns_proper_values() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + let code_hash = ctx.ext.code_hash(&BOB).unwrap(); + assert_eq!(*ctx.ext.own_code_hash(), code_hash); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, bob_ch); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + // ALICE (not contract) -> BOB (contract) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn caller_is_origin_returns_proper_values() { + let code_charlie = MockLoader::insert(Call, |ctx, _| { + // BOB is not the origin of the stack call + assert!(!ctx.ext.caller_is_origin()); + exec_success() + }); + + let code_bob = MockLoader::insert(Call, |ctx, _| { + // ALICE is the origin of the call stack + assert!(ctx.ext.caller_is_origin()); + // BOB calls CHARLIE + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true, false) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + // ALICE -> BOB (caller is origin) -> CHARLIE (caller is not origin) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn root_caller_succeeds() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + // root is the origin of the call stack. + assert!(ctx.ext.caller_is_root()); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + let contract_origin = Origin::Root; + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + // root -> BOB (caller is root) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn root_caller_does_not_succeed_when_value_not_zero() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + // root is the origin of the call stack. + assert!(ctx.ext.caller_is_root()); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + let contract_origin = Origin::Root; + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + // root -> BOB (caller is root) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 1, + vec![0], + None, + ); + assert_matches!(result, Err(_)); + }); + } + + #[test] + fn root_caller_succeeds_with_consecutive_calls() { + let code_charlie = MockLoader::insert(Call, |ctx, _| { + // BOB is not root, even though the origin is root. + assert!(!ctx.ext.caller_is_root()); + exec_success() + }); + + let code_bob = MockLoader::insert(Call, |ctx, _| { + // root is the origin of the call stack. + assert!(ctx.ext.caller_is_root()); + // BOB calls CHARLIE. + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true, false) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::Root; + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + // root -> BOB (caller is root) -> CHARLIE (caller is not root) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn address_returns_proper_values() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + // Verify that address matches BOB. + assert_eq!(*ctx.ext.address(), BOB); + + // Call into charlie contract. + assert_matches!( + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + true, + false + ), + Ok(_) + ); + exec_success() + }); + let charlie_ch = MockLoader::insert(Call, |ctx, _| { + assert_eq!(*ctx.ext.address(), CHARLIE); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, bob_ch); + place_contract(&CHARLIE, charlie_ch); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn refuse_instantiate_with_value_below_existential_deposit() { + let dummy_ch = MockLoader::insert(Constructor, |_, _| exec_success()); + + ExtBuilder::default().existential_deposit(15).build().execute_with(|| { + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + assert_matches!( + MockStack::run_instantiate( + ALICE, + executable, + &mut gas_meter, + &mut storage_meter, + 0, // <- zero value + vec![], + &[], + None, + ), + Err(_) + ); + }); + } + + #[test] + fn instantiation_work_with_success_output() { + let dummy_ch = MockLoader::insert(Constructor, |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] }) + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 1000); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, min_balance * 100, min_balance) + .unwrap(); + + let instantiated_contract_address = assert_matches!( + MockStack::run_instantiate( + ALICE, + executable, + &mut gas_meter, + &mut storage_meter, + + min_balance, + vec![], + &[], + None, + ), + Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address + ); + + // Check that the newly created account has the expected code hash and + // there are instantiation event. + assert_eq!( + ContractInfo::::load_code_hash(&instantiated_contract_address).unwrap(), + dummy_ch + ); + assert_eq!( + &events(), + &[Event::Instantiated { + deployer: ALICE, + contract: instantiated_contract_address + }] + ); + }); + } + + #[test] + fn instantiation_fails_with_failing_output() { + let dummy_ch = MockLoader::insert(Constructor, |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] }) + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 1000); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, min_balance * 100, min_balance) + .unwrap(); + + let instantiated_contract_address = assert_matches!( + MockStack::run_instantiate( + ALICE, + executable, + &mut gas_meter, + &mut storage_meter, + + min_balance, + vec![], + &[], + None, + ), + Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address + ); + + // Check that the account has not been created. + assert!( + ContractInfo::::load_code_hash(&instantiated_contract_address).is_none() + ); + assert!(events().is_empty()); + }); + } + + #[test] + fn instantiation_from_contract() { + let dummy_ch = MockLoader::insert(Call, |_, _| exec_success()); + let instantiated_contract_address = Rc::new(RefCell::new(None::>)); + let instantiator_ch = MockLoader::insert(Call, { + let instantiated_contract_address = Rc::clone(&instantiated_contract_address); + move |ctx, _| { + // Instantiate a contract and save it's address in `instantiated_contract_address`. + let (address, output) = ctx + .ext + .instantiate( + Weight::zero(), + BalanceOf::::zero(), + dummy_ch, + ::Currency::minimum_balance(), + vec![], + &[48, 49, 50], + ) + .unwrap(); + + *instantiated_contract_address.borrow_mut() = address.into(); + Ok(output) + } + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + set_balance(&ALICE, min_balance * 100); + place_contract(&BOB, instantiator_ch); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new( + &contract_origin, + min_balance * 10, + min_balance * 10, + ) + .unwrap(); + + assert_matches!( + MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + min_balance * 10, + vec![], + None, + ), + Ok(_) + ); + + let instantiated_contract_address = + instantiated_contract_address.borrow().as_ref().unwrap().clone(); + + // Check that the newly created account has the expected code hash and + // there are instantiation event. + assert_eq!( + ContractInfo::::load_code_hash(&instantiated_contract_address).unwrap(), + dummy_ch + ); + assert_eq!( + &events(), + &[ + Event::Instantiated { + deployer: BOB, + contract: instantiated_contract_address + }, + Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB }, + ] + ); + }); + } + + #[test] + fn instantiation_traps() { + let dummy_ch = MockLoader::insert(Constructor, |_, _| Err("It's a trap!".into())); + let instantiator_ch = MockLoader::insert(Call, { + move |ctx, _| { + // Instantiate a contract and save it's address in `instantiated_contract_address`. + assert_matches!( + ctx.ext.instantiate( + Weight::zero(), + BalanceOf::::zero(), + dummy_ch, + ::Currency::minimum_balance(), + vec![], + &[], + ), + Err(ExecError { + error: DispatchError::Other("It's a trap!"), + origin: ErrorOrigin::Callee, + }) + ); + + exec_success() + } + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + set_balance(&ALICE, 1000); + set_balance(&BOB, 100); + place_contract(&BOB, instantiator_ch); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, 200, 0).unwrap(); + + assert_matches!( + MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ), + Ok(_) + ); + + // The contract wasn't instantiated so we don't expect to see an instantiation + // event here. + assert_eq!( + &events(), + &[Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB },] + ); + }); + } + + #[test] + fn termination_from_instantiate_fails() { + let terminate_ch = MockLoader::insert(Constructor, |ctx, _| { + ctx.ext.terminate(&ALICE).unwrap(); + exec_success() + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let executable = + MockExecutable::from_storage(terminate_ch, &mut gas_meter).unwrap(); + set_balance(&ALICE, 10_000); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 100) + .unwrap(); + + assert_eq!( + MockStack::run_instantiate( + ALICE, + executable, + &mut gas_meter, + &mut storage_meter, + 100, + vec![], + &[], + None, + ), + Err(Error::::TerminatedInConstructor.into()) + ); + + assert_eq!(&events(), &[]); + }); + } + + #[test] + fn in_memory_changes_not_discarded() { + // Call stack: BOB -> CHARLIE (trap) -> BOB' (success) + // This tests verifies some edge case of the contract info cache: + // We change some value in our contract info before calling into a contract + // that calls into ourself. This triggers a case where BOBs contract info + // is written to storage and invalidated by the successful execution of BOB'. + // The trap of CHARLIE reverts the storage changes to BOB. When the root BOB regains + // control it reloads its contract info from storage. We check that changes that + // are made before calling into CHARLIE are not discarded. + let code_bob = MockLoader::insert(Call, |ctx, _| { + if ctx.input_data[0] == 0 { + let info = ctx.ext.contract_info(); + assert_eq!(info.storage_byte_deposit, 0); + info.storage_byte_deposit = 42; + assert_eq!( + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + true, + false + ), + exec_trapped() + ); + assert_eq!(ctx.ext.contract_info().storage_byte_deposit, 42); + } + exec_success() + }); + let code_charlie = MockLoader::insert(Call, |ctx, _| { + assert!(ctx + .ext + .call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![99], true, false) + .is_ok()); + exec_trapped() + }); + + // This one tests passing the input data into a contract via call. + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn recursive_call_during_constructor_fails() { + let code = MockLoader::insert(Constructor, |ctx, _| { + assert_matches!( + ctx.ext.call(Weight::zero(), BalanceOf::::zero(), ctx.ext.address().clone(), 0, vec![], true, false), + Err(ExecError{error, ..}) if error == >::ContractNotFound.into() + ); + exec_success() + }); + + // This one tests passing the input data into a contract via instantiate. + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .build() + .execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let executable = MockExecutable::from_storage(code, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 10_000); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new( + &contract_origin, + deposit_limit::(), + min_balance, + ) + .unwrap(); + + let result = MockStack::run_instantiate( + ALICE, + executable, + &mut gas_meter, + &mut storage_meter, + min_balance, + vec![], + &[], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn printing_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + ctx.ext.append_debug_buffer("This is a test"); + ctx.ext.append_debug_buffer("More text"); + exec_success() + }); + + let mut debug_buffer = DebugBuffer::try_from(Vec::new()).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 10); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + Some(&mut debug_buffer), + ) + .unwrap(); + }); + + assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); + } + + #[test] + fn printing_works_on_fail() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + ctx.ext.append_debug_buffer("This is a test"); + ctx.ext.append_debug_buffer("More text"); + exec_trapped() + }); + + let mut debug_buffer = DebugBuffer::try_from(Vec::new()).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 10); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + let result = MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + Some(&mut debug_buffer), + ); + assert!(result.is_err()); + }); + + assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); + } + + #[test] + fn debug_buffer_is_limited() { + let code_hash = MockLoader::insert(Call, move |ctx, _| { + ctx.ext.append_debug_buffer("overflowing bytes"); + exec_success() + }); + + // Pre-fill the buffer almost up to its limit, leaving not enough space to the message + let debug_buf_before = DebugBuffer::try_from(vec![0u8; DebugBuffer::bound() - 5]).unwrap(); + let mut debug_buf_after = debug_buf_before.clone(); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 10); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + Some(&mut debug_buf_after), + ) + .unwrap(); + assert_eq!(debug_buf_before, debug_buf_after); + }); + } + + #[test] + fn call_reentry_direct_recursion() { + // call the contract passed as input with disabled reentry + let code_bob = MockLoader::insert(Call, |ctx, _| { + let dest = Decode::decode(&mut ctx.input_data.as_ref()).unwrap(); + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), dest, 0, vec![], false, false) + }); + + let code_charlie = MockLoader::insert(Call, |_, _| exec_success()); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + // Calling another contract should succeed + assert_ok!(MockStack::run_call( + contract_origin.clone(), + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + CHARLIE.encode(), + None, + )); + + // Calling into oneself fails + assert_err!( + MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + BOB.encode(), + None, + ) + .map_err(|e| e.error), + >::ReentranceDenied, + ); + }); + } + + #[test] + fn call_deny_reentry() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + if ctx.input_data[0] == 0 { + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + false, + false, + ) + } else { + exec_success() + } + }); + + // call BOB with input set to '1' + let code_charlie = MockLoader::insert(Call, |ctx, _| { + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![1], true, false) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + // BOB -> CHARLIE -> BOB fails as BOB denies reentry. + assert_err!( + MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ) + .map_err(|e| e.error), + >::ReentranceDenied, + ); + }); + } + + #[test] + fn call_runtime_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + let call = RuntimeCall::System(frame_system::Call::remark_with_event { + remark: b"Hello World".to_vec(), + }); + ctx.ext.call_runtime(call).unwrap(); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 10); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + System::reset_events(); + MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + ) + .unwrap(); + + let remark_hash = ::Hashing::hash(b"Hello World"); + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::System(frame_system::Event::Remarked { + sender: BOB, + hash: remark_hash + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: BOB, + }), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn call_runtime_filter() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + use frame_system::Call as SysCall; + use pallet_balances::Call as BalanceCall; + use pallet_utility::Call as UtilCall; + + // remark should still be allowed + let allowed_call = + RuntimeCall::System(SysCall::remark_with_event { remark: b"Hello".to_vec() }); + + // transfers are disallowed by the `TestFiler` (see below) + let forbidden_call = RuntimeCall::Balances(BalanceCall::transfer_allow_death { + dest: CHARLIE, + value: 22, + }); + + // simple cases: direct call + assert_err!( + ctx.ext.call_runtime(forbidden_call.clone()), + frame_system::Error::::CallFiltered + ); + + // as part of a patch: return is OK (but it interrupted the batch) + assert_ok!(ctx.ext.call_runtime(RuntimeCall::Utility(UtilCall::batch { + calls: vec![allowed_call.clone(), forbidden_call, allowed_call] + })),); + + // the transfer wasn't performed + assert_eq!(get_balance(&CHARLIE), 0); + + exec_success() + }); + + TestFilter::set_filter(|call| match call { + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) => false, + _ => true, + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 10); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + System::reset_events(); + MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + ) + .unwrap(); + + let remark_hash = ::Hashing::hash(b"Hello"); + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::System(frame_system::Event::Remarked { + sender: BOB, + hash: remark_hash + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::Utility(pallet_utility::Event::ItemCompleted), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::Utility(pallet_utility::Event::BatchInterrupted { + index: 1, + error: frame_system::Error::::CallFiltered.into() + },), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: BOB, + }), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn nonce() { + let fail_code = MockLoader::insert(Constructor, |_, _| exec_trapped()); + let success_code = MockLoader::insert(Constructor, |_, _| exec_success()); + let succ_fail_code = MockLoader::insert(Constructor, move |ctx, _| { + ctx.ext + .instantiate( + Weight::zero(), + BalanceOf::::zero(), + fail_code, + ctx.ext.minimum_balance() * 100, + vec![], + &[], + ) + .ok(); + exec_success() + }); + let succ_succ_code = MockLoader::insert(Constructor, move |ctx, _| { + let alice_nonce = System::account_nonce(&ALICE); + assert_eq!(System::account_nonce(ctx.ext.address()), 0); + assert_eq!(ctx.ext.caller().account_id().unwrap(), &ALICE); + let (account_id, _) = ctx + .ext + .instantiate( + Weight::zero(), + BalanceOf::::zero(), + success_code, + ctx.ext.minimum_balance() * 100, + vec![], + &[], + ) + .unwrap(); + + assert_eq!(System::account_nonce(&ALICE), alice_nonce); + assert_eq!(System::account_nonce(ctx.ext.address()), 1); + assert_eq!(System::account_nonce(&account_id), 0); + + // a plain call should not influence the account counter + ctx.ext + .call( + Weight::zero(), + BalanceOf::::zero(), + account_id.clone(), + 0, + vec![], + false, + false, + ) + .unwrap(); + + assert_eq!(System::account_nonce(ALICE), alice_nonce); + assert_eq!(System::account_nonce(ctx.ext.address()), 1); + assert_eq!(System::account_nonce(&account_id), 0); + + exec_success() + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .build() + .execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let fail_executable = + MockExecutable::from_storage(fail_code, &mut gas_meter).unwrap(); + let success_executable = + MockExecutable::from_storage(success_code, &mut gas_meter).unwrap(); + let succ_fail_executable = + MockExecutable::from_storage(succ_fail_code, &mut gas_meter).unwrap(); + let succ_succ_executable = + MockExecutable::from_storage(succ_succ_code, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 10_000); + set_balance(&BOB, min_balance * 10_000); + let contract_origin = Origin::from_account_id(BOB); + let mut storage_meter = storage::meter::Meter::new( + &contract_origin, + deposit_limit::(), + min_balance * 100, + ) + .unwrap(); + + // fail should not increment + MockStack::run_instantiate( + ALICE, + fail_executable, + &mut gas_meter, + &mut storage_meter, + min_balance * 100, + vec![], + &[], + None, + ) + .ok(); + assert_eq!(System::account_nonce(&ALICE), 0); + + assert_ok!(MockStack::run_instantiate( + ALICE, + success_executable, + &mut gas_meter, + &mut storage_meter, + min_balance * 100, + vec![], + &[], + None, + )); + assert_eq!(System::account_nonce(&ALICE), 1); + + assert_ok!(MockStack::run_instantiate( + ALICE, + succ_fail_executable, + &mut gas_meter, + &mut storage_meter, + min_balance * 200, + vec![], + &[], + None, + )); + assert_eq!(System::account_nonce(&ALICE), 2); + + assert_ok!(MockStack::run_instantiate( + ALICE, + succ_succ_executable, + &mut gas_meter, + &mut storage_meter, + min_balance * 200, + vec![], + &[], + None, + )); + assert_eq!(System::account_nonce(&ALICE), 3); + }); + } + + #[test] + fn set_storage_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + // Write + assert_eq!( + ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![4, 5, 6]), true), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.set_storage(&Key::Fix([3; 32]), None, false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&Key::Fix([4; 32]), None, true), Ok(WriteOutcome::New)); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([5; 32]), Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([6; 32]), Some(vec![]), true), + Ok(WriteOutcome::New) + ); + + // Overwrite + assert_eq!( + ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![42]), false), + Ok(WriteOutcome::Overwritten(3)) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![48]), true), + Ok(WriteOutcome::Taken(vec![4, 5, 6])) + ); + assert_eq!(ctx.ext.set_storage(&Key::Fix([3; 32]), None, false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&Key::Fix([4; 32]), None, true), Ok(WriteOutcome::New)); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([5; 32]), Some(vec![]), false), + Ok(WriteOutcome::Overwritten(0)) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([6; 32]), Some(vec![]), true), + Ok(WriteOutcome::Taken(vec![])) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn set_storage_varsized_key_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + // Write + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([1; 64].to_vec()).unwrap(), + Some(vec![1, 2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([2; 19].to_vec()).unwrap(), + Some(vec![4, 5, 6]), + true + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::try_from_var([3; 19].to_vec()).unwrap(), None, false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::try_from_var([4; 64].to_vec()).unwrap(), None, true), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([5; 30].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([6; 128].to_vec()).unwrap(), + Some(vec![]), + true + ), + Ok(WriteOutcome::New) + ); + + // Overwrite + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([1; 64].to_vec()).unwrap(), + Some(vec![42, 43, 44]), + false + ), + Ok(WriteOutcome::Overwritten(3)) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([2; 19].to_vec()).unwrap(), + Some(vec![48]), + true + ), + Ok(WriteOutcome::Taken(vec![4, 5, 6])) + ); + assert_eq!( + ctx.ext.set_storage(&Key::try_from_var([3; 19].to_vec()).unwrap(), None, false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::try_from_var([4; 64].to_vec()).unwrap(), None, true), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([5; 30].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::Overwritten(0)) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([6; 128].to_vec()).unwrap(), + Some(vec![]), + true + ), + Ok(WriteOutcome::Taken(vec![])) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn get_storage_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.get_storage(&Key::Fix([1; 32])), Some(vec![1, 2, 3])); + assert_eq!(ctx.ext.get_storage(&Key::Fix([2; 32])), Some(vec![])); + assert_eq!(ctx.ext.get_storage(&Key::Fix([3; 32])), None); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn get_storage_size_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.get_storage_size(&Key::Fix([1; 32])), Some(3)); + assert_eq!(ctx.ext.get_storage_size(&Key::Fix([2; 32])), Some(0)); + assert_eq!(ctx.ext.get_storage_size(&Key::Fix([3; 32])), None); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn get_storage_varsized_key_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([1; 19].to_vec()).unwrap(), + Some(vec![1, 2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([2; 16].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.get_storage(&Key::try_from_var([1; 19].to_vec()).unwrap()), + Some(vec![1, 2, 3]) + ); + assert_eq!( + ctx.ext.get_storage(&Key::try_from_var([2; 16].to_vec()).unwrap()), + Some(vec![]) + ); + assert_eq!(ctx.ext.get_storage(&Key::try_from_var([3; 8].to_vec()).unwrap()), None); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn get_storage_size_varsized_key_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([1; 19].to_vec()).unwrap(), + Some(vec![1, 2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([2; 16].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.get_storage_size(&Key::try_from_var([1; 19].to_vec()).unwrap()), + Some(3) + ); + assert_eq!( + ctx.ext.get_storage_size(&Key::try_from_var([2; 16].to_vec()).unwrap()), + Some(0) + ); + assert_eq!( + ctx.ext.get_storage_size(&Key::try_from_var([3; 8].to_vec()).unwrap()), + None + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn set_transient_storage_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + // Write + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([2; 32]), Some(vec![4, 5, 6]), true), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([3; 32]), None, false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([4; 32]), None, true), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([5; 32]), Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([6; 32]), Some(vec![]), true), + Ok(WriteOutcome::New) + ); + + // Overwrite + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([1; 32]), Some(vec![42]), false), + Ok(WriteOutcome::Overwritten(3)) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([2; 32]), Some(vec![48]), true), + Ok(WriteOutcome::Taken(vec![4, 5, 6])) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([3; 32]), None, false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([4; 32]), None, true), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([5; 32]), Some(vec![]), false), + Ok(WriteOutcome::Overwritten(0)) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([6; 32]), Some(vec![]), true), + Ok(WriteOutcome::Taken(vec![])) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn get_transient_storage_works() { + // Call stack: BOB -> CHARLIE(success) -> BOB' (success) + let storage_key_1 = &Key::Fix([1; 32]); + let storage_key_2 = &Key::Fix([2; 32]); + let storage_key_3 = &Key::Fix([3; 32]); + let code_bob = MockLoader::insert(Call, |ctx, _| { + if ctx.input_data[0] == 0 { + assert_eq!( + ctx.ext.set_transient_storage(storage_key_1, Some(vec![1, 2]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + true, + false, + ), + exec_success() + ); + assert_eq!(ctx.ext.get_transient_storage(storage_key_1), Some(vec![3])); + assert_eq!(ctx.ext.get_transient_storage(storage_key_2), Some(vec![])); + assert_eq!(ctx.ext.get_transient_storage(storage_key_3), None); + } else { + assert_eq!( + ctx.ext.set_transient_storage(storage_key_1, Some(vec![3]), true), + Ok(WriteOutcome::Taken(vec![1, 2])) + ); + assert_eq!( + ctx.ext.set_transient_storage(storage_key_2, Some(vec![]), false), + Ok(WriteOutcome::New) + ); + } + exec_success() + }); + let code_charlie = MockLoader::insert(Call, |ctx, _| { + assert!(ctx + .ext + .call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![99], true, false) + .is_ok()); + // CHARLIE can not read BOB`s storage. + assert_eq!(ctx.ext.get_transient_storage(storage_key_1), None); + exec_success() + }); + + // This one tests passing the input data into a contract via call. + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn get_transient_storage_size_works() { + let storage_key_1 = &Key::Fix([1; 32]); + let storage_key_2 = &Key::Fix([2; 32]); + let storage_key_3 = &Key::Fix([3; 32]); + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_transient_storage(storage_key_1, Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(storage_key_2, Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.get_transient_storage_size(storage_key_1), Some(3)); + assert_eq!(ctx.ext.get_transient_storage_size(storage_key_2), Some(0)); + assert_eq!(ctx.ext.get_transient_storage_size(storage_key_3), None); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn rollback_transient_storage_works() { + // Call stack: BOB -> CHARLIE (trap) -> BOB' (success) + let storage_key = &Key::Fix([1; 32]); + let code_bob = MockLoader::insert(Call, |ctx, _| { + if ctx.input_data[0] == 0 { + assert_eq!( + ctx.ext.set_transient_storage(storage_key, Some(vec![1, 2]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + true, + false + ), + exec_trapped() + ); + assert_eq!(ctx.ext.get_transient_storage(storage_key), Some(vec![1, 2])); + } else { + let overwritten_length = ctx.ext.get_transient_storage_size(storage_key).unwrap(); + assert_eq!( + ctx.ext.set_transient_storage(storage_key, Some(vec![3]), false), + Ok(WriteOutcome::Overwritten(overwritten_length)) + ); + assert_eq!(ctx.ext.get_transient_storage(storage_key), Some(vec![3])); + } + exec_success() + }); + let code_charlie = MockLoader::insert(Call, |ctx, _| { + assert!(ctx + .ext + .call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![99], true, false) + .is_ok()); + exec_trapped() + }); + + // This one tests passing the input data into a contract via call. + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn ecdsa_to_eth_address_returns_proper_value() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + let pubkey_compressed = array_bytes::hex2array_unchecked( + "028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd91", + ); + assert_eq!( + ctx.ext.ecdsa_to_eth_address(&pubkey_compressed).unwrap(), + array_bytes::hex2array_unchecked::<_, 20>( + "09231da7b19A016f9e576d23B16277062F4d46A8" + ) + ); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, bob_ch); + + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + assert_matches!(result, Ok(_)); + }); + } +} diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs new file mode 100644 index 000000000000..2034f39e9bc5 --- /dev/null +++ b/substrate/frame/revive/src/gas.rs @@ -0,0 +1,416 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{exec::ExecError, weights::WeightInfo, Config, Error}; +use core::marker::PhantomData; +use frame_support::{ + dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo}, + weights::Weight, + DefaultNoBound, +}; +use sp_runtime::{traits::Zero, DispatchError}; + +#[cfg(test)] +use std::{any::Any, fmt::Debug}; + +#[derive(Debug, PartialEq, Eq)] +pub struct ChargedAmount(Weight); + +impl ChargedAmount { + pub fn amount(&self) -> Weight { + self.0 + } +} + +/// Meter for syncing the gas between the executor and the gas meter. +#[derive(DefaultNoBound)] +struct EngineMeter { + fuel: u64, + _phantom: PhantomData, +} + +impl EngineMeter { + /// Create a meter with the given fuel limit. + fn new(limit: Weight) -> Self { + Self { + fuel: limit.ref_time().saturating_div(Self::ref_time_per_fuel()), + _phantom: PhantomData, + } + } + + /// Set the fuel left to the given value. + /// Returns the amount of Weight consumed since the last update. + fn set_fuel(&mut self, fuel: u64) -> Weight { + let consumed = self.fuel.saturating_sub(fuel).saturating_mul(Self::ref_time_per_fuel()); + self.fuel = fuel; + Weight::from_parts(consumed, 0) + } + + /// Charge the given amount of gas. + /// Returns the amount of fuel left. + fn charge_ref_time(&mut self, ref_time: u64) -> Result { + let amount = ref_time + .checked_div(Self::ref_time_per_fuel()) + .ok_or(Error::::InvalidSchedule)?; + + self.fuel.checked_sub(amount).ok_or_else(|| Error::::OutOfGas)?; + Ok(Syncable(self.fuel.try_into().map_err(|_| Error::::OutOfGas)?)) + } + + /// How much ref time does each PolkaVM gas correspond to. + fn ref_time_per_fuel() -> u64 { + // We execute 6 different instructions therefore we have to divide the actual + // computed gas costs by 6 to have a rough estimate as to how expensive each + // single executed instruction is going to be. + let instr_cost = T::WeightInfo::instr_i64_load_store(1) + .saturating_sub(T::WeightInfo::instr_i64_load_store(0)) + .ref_time(); + instr_cost / 6 + } +} + +/// Used to capture the gas left before entering a host function. +/// +/// Has to be consumed in order to sync back the gas after leaving the host function. +#[must_use] +pub struct RefTimeLeft(u64); + +/// Resource that needs to be synced to the executor. +/// +/// Wrapped to make sure that the resource will be synced back the the executor. +#[must_use] +pub struct Syncable(polkavm::Gas); + +impl From for polkavm::Gas { + fn from(from: Syncable) -> Self { + from.0 + } +} + +#[cfg(not(test))] +pub trait TestAuxiliaries {} +#[cfg(not(test))] +impl TestAuxiliaries for T {} + +#[cfg(test)] +pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {} +#[cfg(test)] +impl TestAuxiliaries for T {} + +/// This trait represents a token that can be used for charging `GasMeter`. +/// There is no other way of charging it. +/// +/// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added +/// for consistency). If inlined there should be no observable difference compared +/// to a hand-written code. +pub trait Token: Copy + Clone + TestAuxiliaries { + /// Return the amount of gas that should be taken by this token. + /// + /// This function should be really lightweight and must not fail. It is not + /// expected that implementors will query the storage or do any kinds of heavy operations. + /// + /// That said, implementors of this function still can run into overflows + /// while calculating the amount. In this case it is ok to use saturating operations + /// since on overflow they will return `max_value` which should consume all gas. + fn weight(&self) -> Weight; + + /// Returns true if this token is expected to influence the lowest gas limit. + fn influence_lowest_gas_limit(&self) -> bool { + true + } +} + +/// A wrapper around a type-erased trait object of what used to be a `Token`. +#[cfg(test)] +pub struct ErasedToken { + pub description: String, + pub token: Box, +} + +#[derive(DefaultNoBound)] +pub struct GasMeter { + gas_limit: Weight, + /// Amount of gas left from initial gas limit. Can reach zero. + gas_left: Weight, + /// Due to `adjust_gas` and `nested` the `gas_left` can temporarily dip below its final value. + gas_left_lowest: Weight, + /// The amount of resources that was consumed by the execution engine. + /// We have to track it separately in order to avoid the loss of precision that happens when + /// converting from ref_time to the execution engine unit. + engine_meter: EngineMeter, + _phantom: PhantomData, + #[cfg(test)] + tokens: Vec, +} + +impl GasMeter { + pub fn new(gas_limit: Weight) -> Self { + GasMeter { + gas_limit, + gas_left: gas_limit, + gas_left_lowest: gas_limit, + engine_meter: EngineMeter::new(gas_limit), + _phantom: PhantomData, + #[cfg(test)] + tokens: Vec::new(), + } + } + + /// Create a new gas meter by removing gas from the current meter. + /// + /// # Note + /// + /// Passing `0` as amount is interpreted as "all remaining gas". + pub fn nested(&mut self, amount: Weight) -> Self { + let amount = Weight::from_parts( + if amount.ref_time().is_zero() { + self.gas_left().ref_time() + } else { + amount.ref_time() + }, + if amount.proof_size().is_zero() { + self.gas_left().proof_size() + } else { + amount.proof_size() + }, + ) + .min(self.gas_left); + self.gas_left -= amount; + GasMeter::new(amount) + } + + /// Absorb the remaining gas of a nested meter after we are done using it. + pub fn absorb_nested(&mut self, nested: Self) { + self.gas_left_lowest = (self.gas_left + nested.gas_limit) + .saturating_sub(nested.gas_required()) + .min(self.gas_left_lowest); + self.gas_left += nested.gas_left; + } + + /// Account for used gas. + /// + /// Amount is calculated by the given `token`. + /// + /// Returns `OutOfGas` if there is not enough gas or addition of the specified + /// amount of gas has lead to overflow. + /// + /// NOTE that amount isn't consumed if there is not enough gas. This is considered + /// safe because we always charge gas before performing any resource-spending action. + #[inline] + pub fn charge>(&mut self, token: Tok) -> Result { + #[cfg(test)] + { + // Unconditionally add the token to the storage. + let erased_tok = + ErasedToken { description: format!("{:?}", token), token: Box::new(token) }; + self.tokens.push(erased_tok); + } + let amount = token.weight(); + // It is OK to not charge anything on failure because we always charge _before_ we perform + // any action + self.gas_left = self.gas_left.checked_sub(&amount).ok_or_else(|| Error::::OutOfGas)?; + Ok(ChargedAmount(amount)) + } + + /// Adjust a previously charged amount down to its actual amount. + /// + /// This is when a maximum a priori amount was charged and then should be partially + /// refunded to match the actual amount. + pub fn adjust_gas>(&mut self, charged_amount: ChargedAmount, token: Tok) { + if token.influence_lowest_gas_limit() { + self.gas_left_lowest = self.gas_left_lowest(); + } + let adjustment = charged_amount.0.saturating_sub(token.weight()); + self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit); + } + + /// Hand over the gas metering responsibility from the executor to this meter. + /// + /// Needs to be called when entering a host function to update this meter with the + /// gas that was tracked by the executor. It tracks the latest seen total value + /// in order to compute the delta that needs to be charged. + pub fn sync_from_executor( + &mut self, + engine_fuel: polkavm::Gas, + ) -> Result { + let weight_consumed = self + .engine_meter + .set_fuel(engine_fuel.try_into().map_err(|_| Error::::OutOfGas)?); + self.gas_left + .checked_reduce(weight_consumed) + .ok_or_else(|| Error::::OutOfGas)?; + Ok(RefTimeLeft(self.gas_left.ref_time())) + } + + /// Hand over the gas metering responsibility from this meter to the executor. + /// + /// Needs to be called when leaving a host function in order to calculate how much + /// gas needs to be charged from the **executor**. It updates the last seen executor + /// total value so that it is correct when `sync_from_executor` is called the next time. + /// + /// It is important that this does **not** actually sync with the executor. That has + /// to be done by the caller. + pub fn sync_to_executor(&mut self, before: RefTimeLeft) -> Result { + let ref_time_consumed = before.0.saturating_sub(self.gas_left().ref_time()); + self.engine_meter.charge_ref_time(ref_time_consumed) + } + + /// Returns the amount of gas that is required to run the same call. + /// + /// This can be different from `gas_spent` because due to `adjust_gas` the amount of + /// spent gas can temporarily drop and be refunded later. + pub fn gas_required(&self) -> Weight { + self.gas_limit.saturating_sub(self.gas_left_lowest()) + } + + /// Returns how much gas was spent + pub fn gas_consumed(&self) -> Weight { + self.gas_limit.saturating_sub(self.gas_left) + } + + /// Returns how much gas left from the initial budget. + pub fn gas_left(&self) -> Weight { + self.gas_left + } + + /// The amount of gas in terms of engine gas. + pub fn engine_fuel_left(&self) -> Result { + self.engine_meter.fuel.try_into().map_err(|_| >::OutOfGas.into()) + } + + /// Turn this GasMeter into a DispatchResult that contains the actually used gas. + pub fn into_dispatch_result( + self, + result: Result, + base_weight: Weight, + ) -> DispatchResultWithPostInfo + where + E: Into, + { + let post_info = PostDispatchInfo { + actual_weight: Some(self.gas_consumed().saturating_add(base_weight)), + pays_fee: Default::default(), + }; + + result + .map(|_| post_info) + .map_err(|e| DispatchErrorWithPostInfo { post_info, error: e.into().error }) + } + + fn gas_left_lowest(&self) -> Weight { + self.gas_left_lowest.min(self.gas_left) + } + + #[cfg(test)] + pub fn tokens(&self) -> &[ErasedToken] { + &self.tokens + } +} + +#[cfg(test)] +mod tests { + use super::{GasMeter, Token, Weight}; + use crate::tests::Test; + + /// A simple utility macro that helps to match against a + /// list of tokens. + macro_rules! match_tokens { + ($tokens_iter:ident,) => { + }; + ($tokens_iter:ident, $x:expr, $($rest:tt)*) => { + { + let next = ($tokens_iter).next().unwrap(); + let pattern = $x; + + // Note that we don't specify the type name directly in this macro, + // we only have some expression $x of some type. At the same time, we + // have an iterator of Box and to downcast we need to specify + // the type which we want downcast to. + // + // So what we do is we assign `_pattern_typed_next_ref` to a variable which has + // the required type. + // + // Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes + // rustc infer the type `T` (in `downcast_ref`) to be the same as in $x. + + let mut _pattern_typed_next_ref = &pattern; + _pattern_typed_next_ref = match next.token.downcast_ref() { + Some(p) => { + assert_eq!(p, &pattern); + p + } + None => { + panic!("expected type {} got {}", stringify!($x), next.description); + } + }; + } + + match_tokens!($tokens_iter, $($rest)*); + }; + } + + /// A trivial token that charges the specified number of gas units. + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + struct SimpleToken(u64); + impl Token for SimpleToken { + fn weight(&self) -> Weight { + Weight::from_parts(self.0, 0) + } + } + + #[test] + fn it_works() { + let gas_meter = GasMeter::::new(Weight::from_parts(50000, 0)); + assert_eq!(gas_meter.gas_left(), Weight::from_parts(50000, 0)); + } + + #[test] + fn tracing() { + let mut gas_meter = GasMeter::::new(Weight::from_parts(50000, 0)); + assert!(!gas_meter.charge(SimpleToken(1)).is_err()); + + let mut tokens = gas_meter.tokens().iter(); + match_tokens!(tokens, SimpleToken(1),); + } + + // This test makes sure that nothing can be executed if there is no gas. + #[test] + fn refuse_to_execute_anything_if_zero() { + let mut gas_meter = GasMeter::::new(Weight::zero()); + assert!(gas_meter.charge(SimpleToken(1)).is_err()); + } + + // Make sure that the gas meter does not charge in case of overcharge + #[test] + fn overcharge_does_not_charge() { + let mut gas_meter = GasMeter::::new(Weight::from_parts(200, 0)); + + // The first charge is should lead to OOG. + assert!(gas_meter.charge(SimpleToken(300)).is_err()); + + // The gas meter should still contain the full 200. + assert!(gas_meter.charge(SimpleToken(200)).is_ok()); + } + + // Charging the exact amount that the user paid for should be + // possible. + #[test] + fn charge_exact_amount() { + let mut gas_meter = GasMeter::::new(Weight::from_parts(25, 0)); + assert!(!gas_meter.charge(SimpleToken(25)).is_err()); + } +} diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs new file mode 100644 index 000000000000..303c649bc8cf --- /dev/null +++ b/substrate/frame/revive/src/lib.rs @@ -0,0 +1,1354 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![doc = include_str!("../README.md")] +#![allow(rustdoc::private_intra_doc_links)] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "1024")] + +extern crate alloc; +mod address; +mod benchmarking; +mod benchmarking_dummy; +mod exec; +mod gas; +mod primitives; +pub use primitives::*; + +mod limits; +mod storage; +mod transient_storage; +mod wasm; + +pub mod chain_extension; +pub mod debug; +pub mod migration; +pub mod test_utils; +pub mod weights; + +#[cfg(test)] +mod tests; +use crate::{ + exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack}, + gas::GasMeter, + storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, + wasm::{CodeInfo, RuntimeCosts, WasmBlob}, +}; +use codec::{Codec, Decode, Encode, HasCompact}; +use core::fmt::Debug; +use environmental::*; +use frame_support::{ + dispatch::{ + DispatchErrorWithPostInfo, DispatchResultWithPostInfo, GetDispatchInfo, Pays, + PostDispatchInfo, RawOrigin, WithPostDispatchInfo, + }, + ensure, + traits::{ + fungible::{Inspect, Mutate, MutateHold}, + ConstU32, Contains, EnsureOrigin, Get, Time, + }, + weights::{Weight, WeightMeter}, + BoundedVec, RuntimeDebugNoBound, +}; +use frame_system::{ + ensure_signed, + pallet_prelude::{BlockNumberFor, OriginFor}, + EventRecord, Pallet as System, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{BadOrigin, Convert, Dispatchable, Saturating, StaticLookup}, + DispatchError, +}; + +pub use crate::{ + address::{AddressGenerator, DefaultAddressGenerator}, + debug::Tracing, + migration::{MigrateSequence, Migration, NoopMigration}, + pallet::*, +}; +pub use weights::WeightInfo; + +#[cfg(doc)] +pub use crate::wasm::SyscallDoc; + +type CodeHash = ::Hash; +type TrieId = BoundedVec>; +type BalanceOf = + <::Currency as Inspect<::AccountId>>::Balance; +type CodeVec = BoundedVec::MaxCodeLen>; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +type EventRecordOf = + EventRecord<::RuntimeEvent, ::Hash>; +type DebugBuffer = BoundedVec>; + +/// Used as a sentinel value when reading and writing contract memory. +/// +/// It is usually used to signal `None` to a contract when only a primitive is allowed +/// and we don't want to go through encoding a full Rust type. Using `u32::Max` is a safe +/// sentinel because contracts are never allowed to use such a large amount of resources +/// that this value makes sense for a memory location or length. +const SENTINEL: u32 = u32::MAX; + +/// The target that is used for the log output emitted by this crate. +/// +/// Hence you can use this target to selectively increase the log level for this crate. +/// +/// Example: `RUST_LOG=runtime::revive=debug my_code --dev` +const LOG_TARGET: &str = "runtime::revive"; + +/// This version determines which syscalls are available to contracts. +/// +/// Needs to be bumped every time a versioned syscall is added. +const API_VERSION: u16 = 0; + +#[test] +fn api_version_up_to_date() { + assert!( + API_VERSION == crate::wasm::HIGHEST_API_VERSION, + "A new versioned API has been added. The `API_VERSION` needs to be bumped." + ); +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use crate::debug::Debugger; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_runtime::Perbill; + + /// The in-code storage version. + pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + /// The time implementation used to supply timestamps to contracts through `seal_now`. + type Time: Time; + + /// The fungible in which fees are paid and contract balances are held. + #[pallet::no_default] + type Currency: Inspect + + Mutate + + MutateHold; + + /// The overarching event type. + #[pallet::no_default_bounds] + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The overarching call type. + #[pallet::no_default_bounds] + type RuntimeCall: Dispatchable + + GetDispatchInfo + + codec::Decode + + IsType<::RuntimeCall>; + + /// Overarching hold reason. + #[pallet::no_default_bounds] + type RuntimeHoldReason: From; + + /// Filter that is applied to calls dispatched by contracts. + /// + /// Use this filter to control which dispatchables are callable by contracts. + /// This is applied in **addition** to [`frame_system::Config::BaseCallFilter`]. + /// It is recommended to treat this as a whitelist. + /// + /// # Stability + /// + /// The runtime **must** make sure that all dispatchables that are callable by + /// contracts remain stable. In addition [`Self::RuntimeCall`] itself must remain stable. + /// This means that no existing variants are allowed to switch their positions. + /// + /// # Note + /// + /// Note that dispatchables that are called via contracts do not spawn their + /// own wasm instance for each call (as opposed to when called via a transaction). + /// Therefore please make sure to be restrictive about which dispatchables are allowed + /// in order to not introduce a new DoS vector like memory allocation patterns that can + /// be exploited to drive the runtime into a panic. + /// + /// This filter does not apply to XCM transact calls. To impose restrictions on XCM transact + /// calls, you must configure them separately within the XCM pallet itself. + #[pallet::no_default_bounds] + type CallFilter: Contains<::RuntimeCall>; + + /// Used to answer contracts' queries regarding the current weight price. This is **not** + /// used to calculate the actual fee and is only for informational purposes. + #[pallet::no_default_bounds] + type WeightPrice: Convert>; + + /// Describes the weights of the dispatchables of this module and is also used to + /// construct a default cost schedule. + type WeightInfo: WeightInfo; + + /// Type that allows the runtime authors to add new host functions for a contract to call. + #[pallet::no_default_bounds] + type ChainExtension: chain_extension::ChainExtension + Default; + + /// The amount of balance a caller has to pay for each byte of storage. + /// + /// # Note + /// + /// It is safe to chage this value on a live chain as all refunds are pro rata. + #[pallet::constant] + #[pallet::no_default_bounds] + type DepositPerByte: Get>; + + /// The amount of balance a caller has to pay for each storage item. + /// + /// # Note + /// + /// It is safe to chage this value on a live chain as all refunds are pro rata. + #[pallet::constant] + #[pallet::no_default_bounds] + type DepositPerItem: Get>; + + /// The percentage of the storage deposit that should be held for using a code hash. + /// Instantiating a contract, or calling [`chain_extension::Ext::lock_delegate_dependency`] + /// protects the code from being removed. In order to prevent abuse these actions are + /// protected with a percentage of the code deposit. + #[pallet::constant] + type CodeHashLockupDepositPercent: Get; + + /// The address generator used to generate the addresses of contracts. + #[pallet::no_default_bounds] + type AddressGenerator: AddressGenerator; + + /// The maximum length of a contract code in bytes. + /// + /// This value hugely affects the memory requirements of this pallet since all the code of + /// all contracts on the call stack will need to be held in memory. Setting of a correct + /// value will be enforced in [`Pallet::integrity_test`]. + #[pallet::constant] + type MaxCodeLen: Get; + + /// Make contract callable functions marked as `#[unstable]` available. + /// + /// Contracts that use `#[unstable]` functions won't be able to be uploaded unless + /// this is set to `true`. This is only meant for testnets and dev nodes in order to + /// experiment with new features. + /// + /// # Warning + /// + /// Do **not** set to `true` on productions chains. + #[pallet::constant] + type UnsafeUnstableInterface: Get; + + /// Origin allowed to upload code. + /// + /// By default, it is safe to set this to `EnsureSigned`, allowing anyone to upload contract + /// code. + #[pallet::no_default_bounds] + type UploadOrigin: EnsureOrigin; + + /// Origin allowed to instantiate code. + /// + /// # Note + /// + /// This is not enforced when a contract instantiates another contract. The + /// [`Self::UploadOrigin`] should make sure that no code is deployed that does unwanted + /// instantiations. + /// + /// By default, it is safe to set this to `EnsureSigned`, allowing anyone to instantiate + /// contract code. + #[pallet::no_default_bounds] + type InstantiateOrigin: EnsureOrigin; + + /// The sequence of migration steps that will be applied during a migration. + /// + /// # Examples + /// ```ignore + /// use pallet_revive::migration::{v10, v11}; + /// # struct Runtime {}; + /// # struct Currency {}; + /// type Migrations = (v10::Migration, v11::Migration); + /// ``` + /// + /// If you have a single migration step, you can use a tuple with a single element: + /// ```ignore + /// use pallet_revive::migration::v10; + /// # struct Runtime {}; + /// # struct Currency {}; + /// type Migrations = (v10::Migration,); + /// ``` + type Migrations: MigrateSequence; + + /// For most production chains, it's recommended to use the `()` implementation of this + /// trait. This implementation offers additional logging when the log target + /// "runtime::revive" is set to trace. + #[pallet::no_default_bounds] + type Debug: Debugger; + + /// A type that exposes XCM APIs, allowing contracts to interact with other parachains, and + /// execute XCM programs. + #[pallet::no_default_bounds] + type Xcm: xcm_builder::Controller< + OriginFor, + ::RuntimeCall, + BlockNumberFor, + >; + + /// The amount of memory in bytes that parachain nodes alot to the runtime. + /// + /// This is used in [`Pallet::integrity_test`] to make sure that the runtime has enough + /// memory to support this pallet if set to the correct value. + type RuntimeMemory: Get; + + /// The amount of memory in bytes that relay chain validators alot to the PoV. + /// + /// This is used in [`Pallet::integrity_test`] to make sure that the runtime has enough + /// memory to support this pallet if set to the correct value. + /// + /// This value is usually higher than [`Self::RuntimeMemory`] to account for the fact + /// that validators have to hold all storage items in PvF memory. + type PVFMemory: Get; + } + + /// Container for different types that implement [`DefaultConfig`]` of this pallet. + pub mod config_preludes { + use super::*; + use frame_support::{ + derive_impl, + traits::{ConstBool, ConstU32}, + }; + use frame_system::EnsureSigned; + use sp_core::parameter_types; + + type AccountId = sp_runtime::AccountId32; + type Balance = u64; + const UNITS: Balance = 10_000_000_000; + const CENTS: Balance = UNITS / 100; + + pub const fn deposit(items: u32, bytes: u32) -> Balance { + items as Balance * 1 * CENTS + (bytes as Balance) * 1 * CENTS + } + + parameter_types! { + pub const DepositPerItem: Balance = deposit(1, 0); + pub const DepositPerByte: Balance = deposit(0, 1); + pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); + } + + /// A type providing default configurations for this pallet in testing environment. + pub struct TestDefaultConfig; + + impl Time for TestDefaultConfig { + type Moment = u64; + fn now() -> Self::Moment { + unimplemented!("No default `now` implementation in `TestDefaultConfig` provide a custom `T::Time` type.") + } + } + + impl> Convert for TestDefaultConfig { + fn convert(w: Weight) -> T { + w.ref_time().into() + } + } + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)] + impl frame_system::DefaultConfig for TestDefaultConfig {} + + #[frame_support::register_default_impl(TestDefaultConfig)] + impl DefaultConfig for TestDefaultConfig { + #[inject_runtime_type] + type RuntimeEvent = (); + + #[inject_runtime_type] + type RuntimeHoldReason = (); + + #[inject_runtime_type] + type RuntimeCall = (); + + type AddressGenerator = DefaultAddressGenerator; + type CallFilter = (); + type ChainExtension = (); + type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; + type DepositPerByte = DepositPerByte; + type DepositPerItem = DepositPerItem; + type MaxCodeLen = ConstU32<{ 123 * 1024 }>; + type Migrations = (); + type Time = Self; + type UnsafeUnstableInterface = ConstBool; + type UploadOrigin = EnsureSigned; + type InstantiateOrigin = EnsureSigned; + type WeightInfo = (); + type WeightPrice = Self; + type Debug = (); + type Xcm = (); + type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; + type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; + } + } + + #[pallet::event] + pub enum Event { + /// Contract deployed by address at the specified address. + Instantiated { deployer: T::AccountId, contract: T::AccountId }, + + /// Contract has been removed. + /// + /// # Note + /// + /// The only way for a contract to be removed and emitting this event is by calling + /// `seal_terminate`. + Terminated { + /// The contract that was terminated. + contract: T::AccountId, + /// The account that received the contracts remaining balance + beneficiary: T::AccountId, + }, + + /// Code with the specified hash has been stored. + CodeStored { code_hash: T::Hash, deposit_held: BalanceOf, uploader: T::AccountId }, + + /// A custom event emitted by the contract. + ContractEmitted { + /// The contract that emitted the event. + contract: T::AccountId, + /// Data supplied by the contract. Metadata generated during contract compilation + /// is needed to decode it. + data: Vec, + }, + + /// A code with the specified hash was removed. + CodeRemoved { code_hash: T::Hash, deposit_released: BalanceOf, remover: T::AccountId }, + + /// A contract's code was updated. + ContractCodeUpdated { + /// The contract that has been updated. + contract: T::AccountId, + /// New code hash that was set for the contract. + new_code_hash: T::Hash, + /// Previous code hash of the contract. + old_code_hash: T::Hash, + }, + + /// A contract was called either by a plain account or another contract. + /// + /// # Note + /// + /// Please keep in mind that like all events this is only emitted for successful + /// calls. This is because on failure all storage changes including events are + /// rolled back. + Called { + /// The caller of the `contract`. + caller: Origin, + /// The contract that was called. + contract: T::AccountId, + }, + + /// A contract delegate called a code hash. + /// + /// # Note + /// + /// Please keep in mind that like all events this is only emitted for successful + /// calls. This is because on failure all storage changes including events are + /// rolled back. + DelegateCalled { + /// The contract that performed the delegate call and hence in whose context + /// the `code_hash` is executed. + contract: T::AccountId, + /// The code hash that was delegate called. + code_hash: CodeHash, + }, + + /// Some funds have been transferred and held as storage deposit. + StorageDepositTransferredAndHeld { + from: T::AccountId, + to: T::AccountId, + amount: BalanceOf, + }, + + /// Some storage deposit funds have been transferred and released. + StorageDepositTransferredAndReleased { + from: T::AccountId, + to: T::AccountId, + amount: BalanceOf, + }, + } + + #[pallet::error] + pub enum Error { + /// Invalid schedule supplied, e.g. with zero weight of a basic operation. + InvalidSchedule, + /// Invalid combination of flags supplied to `seal_call` or `seal_delegate_call`. + InvalidCallFlags, + /// The executed contract exhausted its gas limit. + OutOfGas, + /// The output buffer supplied to a contract API call was too small. + OutputBufferTooSmall, + /// Performing the requested transfer failed. Probably because there isn't enough + /// free balance in the sender's account. + TransferFailed, + /// Performing a call was denied because the calling depth reached the limit + /// of what is specified in the schedule. + MaxCallDepthReached, + /// No contract was found at the specified address. + ContractNotFound, + /// The code supplied to `instantiate_with_code` exceeds the limit specified in the + /// current schedule. + CodeTooLarge, + /// No code could be found at the supplied code hash. + CodeNotFound, + /// No code info could be found at the supplied code hash. + CodeInfoNotFound, + /// A buffer outside of sandbox memory was passed to a contract API function. + OutOfBounds, + /// Input passed to a contract API function failed to decode as expected type. + DecodingFailed, + /// Contract trapped during execution. + ContractTrapped, + /// The size defined in `T::MaxValueSize` was exceeded. + ValueTooLarge, + /// Termination of a contract is not allowed while the contract is already + /// on the call stack. Can be triggered by `seal_terminate`. + TerminatedWhileReentrant, + /// `seal_call` forwarded this contracts input. It therefore is no longer available. + InputForwarded, + /// The amount of topics passed to `seal_deposit_events` exceeds the limit. + TooManyTopics, + /// The chain does not provide a chain extension. Calling the chain extension results + /// in this error. Note that this usually shouldn't happen as deploying such contracts + /// is rejected. + NoChainExtension, + /// Failed to decode the XCM program. + XCMDecodeFailed, + /// A contract with the same AccountId already exists. + DuplicateContract, + /// A contract self destructed in its constructor. + /// + /// This can be triggered by a call to `seal_terminate`. + TerminatedInConstructor, + /// A call tried to invoke a contract that is flagged as non-reentrant. + ReentranceDenied, + /// A contract called into the runtime which then called back into this pallet. + ReenteredPallet, + /// A contract attempted to invoke a state modifying API while being in read-only mode. + StateChangeDenied, + /// Origin doesn't have enough balance to pay the required storage deposits. + StorageDepositNotEnoughFunds, + /// More storage was created than allowed by the storage deposit limit. + StorageDepositLimitExhausted, + /// Code removal was denied because the code is still in use by at least one contract. + CodeInUse, + /// The contract ran to completion but decided to revert its storage changes. + /// Please note that this error is only returned from extrinsics. When called directly + /// or via RPC an `Ok` will be returned. In this case the caller needs to inspect the flags + /// to determine whether a reversion has taken place. + ContractReverted, + /// The contract failed to compile or is missing the correct entry points. + /// + /// A more detailed error can be found on the node console if debug messages are enabled + /// by supplying `-lruntime::revive=debug`. + CodeRejected, + /// A pending migration needs to complete before the extrinsic can be called. + MigrationInProgress, + /// Migrate dispatch call was attempted but no migration was performed. + NoMigrationPerformed, + /// The contract has reached its maximum number of delegate dependencies. + MaxDelegateDependenciesReached, + /// The dependency was not found in the contract's delegate dependencies. + DelegateDependencyNotFound, + /// The contract already depends on the given delegate dependency. + DelegateDependencyAlreadyExists, + /// Can not add a delegate dependency to the code hash of the contract itself. + CannotAddSelfAsDelegateDependency, + /// Can not add more data to transient storage. + OutOfTransientStorage, + /// The contract tried to call a syscall which does not exist (at its current api level). + InvalidSyscall, + /// Invalid storage flags were passed to one of the storage syscalls. + InvalidStorageFlags, + /// PolkaVM failed during code execution. Probably due to a malformed program. + ExecutionFailed, + } + + /// A reason for the pallet contracts placing a hold on funds. + #[pallet::composite_enum] + pub enum HoldReason { + /// The Pallet has reserved it for storing code on-chain. + CodeUploadDepositReserve, + /// The Pallet has reserved it for storage deposit. + StorageDepositReserve, + } + + /// A mapping from a contract's code hash to its code. + #[pallet::storage] + pub(crate) type PristineCode = StorageMap<_, Identity, CodeHash, CodeVec>; + + /// A mapping from a contract's code hash to its code info. + #[pallet::storage] + pub(crate) type CodeInfoOf = StorageMap<_, Identity, CodeHash, CodeInfo>; + + /// The code associated with a given account. + #[pallet::storage] + pub(crate) type ContractInfoOf = + StorageMap<_, Identity, T::AccountId, ContractInfo>; + + /// Evicted contracts that await child trie deletion. + /// + /// Child trie deletion is a heavy operation depending on the amount of storage items + /// stored in said trie. Therefore this operation is performed lazily in `on_idle`. + #[pallet::storage] + pub(crate) type DeletionQueue = StorageMap<_, Twox64Concat, u32, TrieId>; + + /// A pair of monotonic counters used to track the latest contract marked for deletion + /// and the latest deleted contract in queue. + #[pallet::storage] + pub(crate) type DeletionQueueCounter = + StorageValue<_, DeletionQueueManager, ValueQuery>; + + /// A migration can span across multiple blocks. This storage defines a cursor to track the + /// progress of the migration, enabling us to resume from the last completed position. + #[pallet::storage] + pub(crate) type MigrationInProgress = + StorageValue<_, migration::Cursor, OptionQuery>; + + #[pallet::extra_constants] + impl Pallet { + #[pallet::constant_name(ApiVersion)] + fn api_version() -> u16 { + API_VERSION + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_idle(_block: BlockNumberFor, limit: Weight) -> Weight { + use migration::MigrateResult::*; + let mut meter = WeightMeter::with_limit(limit); + + loop { + match Migration::::migrate(&mut meter) { + // There is not enough weight to perform a migration. + // We can't do anything more, so we return the used weight. + NoMigrationPerformed | InProgress { steps_done: 0 } => return meter.consumed(), + // Migration is still in progress, we can start the next step. + InProgress { .. } => continue, + // Either no migration is in progress, or we are done with all migrations, we + // can do some more other work with the remaining weight. + Completed | NoMigrationInProgress => break, + } + } + + ContractInfo::::process_deletion_queue_batch(&mut meter); + meter.consumed() + } + + fn integrity_test() { + Migration::::integrity_test(); + + // Total runtime memory limit + let max_runtime_mem: u32 = T::RuntimeMemory::get(); + // Memory limits for a single contract: + // Value stack size: 1Mb per contract, default defined in wasmi + const MAX_STACK_SIZE: u32 = 1024 * 1024; + // Heap limit is normally 16 mempages of 64kb each = 1Mb per contract + let max_heap_size = limits::MEMORY_BYTES; + // The root frame is not accounted in CALL_STACK_DEPTH + let max_call_depth = + limits::CALL_STACK_DEPTH.checked_add(1).expect("CallStack size is too big"); + // Transient storage uses a BTreeMap, which has overhead compared to the raw size of + // key-value data. To ensure safety, a margin of 2x the raw key-value size is used. + let max_transient_storage_size = limits::TRANSIENT_STORAGE_BYTES + .checked_mul(2) + .expect("MaxTransientStorageSize is too large"); + + // Check that given configured `MaxCodeLen`, runtime heap memory limit can't be broken. + // + // In worst case, the decoded Wasm contract code would be `x16` times larger than the + // encoded one. This is because even a single-byte wasm instruction has 16-byte size in + // wasmi. This gives us `MaxCodeLen*16` safety margin. + // + // Next, the pallet keeps the Wasm blob for each + // contract, hence we add up `MaxCodeLen` to the safety margin. + // + // The inefficiencies of the freeing-bump allocator + // being used in the client for the runtime memory allocations, could lead to possible + // memory allocations for contract code grow up to `x4` times in some extreme cases, + // which gives us total multiplier of `17*4` for `MaxCodeLen`. + // + // That being said, for every contract executed in runtime, at least `MaxCodeLen*17*4` + // memory should be available. Note that maximum allowed heap memory and stack size per + // each contract (stack frame) should also be counted. + // + // The pallet holds transient storage with a size up to `max_transient_storage_size`. + // + // Finally, we allow 50% of the runtime memory to be utilized by the contracts call + // stack, keeping the rest for other facilities, such as PoV, etc. + // + // This gives us the following formula: + // + // `(MaxCodeLen * 17 * 4 + MAX_STACK_SIZE + max_heap_size) * max_call_depth + + // max_transient_storage_size < max_runtime_mem/2` + // + // Hence the upper limit for the `MaxCodeLen` can be defined as follows: + let code_len_limit = max_runtime_mem + .saturating_div(2) + .saturating_sub(max_transient_storage_size) + .saturating_div(max_call_depth) + .saturating_sub(max_heap_size) + .saturating_sub(MAX_STACK_SIZE) + .saturating_div(17 * 4); + + assert!( + T::MaxCodeLen::get() < code_len_limit, + "Given `CallStack` height {:?}, `MaxCodeLen` should be set less than {:?} \ + (current value is {:?}), to avoid possible runtime oom issues.", + max_call_depth, + code_len_limit, + T::MaxCodeLen::get(), + ); + + // Validators are configured to be able to use more memory than block builders. This is + // because in addition to `max_runtime_mem` they need to hold additional data in + // memory: PoV in multiple copies (1x encoded + 2x decoded) and all storage which + // includes emitted events. The assumption is that storage/events size + // can be a maximum of half of the validator runtime memory - max_runtime_mem. + let max_block_ref_time = T::BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap_or_else(|| T::BlockWeights::get().max_block) + .ref_time(); + let max_payload_size = limits::PAYLOAD_BYTES; + let max_key_size = + Key::try_from_var(alloc::vec![0u8; limits::STORAGE_KEY_BYTES as usize]) + .expect("Key of maximal size shall be created") + .hash() + .len() as u32; + + // We can use storage to store items using the available block ref_time with the + // `set_storage` host function. + let max_storage_size: u32 = ((max_block_ref_time / + (>::weight(&RuntimeCosts::SetStorage { + new_bytes: max_payload_size, + old_bytes: 0, + }) + .ref_time())) + .saturating_mul(max_payload_size.saturating_add(max_key_size) as u64)) + .try_into() + .expect("Storage size too big"); + + let max_pvf_mem: u32 = T::PVFMemory::get(); + let storage_size_limit = max_pvf_mem.saturating_sub(max_runtime_mem) / 2; + + assert!( + max_storage_size < storage_size_limit, + "Maximal storage size {} exceeds the storage limit {}", + max_storage_size, + storage_size_limit + ); + + // We can use storage to store events using the available block ref_time with the + // `deposit_event` host function. The overhead of stored events, which is around 100B, + // is not taken into account to simplify calculations, as it does not change much. + let max_events_size: u32 = ((max_block_ref_time / + (>::weight(&RuntimeCosts::DepositEvent { + num_topic: 0, + len: max_payload_size, + }) + .ref_time())) + .saturating_mul(max_payload_size as u64)) + .try_into() + .expect("Events size too big"); + + assert!( + max_events_size < storage_size_limit, + "Maximal events size {} exceeds the events limit {}", + max_events_size, + storage_size_limit + ); + } + } + + #[pallet::call] + impl Pallet + where + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + { + /// Makes a call to an account, optionally transferring some balance. + /// + /// # Parameters + /// + /// * `dest`: Address of the contract to call. + /// * `value`: The balance to transfer from the `origin` to `dest`. + /// * `gas_limit`: The gas limit enforced when executing the constructor. + /// * `storage_deposit_limit`: The maximum amount of balance that can be charged from the + /// caller to pay for the storage consumed. + /// * `data`: The input data to pass to the contract. + /// + /// * If the account is a smart-contract account, the associated code will be + /// executed and any value will be transferred. + /// * If the account is a regular account, any value will be transferred. + /// * If no account exists and the call value is not less than `existential_deposit`, + /// a regular account will be created and any value will be transferred. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))] + pub fn call( + origin: OriginFor, + dest: AccountIdLookupOf, + #[pallet::compact] value: BalanceOf, + gas_limit: Weight, + #[pallet::compact] storage_deposit_limit: BalanceOf, + data: Vec, + ) -> DispatchResultWithPostInfo { + let dest = T::Lookup::lookup(dest)?; + let mut output = Self::bare_call( + origin, + dest, + value, + gas_limit, + storage_deposit_limit, + data, + DebugInfo::Skip, + CollectEvents::Skip, + ); + if let Ok(return_value) = &output.result { + if return_value.did_revert() { + output.result = Err(>::ContractReverted.into()); + } + } + dispatch_result(output.result, output.gas_consumed, T::WeightInfo::call()) + } + + /// Instantiates a contract from a previously deployed wasm binary. + /// + /// This function is identical to [`Self::instantiate_with_code`] but without the + /// code deployment step. Instead, the `code_hash` of an on-chain deployed wasm binary + /// must be supplied. + #[pallet::call_index(1)] + #[pallet::weight( + T::WeightInfo::instantiate(data.len() as u32, salt.len() as u32).saturating_add(*gas_limit) + )] + pub fn instantiate( + origin: OriginFor, + #[pallet::compact] value: BalanceOf, + gas_limit: Weight, + #[pallet::compact] storage_deposit_limit: BalanceOf, + code_hash: CodeHash, + data: Vec, + salt: Vec, + ) -> DispatchResultWithPostInfo { + let data_len = data.len() as u32; + let salt_len = salt.len() as u32; + let mut output = Self::bare_instantiate( + origin, + value, + gas_limit, + storage_deposit_limit, + Code::Existing(code_hash), + data, + salt, + DebugInfo::Skip, + CollectEvents::Skip, + ); + if let Ok(retval) = &output.result { + if retval.result.did_revert() { + output.result = Err(>::ContractReverted.into()); + } + } + dispatch_result( + output.result.map(|result| result.result), + output.gas_consumed, + T::WeightInfo::instantiate(data_len, salt_len), + ) + } + + /// Instantiates a new contract from the supplied `code` optionally transferring + /// some balance. + /// + /// This dispatchable has the same effect as calling [`Self::upload_code`] + + /// [`Self::instantiate`]. Bundling them together provides efficiency gains. Please + /// also check the documentation of [`Self::upload_code`]. + /// + /// # Parameters + /// + /// * `value`: The balance to transfer from the `origin` to the newly created contract. + /// * `gas_limit`: The gas limit enforced when executing the constructor. + /// * `storage_deposit_limit`: The maximum amount of balance that can be charged/reserved + /// from the caller to pay for the storage consumed. + /// * `code`: The contract code to deploy in raw bytes. + /// * `data`: The input data to pass to the contract constructor. + /// * `salt`: Used for the address derivation. See [`Pallet::contract_address`]. + /// + /// Instantiation is executed as follows: + /// + /// - The supplied `code` is deployed, and a `code_hash` is created for that code. + /// - If the `code_hash` already exists on the chain the underlying `code` will be shared. + /// - The destination address is computed based on the sender, code_hash and the salt. + /// - The smart-contract account is created at the computed address. + /// - The `value` is transferred to the new account. + /// - The `deploy` function is executed in the context of the newly-created account. + #[pallet::call_index(2)] + #[pallet::weight( + T::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32, salt.len() as u32) + .saturating_add(*gas_limit) + )] + pub fn instantiate_with_code( + origin: OriginFor, + #[pallet::compact] value: BalanceOf, + gas_limit: Weight, + #[pallet::compact] storage_deposit_limit: BalanceOf, + code: Vec, + data: Vec, + salt: Vec, + ) -> DispatchResultWithPostInfo { + let code_len = code.len() as u32; + let data_len = data.len() as u32; + let salt_len = salt.len() as u32; + let mut output = Self::bare_instantiate( + origin, + value, + gas_limit, + storage_deposit_limit, + Code::Upload(code), + data, + salt, + DebugInfo::Skip, + CollectEvents::Skip, + ); + if let Ok(retval) = &output.result { + if retval.result.did_revert() { + output.result = Err(>::ContractReverted.into()); + } + } + dispatch_result( + output.result.map(|result| result.result), + output.gas_consumed, + T::WeightInfo::instantiate_with_code(code_len, data_len, salt_len), + ) + } + + /// Upload new `code` without instantiating a contract from it. + /// + /// If the code does not already exist a deposit is reserved from the caller + /// and unreserved only when [`Self::remove_code`] is called. The size of the reserve + /// depends on the size of the supplied `code`. + /// + /// # Note + /// + /// Anyone can instantiate a contract from any uploaded code and thus prevent its removal. + /// To avoid this situation a constructor could employ access control so that it can + /// only be instantiated by permissioned entities. The same is true when uploading + /// through [`Self::instantiate_with_code`]. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::upload_code_determinism_enforced(code.len() as u32))] + pub fn upload_code( + origin: OriginFor, + code: Vec, + #[pallet::compact] storage_deposit_limit: BalanceOf, + ) -> DispatchResult { + Self::bare_upload_code(origin, code, storage_deposit_limit).map(|_| ()) + } + + /// Remove the code stored under `code_hash` and refund the deposit to its owner. + /// + /// A code can only be removed by its original uploader (its owner) and only if it is + /// not used by any contract. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::remove_code())] + pub fn remove_code( + origin: OriginFor, + code_hash: CodeHash, + ) -> DispatchResultWithPostInfo { + Migration::::ensure_migrated()?; + let origin = ensure_signed(origin)?; + >::remove(&origin, code_hash)?; + // we waive the fee because removing unused code is beneficial + Ok(Pays::No.into()) + } + + /// Privileged function that changes the code of an existing contract. + /// + /// This takes care of updating refcounts and all other necessary operations. Returns + /// an error if either the `code_hash` or `dest` do not exist. + /// + /// # Note + /// + /// This does **not** change the address of the contract in question. This means + /// that the contract address is no longer derived from its code hash after calling + /// this dispatchable. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::set_code())] + pub fn set_code( + origin: OriginFor, + dest: AccountIdLookupOf, + code_hash: CodeHash, + ) -> DispatchResult { + Migration::::ensure_migrated()?; + ensure_root(origin)?; + let dest = T::Lookup::lookup(dest)?; + >::try_mutate(&dest, |contract| { + let contract = if let Some(contract) = contract { + contract + } else { + return Err(>::ContractNotFound.into()) + }; + >>::increment_refcount(code_hash)?; + >>::decrement_refcount(contract.code_hash); + Self::deposit_event(Event::ContractCodeUpdated { + contract: dest.clone(), + new_code_hash: code_hash, + old_code_hash: contract.code_hash, + }); + contract.code_hash = code_hash; + Ok(()) + }) + } + + /// When a migration is in progress, this dispatchable can be used to run migration steps. + /// Calls that contribute to advancing the migration have their fees waived, as it's helpful + /// for the chain. Note that while the migration is in progress, the pallet will also + /// leverage the `on_idle` hooks to run migration steps. + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::migrate().saturating_add(*weight_limit))] + pub fn migrate(origin: OriginFor, weight_limit: Weight) -> DispatchResultWithPostInfo { + use migration::MigrateResult::*; + ensure_signed(origin)?; + + let weight_limit = weight_limit.saturating_add(T::WeightInfo::migrate()); + let mut meter = WeightMeter::with_limit(weight_limit); + let result = Migration::::migrate(&mut meter); + + match result { + Completed => Ok(PostDispatchInfo { + actual_weight: Some(meter.consumed()), + pays_fee: Pays::No, + }), + InProgress { steps_done, .. } if steps_done > 0 => Ok(PostDispatchInfo { + actual_weight: Some(meter.consumed()), + pays_fee: Pays::No, + }), + InProgress { .. } => Ok(PostDispatchInfo { + actual_weight: Some(meter.consumed()), + pays_fee: Pays::Yes, + }), + NoMigrationInProgress | NoMigrationPerformed => { + let err: DispatchError = >::NoMigrationPerformed.into(); + Err(err.with_weight(meter.consumed())) + }, + } + } + } +} + +/// Create a dispatch result reflecting the amount of consumed gas. +fn dispatch_result( + result: Result, + gas_consumed: Weight, + base_weight: Weight, +) -> DispatchResultWithPostInfo { + let post_info = PostDispatchInfo { + actual_weight: Some(gas_consumed.saturating_add(base_weight)), + pays_fee: Default::default(), + }; + + result + .map(|_| post_info) + .map_err(|e| DispatchErrorWithPostInfo { post_info, error: e }) +} + +impl Pallet { + /// A generalized version of [`Self::call`]. + /// + /// Identical to [`Self::call`] but tailored towards being called by other code within the + /// runtime as opposed to from an extrinsic. It returns more information and allows the + /// enablement of features that are not suitable for an extrinsic (debugging, event + /// collection). + pub fn bare_call( + origin: OriginFor, + dest: T::AccountId, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: BalanceOf, + data: Vec, + debug: DebugInfo, + collect_events: CollectEvents, + ) -> ContractExecResult, EventRecordOf> { + let mut gas_meter = GasMeter::new(gas_limit); + let mut storage_deposit = Default::default(); + let mut debug_message = if matches!(debug, DebugInfo::UnsafeDebug) { + Some(DebugBuffer::default()) + } else { + None + }; + let try_call = || { + Migration::::ensure_migrated()?; + let origin = Origin::from_runtime_origin(origin)?; + let mut storage_meter = StorageMeter::new(&origin, storage_deposit_limit, value)?; + let result = ExecStack::>::run_call( + origin.clone(), + dest, + &mut gas_meter, + &mut storage_meter, + value, + data, + debug_message.as_mut(), + )?; + storage_deposit = storage_meter.try_into_deposit(&origin)?; + Ok(result) + }; + let result = Self::run_guarded(try_call); + let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { + Some(System::::read_events_no_consensus().map(|e| *e).collect()) + } else { + None + }; + ContractExecResult { + result: result.map_err(|r| r.error), + gas_consumed: gas_meter.gas_consumed(), + gas_required: gas_meter.gas_required(), + storage_deposit, + debug_message: debug_message.unwrap_or_default().to_vec(), + events, + } + } + + /// A generalized version of [`Self::instantiate`] or [`Self::instantiate_with_code`]. + /// + /// Identical to [`Self::instantiate`] or [`Self::instantiate_with_code`] but tailored towards + /// being called by other code within the runtime as opposed to from an extrinsic. It returns + /// more information and allows the enablement of features that are not suitable for an + /// extrinsic (debugging, event collection). + pub fn bare_instantiate( + origin: OriginFor, + value: BalanceOf, + gas_limit: Weight, + mut storage_deposit_limit: BalanceOf, + code: Code>, + data: Vec, + salt: Vec, + debug: DebugInfo, + collect_events: CollectEvents, + ) -> ContractInstantiateResult, EventRecordOf> { + let mut gas_meter = GasMeter::new(gas_limit); + let mut storage_deposit = Default::default(); + let mut debug_message = + if debug == DebugInfo::UnsafeDebug { Some(DebugBuffer::default()) } else { None }; + let try_instantiate = || { + Migration::::ensure_migrated()?; + let instantiate_account = T::InstantiateOrigin::ensure_origin(origin.clone())?; + let (executable, upload_deposit) = match code { + Code::Upload(code) => { + let upload_account = T::UploadOrigin::ensure_origin(origin)?; + let (executable, upload_deposit) = Self::try_upload_code( + upload_account, + code, + storage_deposit_limit, + debug_message.as_mut(), + )?; + storage_deposit_limit.saturating_reduce(upload_deposit); + (executable, upload_deposit) + }, + Code::Existing(code_hash) => + (WasmBlob::from_storage(code_hash, &mut gas_meter)?, Default::default()), + }; + let instantiate_origin = Origin::from_account_id(instantiate_account.clone()); + let mut storage_meter = + StorageMeter::new(&instantiate_origin, storage_deposit_limit, value)?; + let result = ExecStack::>::run_instantiate( + instantiate_account, + executable, + &mut gas_meter, + &mut storage_meter, + value, + data, + &salt, + debug_message.as_mut(), + ); + storage_deposit = storage_meter + .try_into_deposit(&instantiate_origin)? + .saturating_add(&StorageDeposit::Charge(upload_deposit)); + result + }; + let output = Self::run_guarded(try_instantiate); + let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { + Some(System::::read_events_no_consensus().map(|e| *e).collect()) + } else { + None + }; + ContractInstantiateResult { + result: output + .map(|(account_id, result)| InstantiateReturnValue { result, account_id }) + .map_err(|e| e.error), + gas_consumed: gas_meter.gas_consumed(), + gas_required: gas_meter.gas_required(), + storage_deposit, + debug_message: debug_message.unwrap_or_default().to_vec(), + events, + } + } + + /// A generalized version of [`Self::upload_code`]. + /// + /// It is identical to [`Self::upload_code`] and only differs in the information it returns. + pub fn bare_upload_code( + origin: OriginFor, + code: Vec, + storage_deposit_limit: BalanceOf, + ) -> CodeUploadResult, BalanceOf> { + Migration::::ensure_migrated()?; + let origin = T::UploadOrigin::ensure_origin(origin)?; + let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit, None)?; + Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }) + } + + /// Query storage of a specified contract under a specified key. + pub fn get_storage(address: T::AccountId, key: Vec) -> GetStorageResult { + if Migration::::in_progress() { + return Err(ContractAccessError::MigrationInProgress) + } + let contract_info = + ContractInfoOf::::get(&address).ok_or(ContractAccessError::DoesntExist)?; + + let maybe_value = contract_info.read( + &Key::try_from_var(key) + .map_err(|_| ContractAccessError::KeyDecodingFailed)? + .into(), + ); + Ok(maybe_value) + } + + /// Determine the address of a contract. + /// + /// This is the address generation function used by contract instantiation. See + /// [`DefaultAddressGenerator`] for the default implementation. + pub fn contract_address( + deploying_address: &T::AccountId, + code_hash: &CodeHash, + input_data: &[u8], + salt: &[u8], + ) -> T::AccountId { + T::AddressGenerator::contract_address(deploying_address, code_hash, input_data, salt) + } + + /// Uploads new code and returns the Wasm blob and deposit amount collected. + fn try_upload_code( + origin: T::AccountId, + code: Vec, + storage_deposit_limit: BalanceOf, + mut debug_message: Option<&mut DebugBuffer>, + ) -> Result<(WasmBlob, BalanceOf), DispatchError> { + let mut module = WasmBlob::from_code(code, origin).map_err(|(err, msg)| { + debug_message.as_mut().map(|d| d.try_extend(msg.bytes())); + err + })?; + let deposit = module.store_code()?; + ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); + Ok((module, deposit)) + } + + /// Deposit a pallet contracts event. + fn deposit_event(event: Event) { + >::deposit_event(::RuntimeEvent::from(event)) + } + + /// Deposit a pallet contracts indexed event. + fn deposit_indexed_event(topics: Vec, event: Event) { + >::deposit_event_indexed( + &topics, + ::RuntimeEvent::from(event).into(), + ) + } + + /// Return the existential deposit of [`Config::Currency`]. + fn min_balance() -> BalanceOf { + >>::minimum_balance() + } + + /// Run the supplied function `f` if no other instance of this pallet is on the stack. + fn run_guarded Result>(f: F) -> Result { + executing_contract::using_once(&mut false, || { + executing_contract::with(|f| { + // Fail if already entered contract execution + if *f { + return Err(()) + } + // We are entering contract execution + *f = true; + Ok(()) + }) + .expect("Returns `Ok` if called within `using_once`. It is syntactically obvious that this is the case; qed") + .map_err(|_| >::ReenteredPallet.into()) + .map(|_| f()) + .and_then(|r| r) + }) + } +} + +// Set up a global reference to the boolean flag used for the re-entrancy guard. +environmental!(executing_contract: bool); + +sp_api::decl_runtime_apis! { + /// The API used to dry-run contract interactions. + #[api_version(1)] + pub trait ReviveApi where + AccountId: Codec, + Balance: Codec, + BlockNumber: Codec, + Hash: Codec, + EventRecord: Codec, + { + /// Perform a call from a specified account to a given contract. + /// + /// See [`crate::Pallet::bare_call`]. + fn call( + origin: AccountId, + dest: AccountId, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, + input_data: Vec, + ) -> ContractExecResult; + + /// Instantiate a new contract. + /// + /// See `[crate::Pallet::bare_instantiate]`. + fn instantiate( + origin: AccountId, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, + code: Code, + data: Vec, + salt: Vec, + ) -> ContractInstantiateResult; + + /// Upload new code without instantiating a contract from it. + /// + /// See [`crate::Pallet::bare_upload_code`]. + fn upload_code( + origin: AccountId, + code: Vec, + storage_deposit_limit: Option, + ) -> CodeUploadResult; + + /// Query a given storage key in a given contract. + /// + /// Returns `Ok(Some(Vec))` if the storage value exists under the given key in the + /// specified account and `Ok(None)` if it doesn't. If the account specified by the address + /// doesn't exist, or doesn't have a contract then `Err` is returned. + fn get_storage( + address: AccountId, + key: Vec, + ) -> GetStorageResult; + } +} diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs new file mode 100644 index 000000000000..1a714a89d486 --- /dev/null +++ b/substrate/frame/revive/src/limits.rs @@ -0,0 +1,60 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Limits that are observeable by contract code. +//! +//! It is important to never change this limits without supporting the old limits +//! for already deployed contracts. This is what the [`crate::Contract::behaviour_version`] +//! is meant for. This is true for either increasing or decreasing the limit. +//! +//! Limits in this file are different from the limits configured on the [`Config`] trait which are +//! generally only affect actions that cannot be performed by a contract: For example, uploading new +//! code only be done via a transaction but not by a contract. Hence the maximum contract size can +//! be raised (but not lowered) by the runtime configuration. + +/// The maximum depth of the call stack. +/// +/// A 0 means that no callings of other contracts are possible. In other words only the origin +/// called "root contract" is allowed to execute then. +pub const CALL_STACK_DEPTH: u32 = 5; + +/// The maximum number of topics a call to [`crate::SyscallDoc::deposit_event`] can emit. +/// +/// We set it to the same limit that ethereum has. It is unlikely to change. +pub const NUM_EVENT_TOPICS: u32 = 4; + +/// The maximum number of code hashes a contract can lock. +pub const DELEGATE_DEPENDENCIES: u32 = 32; + +/// How much memory do we allow the contract to allocate. +pub const MEMORY_BYTES: u32 = 16 * 64 * 1024; + +/// Maximum size of events (excluding topics) and storage values. +pub const PAYLOAD_BYTES: u32 = 512; + +/// The maximum size of the transient storage in bytes. +/// +/// This includes keys, values, and previous entries used for storage rollback. +pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024; + +/// The maximum allowable length in bytes for (transient) storage keys. +pub const STORAGE_KEY_BYTES: u32 = 128; + +/// The maximum size of the debug buffer contracts can write messages to. +/// +/// The buffer will always be disabled for on-chain execution. +pub const DEBUG_BUFFER_BYTES: u32 = 2 * 1024 * 1024; diff --git a/substrate/frame/revive/src/migration.rs b/substrate/frame/revive/src/migration.rs new file mode 100644 index 000000000000..b67467b322f5 --- /dev/null +++ b/substrate/frame/revive/src/migration.rs @@ -0,0 +1,650 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Multi-block Migration framework for pallet-revive. +//! +//! This module allows us to define a migration as a sequence of [`MigrationStep`]s that can be +//! executed across multiple blocks. +//! +//! # Usage +//! +//! A migration step is defined under `src/migration/vX.rs`, where `X` is the version number. +//! For example, `vX.rs` defines a migration from version `X - 1` to version `X`. +//! +//! ## Example: +//! +//! To configure a migration to `v11` for a runtime using `v10` of pallet-revive on the chain, +//! you would set the `Migrations` type as follows: +//! +//! ```ignore +//! use pallet_revive::migration::{v10, v11}; +//! # pub enum Runtime {}; +//! # struct Currency; +//! type Migrations = (v10::Migration, v11::Migration); +//! ``` +//! +//! ## Notes: +//! +//! - Migrations should always be tested with `try-runtime` before being deployed. +//! - By testing with `try-runtime` against a live network, you ensure that all migration steps work +//! and that you have included the required steps. +//! +//! ## Low Level / Implementation Details +//! +//! When a migration starts and [`OnRuntimeUpgrade::on_runtime_upgrade`] is called, instead of +//! performing the actual migration, we set a custom storage item [`MigrationInProgress`]. +//! This storage item defines a [`Cursor`] for the current migration. +//! +//! If the [`MigrationInProgress`] storage item exists, it means a migration is in progress, and its +//! value holds a cursor for the current migration step. These migration steps are executed during +//! [`Hooks::on_idle`] or when the [`Pallet::migrate`] dispatchable is +//! called. +//! +//! While the migration is in progress, all dispatchables except `migrate`, are blocked, and returns +//! a `MigrationInProgress` error. + +include!(concat!(env!("OUT_DIR"), "/migration_codegen.rs")); + +use crate::{weights::WeightInfo, Config, Error, MigrationInProgress, Pallet, Weight, LOG_TARGET}; +use codec::{Codec, Decode}; +use core::marker::PhantomData; +use frame_support::{ + pallet_prelude::*, + traits::{ConstU32, OnRuntimeUpgrade}, + weights::WeightMeter, +}; +use sp_runtime::Saturating; + +#[cfg(feature = "try-runtime")] +use alloc::vec::Vec; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +const PROOF_ENCODE: &str = "Tuple::max_encoded_len() < Cursor::max_encoded_len()` is verified in `Self::integrity_test()`; qed"; +const PROOF_DECODE: &str = + "We encode to the same type in this trait only. No other code touches this item; qed"; + +fn invalid_version(version: StorageVersion) -> ! { + panic!("Required migration {version:?} not supported by this runtime. This is a bug."); +} + +/// The cursor used to encode the position (usually the last iterated key) of the current migration +/// step. +pub type Cursor = BoundedVec>; + +/// IsFinished describes whether a migration is finished or not. +pub enum IsFinished { + Yes, + No, +} + +/// A trait that allows to migrate storage from one version to another. +/// +/// The migration is done in steps. The migration is finished when +/// `step()` returns `IsFinished::Yes`. +pub trait MigrationStep: Codec + MaxEncodedLen + Default { + /// Returns the version of the migration. + const VERSION: u16; + + /// Returns the maximum weight that can be consumed in a single step. + fn max_step_weight() -> Weight; + + /// Process one step of the migration. + /// + /// Returns whether the migration is finished. + fn step(&mut self, meter: &mut WeightMeter) -> IsFinished; + + /// Verify that the migration step fits into `Cursor`, and that `max_step_weight` is not greater + /// than `max_block_weight`. + fn integrity_test(max_block_weight: Weight) { + if Self::max_step_weight().any_gt(max_block_weight) { + panic!( + "Invalid max_step_weight for Migration {}. Value should be lower than {}", + Self::VERSION, + max_block_weight + ); + } + + let len = ::max_encoded_len(); + let max = Cursor::bound(); + if len > max { + panic!( + "Migration {} has size {} which is bigger than the maximum of {}", + Self::VERSION, + len, + max, + ); + } + } + + /// Execute some pre-checks prior to running the first step of this migration. + #[cfg(feature = "try-runtime")] + fn pre_upgrade_step() -> Result, TryRuntimeError> { + Ok(Vec::new()) + } + + /// Execute some post-checks after running the last step of this migration. + #[cfg(feature = "try-runtime")] + fn post_upgrade_step(_state: Vec) -> Result<(), TryRuntimeError> { + Ok(()) + } +} + +/// A noop migration that can be used when there is no migration to be done for a given version. +#[doc(hidden)] +#[derive(frame_support::DefaultNoBound, Encode, Decode, MaxEncodedLen)] +pub struct NoopMigration; + +impl MigrationStep for NoopMigration { + const VERSION: u16 = N; + fn max_step_weight() -> Weight { + Weight::zero() + } + fn step(&mut self, _meter: &mut WeightMeter) -> IsFinished { + log::debug!(target: LOG_TARGET, "Noop migration for version {}", N); + IsFinished::Yes + } +} + +mod private { + use crate::migration::MigrationStep; + pub trait Sealed {} + #[impl_trait_for_tuples::impl_for_tuples(10)] + #[tuple_types_custom_trait_bound(MigrationStep)] + impl Sealed for Tuple {} +} + +/// Defines a sequence of migrations. +/// +/// The sequence must be defined by a tuple of migrations, each of which must implement the +/// `MigrationStep` trait. Migrations must be ordered by their versions with no gaps. +pub trait MigrateSequence: private::Sealed { + /// Returns the range of versions that this migrations sequence can handle. + /// Migrations must be ordered by their versions with no gaps. + /// + /// The following code will fail to compile: + /// + /// ```compile_fail + /// # use pallet_revive::{NoopMigration, MigrateSequence}; + /// let _ = <(NoopMigration<1>, NoopMigration<3>)>::VERSION_RANGE; + /// ``` + /// The following code will compile: + /// ``` + /// # use pallet_revive::{NoopMigration, MigrateSequence}; + /// let _ = <(NoopMigration<1>, NoopMigration<2>)>::VERSION_RANGE; + /// ``` + const VERSION_RANGE: (u16, u16); + + /// Returns the default cursor for the given version. + fn new(version: StorageVersion) -> Cursor; + + #[cfg(feature = "try-runtime")] + fn pre_upgrade_step(_version: StorageVersion) -> Result, TryRuntimeError> { + Ok(Vec::new()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade_step(_version: StorageVersion, _state: Vec) -> Result<(), TryRuntimeError> { + Ok(()) + } + + /// Execute the migration step until the available weight is consumed. + fn steps(version: StorageVersion, cursor: &[u8], meter: &mut WeightMeter) -> StepResult; + + /// Verify that the migration step fits into `Cursor`, and that `max_step_weight` is not greater + /// than `max_block_weight`. + fn integrity_test(max_block_weight: Weight); + + /// Returns whether migrating from `in_storage` to `target` is supported. + /// + /// A migration is supported if `VERSION_RANGE` is (in_storage + 1, target). + fn is_upgrade_supported(in_storage: StorageVersion, target: StorageVersion) -> bool { + let (low, high) = Self::VERSION_RANGE; + target == high && in_storage + 1 == low + } +} + +/// Performs all necessary migrations based on `StorageVersion`. +/// +/// If `TEST_ALL_STEPS == true` and `try-runtime` is enabled, this will run all the migrations +/// inside `on_runtime_upgrade`. This should be set to false in tests that want to ensure the step +/// by step migration works. +pub struct Migration(PhantomData); + +#[cfg(feature = "try-runtime")] +impl Migration { + fn run_all_steps() -> Result<(), TryRuntimeError> { + let mut meter = &mut WeightMeter::new(); + let name = >::name(); + loop { + let in_progress_version = >::on_chain_storage_version() + 1; + let state = T::Migrations::pre_upgrade_step(in_progress_version)?; + let before = meter.consumed(); + let status = Self::migrate(&mut meter); + log::info!( + target: LOG_TARGET, + "{name}: Migration step {:?} weight = {}", + in_progress_version, + meter.consumed() - before + ); + T::Migrations::post_upgrade_step(in_progress_version, state)?; + if matches!(status, MigrateResult::Completed) { + break + } + } + + let name = >::name(); + log::info!(target: LOG_TARGET, "{name}: Migration steps weight = {}", meter.consumed()); + Ok(()) + } +} + +impl OnRuntimeUpgrade for Migration { + fn on_runtime_upgrade() -> Weight { + let name = >::name(); + let in_code_version = >::in_code_storage_version(); + let on_chain_version = >::on_chain_storage_version(); + + if on_chain_version == in_code_version { + log::warn!( + target: LOG_TARGET, + "{name}: No Migration performed storage_version = latest_version = {:?}", + &on_chain_version + ); + return T::WeightInfo::on_runtime_upgrade_noop() + } + + // In case a migration is already in progress we create the next migration + // (if any) right when the current one finishes. + if Self::in_progress() { + log::warn!( + target: LOG_TARGET, + "{name}: Migration already in progress {:?}", + &on_chain_version + ); + + return T::WeightInfo::on_runtime_upgrade_in_progress() + } + + log::info!( + target: LOG_TARGET, + "{name}: Upgrading storage from {on_chain_version:?} to {in_code_version:?}.", + ); + + let cursor = T::Migrations::new(on_chain_version + 1); + MigrationInProgress::::set(Some(cursor)); + + #[cfg(feature = "try-runtime")] + if TEST_ALL_STEPS { + Self::run_all_steps().unwrap(); + } + + T::WeightInfo::on_runtime_upgrade() + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + // We can't really do much here as our migrations do not happen during the runtime upgrade. + // Instead, we call the migrations `pre_upgrade` and `post_upgrade` hooks when we iterate + // over our migrations. + let on_chain_version = >::on_chain_storage_version(); + let in_code_version = >::in_code_storage_version(); + + if on_chain_version == in_code_version { + return Ok(Default::default()) + } + + log::debug!( + target: LOG_TARGET, + "Requested migration of {} from {:?}(on-chain storage version) to {:?}(in-code storage version)", + >::name(), on_chain_version, in_code_version + ); + + ensure!( + T::Migrations::is_upgrade_supported(on_chain_version, in_code_version), + "Unsupported upgrade: VERSION_RANGE should be (on-chain storage version + 1, in-code storage version)" + ); + + Ok(Default::default()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + if !TEST_ALL_STEPS { + return Ok(()) + } + + log::info!(target: LOG_TARGET, "=== POST UPGRADE CHECKS ==="); + + // Ensure that the hashing algorithm is correct for each storage map. + if let Some(hash) = crate::CodeInfoOf::::iter_keys().next() { + crate::CodeInfoOf::::get(hash).expect("CodeInfo exists for hash; qed"); + } + if let Some(hash) = crate::PristineCode::::iter_keys().next() { + crate::PristineCode::::get(hash).expect("PristineCode exists for hash; qed"); + } + if let Some(account_id) = crate::ContractInfoOf::::iter_keys().next() { + crate::ContractInfoOf::::get(account_id) + .expect("ContractInfo exists for account_id; qed"); + } + if let Some(nonce) = crate::DeletionQueue::::iter_keys().next() { + crate::DeletionQueue::::get(nonce).expect("DeletionQueue exists for nonce; qed"); + } + + Ok(()) + } +} + +/// The result of running the migration. +#[derive(Debug, PartialEq)] +pub enum MigrateResult { + /// No migration was performed + NoMigrationPerformed, + /// No migration currently in progress + NoMigrationInProgress, + /// A migration is in progress + InProgress { steps_done: u32 }, + /// All migrations are completed + Completed, +} + +/// The result of running a migration step. +#[derive(Debug, PartialEq)] +pub enum StepResult { + InProgress { cursor: Cursor, steps_done: u32 }, + Completed { steps_done: u32 }, +} + +impl Migration { + /// Verify that each migration's step of the [`Config::Migrations`] sequence fits into + /// `Cursor`. + pub(crate) fn integrity_test() { + let max_weight = ::BlockWeights::get().max_block; + T::Migrations::integrity_test(max_weight) + } + + /// Execute the multi-step migration. + /// Returns whether or not a migration is in progress + pub(crate) fn migrate(mut meter: &mut WeightMeter) -> MigrateResult { + let name = >::name(); + + if meter.try_consume(T::WeightInfo::migrate()).is_err() { + return MigrateResult::NoMigrationPerformed + } + + MigrationInProgress::::mutate_exists(|progress| { + let Some(cursor_before) = progress.as_mut() else { + meter.consume(T::WeightInfo::migration_noop()); + return MigrateResult::NoMigrationInProgress + }; + + // if a migration is running it is always upgrading to the next version + let storage_version = >::on_chain_storage_version(); + let in_progress_version = storage_version + 1; + + log::info!( + target: LOG_TARGET, + "{name}: Migrating from {:?} to {:?},", + storage_version, + in_progress_version, + ); + + let result = + match T::Migrations::steps(in_progress_version, cursor_before.as_ref(), &mut meter) + { + StepResult::InProgress { cursor, steps_done } => { + *progress = Some(cursor); + MigrateResult::InProgress { steps_done } + }, + StepResult::Completed { steps_done } => { + in_progress_version.put::>(); + if >::in_code_storage_version() != in_progress_version { + log::info!( + target: LOG_TARGET, + "{name}: Next migration is {:?},", + in_progress_version + 1 + ); + *progress = Some(T::Migrations::new(in_progress_version + 1)); + MigrateResult::InProgress { steps_done } + } else { + log::info!( + target: LOG_TARGET, + "{name}: All migrations done. At version {:?},", + in_progress_version + ); + *progress = None; + MigrateResult::Completed + } + }, + }; + + result + }) + } + + pub(crate) fn ensure_migrated() -> DispatchResult { + if Self::in_progress() { + Err(Error::::MigrationInProgress.into()) + } else { + Ok(()) + } + } + + pub(crate) fn in_progress() -> bool { + MigrationInProgress::::exists() + } +} + +#[impl_trait_for_tuples::impl_for_tuples(10)] +#[tuple_types_custom_trait_bound(MigrationStep)] +impl MigrateSequence for Tuple { + const VERSION_RANGE: (u16, u16) = { + let mut versions: (u16, u16) = (0, 0); + for_tuples!( + #( + match versions { + (0, 0) => { + versions = (Tuple::VERSION, Tuple::VERSION); + }, + (min_version, last_version) if Tuple::VERSION == last_version + 1 => { + versions = (min_version, Tuple::VERSION); + }, + _ => panic!("Migrations must be ordered by their versions with no gaps.") + } + )* + ); + versions + }; + + fn new(version: StorageVersion) -> Cursor { + for_tuples!( + #( + if version == Tuple::VERSION { + return Tuple::default().encode().try_into().expect(PROOF_ENCODE) + } + )* + ); + invalid_version(version) + } + + #[cfg(feature = "try-runtime")] + /// Execute the pre-checks of the step associated with this version. + fn pre_upgrade_step(version: StorageVersion) -> Result, TryRuntimeError> { + for_tuples!( + #( + if version == Tuple::VERSION { + return Tuple::pre_upgrade_step() + } + )* + ); + invalid_version(version) + } + + #[cfg(feature = "try-runtime")] + /// Execute the post-checks of the step associated with this version. + fn post_upgrade_step(version: StorageVersion, state: Vec) -> Result<(), TryRuntimeError> { + for_tuples!( + #( + if version == Tuple::VERSION { + return Tuple::post_upgrade_step(state) + } + )* + ); + invalid_version(version) + } + + fn steps(version: StorageVersion, mut cursor: &[u8], meter: &mut WeightMeter) -> StepResult { + for_tuples!( + #( + if version == Tuple::VERSION { + let mut migration = ::decode(&mut cursor) + .expect(PROOF_DECODE); + let max_weight = Tuple::max_step_weight(); + let mut steps_done = 0; + while meter.can_consume(max_weight) { + steps_done.saturating_accrue(1); + if matches!(migration.step(meter), IsFinished::Yes) { + return StepResult::Completed{ steps_done } + } + } + return StepResult::InProgress{cursor: migration.encode().try_into().expect(PROOF_ENCODE), steps_done } + } + )* + ); + invalid_version(version) + } + + fn integrity_test(max_block_weight: Weight) { + for_tuples!( + #( + Tuple::integrity_test(max_block_weight); + )* + ); + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + migration::codegen::LATEST_MIGRATION_VERSION, + tests::{ExtBuilder, Test}, + }; + + #[derive(Default, Encode, Decode, MaxEncodedLen)] + struct MockMigration { + // MockMigration needs `N` steps to finish + count: u16, + } + + impl MigrationStep for MockMigration { + const VERSION: u16 = N; + fn max_step_weight() -> Weight { + Weight::from_all(1) + } + fn step(&mut self, meter: &mut WeightMeter) -> IsFinished { + assert!(self.count != N); + self.count += 1; + meter.consume(Weight::from_all(1)); + if self.count == N { + IsFinished::Yes + } else { + IsFinished::No + } + } + } + + #[test] + fn test_storage_version_matches_last_migration_file() { + assert_eq!(StorageVersion::new(LATEST_MIGRATION_VERSION), crate::pallet::STORAGE_VERSION); + } + + #[test] + fn version_range_works() { + let range = <(MockMigration<1>, MockMigration<2>)>::VERSION_RANGE; + assert_eq!(range, (1, 2)); + } + + #[test] + fn is_upgrade_supported_works() { + type Migrations = (MockMigration<9>, MockMigration<10>, MockMigration<11>); + assert!(Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(11))); + assert!(!Migrations::is_upgrade_supported(StorageVersion::new(9), StorageVersion::new(11))); + assert!(!Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(12))); + } + + #[test] + fn steps_works() { + type Migrations = (MockMigration<2>, MockMigration<3>); + let version = StorageVersion::new(2); + let mut cursor = Migrations::new(version); + + let mut meter = WeightMeter::with_limit(Weight::from_all(1)); + let result = Migrations::steps(version, &cursor, &mut meter); + cursor = alloc::vec![1u8, 0].try_into().unwrap(); + assert_eq!(result, StepResult::InProgress { cursor: cursor.clone(), steps_done: 1 }); + assert_eq!(meter.consumed(), Weight::from_all(1)); + + let mut meter = WeightMeter::with_limit(Weight::from_all(1)); + assert_eq!( + Migrations::steps(version, &cursor, &mut meter), + StepResult::Completed { steps_done: 1 } + ); + } + + #[test] + fn no_migration_in_progress_works() { + type TestMigration = Migration; + + ExtBuilder::default().build().execute_with(|| { + assert_eq!(StorageVersion::get::>(), LATEST_MIGRATION_VERSION); + assert_eq!( + TestMigration::migrate(&mut WeightMeter::new()), + MigrateResult::NoMigrationInProgress + ) + }); + } + + #[test] + fn migration_works() { + type TestMigration = Migration; + + ExtBuilder::default() + .set_storage_version(LATEST_MIGRATION_VERSION - 2) + .build() + .execute_with(|| { + assert_eq!(StorageVersion::get::>(), LATEST_MIGRATION_VERSION - 2); + TestMigration::on_runtime_upgrade(); + for (version, status) in [ + (LATEST_MIGRATION_VERSION - 1, MigrateResult::InProgress { steps_done: 1 }), + (LATEST_MIGRATION_VERSION, MigrateResult::Completed), + ] { + assert_eq!(TestMigration::migrate(&mut WeightMeter::new()), status); + assert_eq!( + >::on_chain_storage_version(), + StorageVersion::new(version) + ); + } + + assert_eq!( + TestMigration::migrate(&mut WeightMeter::new()), + MigrateResult::NoMigrationInProgress + ); + assert_eq!(StorageVersion::get::>(), LATEST_MIGRATION_VERSION); + }); + } +} diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs new file mode 100644 index 000000000000..a4a1133b7104 --- /dev/null +++ b/substrate/frame/revive/src/primitives.rs @@ -0,0 +1,285 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A crate that hosts a common definitions that are relevant for the pallet-revive. + +use alloc::vec::Vec; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::weights::Weight; +use pallet_revive_uapi::ReturnFlags; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Saturating, Zero}, + DispatchError, RuntimeDebug, +}; + +/// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and +/// `ContractsApi::instantiate`. +/// +/// It contains the execution result together with some auxiliary information. +/// +/// #Note +/// +/// It has been extended to include `events` at the end of the struct while not bumping the +/// `ContractsApi` version. Therefore when SCALE decoding a `ContractResult` its trailing data +/// should be ignored to avoid any potential compatibility issues. +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ContractResult { + /// How much weight was consumed during execution. + pub gas_consumed: Weight, + /// How much weight is required as gas limit in order to execute this call. + /// + /// This value should be used to determine the weight limit for on-chain execution. + /// + /// # Note + /// + /// This can only different from [`Self::gas_consumed`] when weight pre charging + /// is used. Currently, only `seal_call_runtime` makes use of pre charging. + /// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging + /// when a non-zero `gas_limit` argument is supplied. + pub gas_required: Weight, + /// How much balance was paid by the origin into the contract's deposit account in order to + /// pay for storage. + /// + /// The storage deposit is never actually charged from the origin in case of [`Self::result`] + /// is `Err`. This is because on error all storage changes are rolled back including the + /// payment of the deposit. + pub storage_deposit: StorageDeposit, + /// An optional debug message. This message is only filled when explicitly requested + /// by the code that calls into the contract. Otherwise it is empty. + /// + /// The contained bytes are valid UTF-8. This is not declared as `String` because + /// this type is not allowed within the runtime. + /// + /// Clients should not make any assumptions about the format of the buffer. + /// They should just display it as-is. It is **not** only a collection of log lines + /// provided by a contract but a formatted buffer with different sections. + /// + /// # Note + /// + /// The debug message is never generated during on-chain execution. It is reserved for + /// RPC calls. + pub debug_message: Vec, + /// The execution result of the wasm code. + pub result: R, + /// The events that were emitted during execution. It is an option as event collection is + /// optional. + pub events: Option>, +} + +/// Result type of a `bare_call` call as well as `ContractsApi::call`. +pub type ContractExecResult = + ContractResult, Balance, EventRecord>; + +/// Result type of a `bare_instantiate` call as well as `ContractsApi::instantiate`. +pub type ContractInstantiateResult = + ContractResult, DispatchError>, Balance, EventRecord>; + +/// Result type of a `bare_code_upload` call. +pub type CodeUploadResult = + Result, DispatchError>; + +/// Result type of a `get_storage` call. +pub type GetStorageResult = Result>, ContractAccessError>; + +/// The possible errors that can happen querying the storage of a contract. +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)] +pub enum ContractAccessError { + /// The given address doesn't point to a contract. + DoesntExist, + /// Storage key cannot be decoded from the provided input data. + KeyDecodingFailed, + /// Storage is migrating. Try again later. + MigrationInProgress, +} + +/// Output of a contract call or instantiation which ran to completion. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ExecReturnValue { + /// Flags passed along by `seal_return`. Empty when `seal_return` was never called. + pub flags: ReturnFlags, + /// Buffer passed along by `seal_return`. Empty when `seal_return` was never called. + pub data: Vec, +} + +impl ExecReturnValue { + /// The contract did revert all storage changes. + pub fn did_revert(&self) -> bool { + self.flags.contains(ReturnFlags::REVERT) + } +} + +/// The result of a successful contract instantiation. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct InstantiateReturnValue { + /// The output of the called constructor. + pub result: ExecReturnValue, + /// The account id of the new contract. + pub account_id: AccountId, +} + +/// The result of successfully uploading a contract. +#[derive(Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)] +pub struct CodeUploadReturnValue { + /// The key under which the new code is stored. + pub code_hash: CodeHash, + /// The deposit that was reserved at the caller. Is zero when the code already existed. + pub deposit: Balance, +} + +/// Reference to an existing code hash or a new wasm module. +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum Code { + /// A wasm module as raw bytes. + Upload(Vec), + /// The code hash of an on-chain wasm blob. + Existing(Hash), +} + +/// The amount of balance that was either charged or refunded in order to pay for storage. +#[derive( + Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo, +)] +pub enum StorageDeposit { + /// The transaction reduced storage consumption. + /// + /// This means that the specified amount of balance was transferred from the involved + /// deposit accounts to the origin. + Refund(Balance), + /// The transaction increased storage consumption. + /// + /// This means that the specified amount of balance was transferred from the origin + /// to the involved deposit accounts. + Charge(Balance), +} + +impl Default for StorageDeposit { + fn default() -> Self { + Self::Charge(Zero::zero()) + } +} + +impl StorageDeposit { + /// Returns how much balance is charged or `0` in case of a refund. + pub fn charge_or_zero(&self) -> Balance { + match self { + Self::Charge(amount) => *amount, + Self::Refund(_) => Zero::zero(), + } + } + + pub fn is_zero(&self) -> bool { + match self { + Self::Charge(amount) => amount.is_zero(), + Self::Refund(amount) => amount.is_zero(), + } + } +} + +impl StorageDeposit +where + Balance: Saturating + Ord + Copy, +{ + /// This is essentially a saturating signed add. + pub fn saturating_add(&self, rhs: &Self) -> Self { + use StorageDeposit::*; + match (self, rhs) { + (Charge(lhs), Charge(rhs)) => Charge(lhs.saturating_add(*rhs)), + (Refund(lhs), Refund(rhs)) => Refund(lhs.saturating_add(*rhs)), + (Charge(lhs), Refund(rhs)) => + if lhs >= rhs { + Charge(lhs.saturating_sub(*rhs)) + } else { + Refund(rhs.saturating_sub(*lhs)) + }, + (Refund(lhs), Charge(rhs)) => + if lhs > rhs { + Refund(lhs.saturating_sub(*rhs)) + } else { + Charge(rhs.saturating_sub(*lhs)) + }, + } + } + + /// This is essentially a saturating signed sub. + pub fn saturating_sub(&self, rhs: &Self) -> Self { + use StorageDeposit::*; + match (self, rhs) { + (Charge(lhs), Refund(rhs)) => Charge(lhs.saturating_add(*rhs)), + (Refund(lhs), Charge(rhs)) => Refund(lhs.saturating_add(*rhs)), + (Charge(lhs), Charge(rhs)) => + if lhs >= rhs { + Charge(lhs.saturating_sub(*rhs)) + } else { + Refund(rhs.saturating_sub(*lhs)) + }, + (Refund(lhs), Refund(rhs)) => + if lhs > rhs { + Refund(lhs.saturating_sub(*rhs)) + } else { + Charge(rhs.saturating_sub(*lhs)) + }, + } + } + + /// If the amount of deposit (this type) is constrained by a `limit` this calculates how + /// much balance (if any) is still available from this limit. + /// + /// # Note + /// + /// In case of a refund the return value can be larger than `limit`. + pub fn available(&self, limit: &Balance) -> Balance { + use StorageDeposit::*; + match self { + Charge(amount) => limit.saturating_sub(*amount), + Refund(amount) => limit.saturating_add(*amount), + } + } +} + +/// Determines whether events should be collected during execution. +#[derive( + Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo, +)] +pub enum CollectEvents { + /// Collect events. + /// + /// # Note + /// + /// Events should only be collected when called off-chain, as this would otherwise + /// collect all the Events emitted in the block so far and put them into the PoV. + /// + /// **Never** use this mode for on-chain execution. + UnsafeCollect, + /// Skip event collection. + Skip, +} + +/// Determines whether debug messages will be collected. +#[derive( + Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo, +)] +pub enum DebugInfo { + /// Collect debug messages. + /// # Note + /// + /// This should only ever be set to `UnsafeDebug` when executing as an RPC because + /// it adds allocations and could be abused to drive the runtime into an OOM panic. + UnsafeDebug, + /// Skip collection of debug messages. + Skip, +} diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs new file mode 100644 index 000000000000..87274ce407fa --- /dev/null +++ b/substrate/frame/revive/src/storage.rs @@ -0,0 +1,482 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains routines for accessing and altering a contract related state. + +pub mod meter; + +use crate::{ + exec::{AccountIdOf, Key}, + limits, + storage::meter::Diff, + weights::WeightInfo, + BalanceOf, CodeHash, CodeInfo, Config, ContractInfoOf, DeletionQueue, DeletionQueueCounter, + Error, TrieId, SENTINEL, +}; +use alloc::vec::Vec; +use codec::{Decode, Encode, MaxEncodedLen}; +use core::marker::PhantomData; +use frame_support::{ + storage::child::{self, ChildInfo}, + weights::{Weight, WeightMeter}, + CloneNoBound, DefaultNoBound, +}; +use scale_info::TypeInfo; +use sp_core::{ConstU32, Get}; +use sp_io::KillStorageResult; +use sp_runtime::{ + traits::{Hash, Saturating, Zero}, + BoundedBTreeMap, DispatchError, DispatchResult, RuntimeDebug, +}; + +type DelegateDependencyMap = + BoundedBTreeMap, BalanceOf, ConstU32<{ limits::DELEGATE_DEPENDENCIES }>>; + +/// Information for managing an account and its sub trie abstraction. +/// This is the required info to cache for an account. +#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(T))] +pub struct ContractInfo { + /// Unique ID for the subtree encoded as a bytes vector. + pub trie_id: TrieId, + /// The code associated with a given account. + pub code_hash: CodeHash, + /// How many bytes of storage are accumulated in this contract's child trie. + storage_bytes: u32, + /// How many items of storage are accumulated in this contract's child trie. + storage_items: u32, + /// This records to how much deposit the accumulated `storage_bytes` amount to. + pub storage_byte_deposit: BalanceOf, + /// This records to how much deposit the accumulated `storage_items` amount to. + storage_item_deposit: BalanceOf, + /// This records how much deposit is put down in order to pay for the contract itself. + /// + /// We need to store this information separately so it is not used when calculating any refunds + /// since the base deposit can only ever be refunded on contract termination. + storage_base_deposit: BalanceOf, + /// Map of code hashes and deposit balances. + /// + /// Tracks the code hash and deposit held for locking delegate dependencies. Dependencies added + /// to the map can not be removed from the chain state and can be safely used for delegate + /// calls. + delegate_dependencies: DelegateDependencyMap, +} + +impl ContractInfo { + /// Constructs a new contract info **without** writing it to storage. + /// + /// This returns an `Err` if an contract with the supplied `account` already exists + /// in storage. + pub fn new( + account: &AccountIdOf, + nonce: T::Nonce, + code_hash: CodeHash, + ) -> Result { + if >::contains_key(account) { + return Err(Error::::DuplicateContract.into()) + } + + let trie_id = { + let buf = ("bcontract_trie_v1", account, nonce).using_encoded(T::Hashing::hash); + buf.as_ref() + .to_vec() + .try_into() + .expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed") + }; + + let contract = Self { + trie_id, + code_hash, + storage_bytes: 0, + storage_items: 0, + storage_byte_deposit: Zero::zero(), + storage_item_deposit: Zero::zero(), + storage_base_deposit: Zero::zero(), + delegate_dependencies: Default::default(), + }; + + Ok(contract) + } + + /// Returns the number of locked delegate dependencies. + pub fn delegate_dependencies_count(&self) -> usize { + self.delegate_dependencies.len() + } + + /// Associated child trie unique id is built from the hash part of the trie id. + pub fn child_trie_info(&self) -> ChildInfo { + ChildInfo::new_default(self.trie_id.as_ref()) + } + + /// The deposit paying for the accumulated storage generated within the contract's child trie. + pub fn extra_deposit(&self) -> BalanceOf { + self.storage_byte_deposit.saturating_add(self.storage_item_deposit) + } + + /// Same as [`Self::extra_deposit`] but including the base deposit. + pub fn total_deposit(&self) -> BalanceOf { + self.extra_deposit().saturating_add(self.storage_base_deposit) + } + + /// Returns the storage base deposit of the contract. + pub fn storage_base_deposit(&self) -> BalanceOf { + self.storage_base_deposit + } + + /// Reads a storage kv pair of a contract. + /// + /// The read is performed from the `trie_id` only. The `address` is not necessary. If the + /// contract doesn't store under the given `key` `None` is returned. + pub fn read(&self, key: &Key) -> Option> { + child::get_raw(&self.child_trie_info(), key.hash().as_slice()) + } + + /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + pub fn size(&self, key: &Key) -> Option { + child::len(&self.child_trie_info(), key.hash().as_slice()) + } + + /// Update a storage entry into a contract's kv storage. + /// + /// If the `new_value` is `None` then the kv pair is removed. If `take` is true + /// a [`WriteOutcome::Taken`] is returned instead of a [`WriteOutcome::Overwritten`]. + /// + /// This function also records how much storage was created or removed if a `storage_meter` + /// is supplied. It should only be absent for testing or benchmarking code. + pub fn write( + &self, + key: &Key, + new_value: Option>, + storage_meter: Option<&mut meter::NestedMeter>, + take: bool, + ) -> Result { + let hashed_key = key.hash(); + self.write_raw(&hashed_key, new_value, storage_meter, take) + } + + /// Update a storage entry into a contract's kv storage. + /// Function used in benchmarks, which can simulate prefix collision in keys. + #[cfg(feature = "runtime-benchmarks")] + pub fn bench_write_raw( + &self, + key: &[u8], + new_value: Option>, + take: bool, + ) -> Result { + self.write_raw(key, new_value, None, take) + } + + fn write_raw( + &self, + key: &[u8], + new_value: Option>, + storage_meter: Option<&mut meter::NestedMeter>, + take: bool, + ) -> Result { + let child_trie_info = &self.child_trie_info(); + let (old_len, old_value) = if take { + let val = child::get_raw(child_trie_info, key); + (val.as_ref().map(|v| v.len() as u32), val) + } else { + (child::len(child_trie_info, key), None) + }; + + if let Some(storage_meter) = storage_meter { + let mut diff = meter::Diff::default(); + match (old_len, new_value.as_ref().map(|v| v.len() as u32)) { + (Some(old_len), Some(new_len)) => + if new_len > old_len { + diff.bytes_added = new_len - old_len; + } else { + diff.bytes_removed = old_len - new_len; + }, + (None, Some(new_len)) => { + diff.bytes_added = new_len; + diff.items_added = 1; + }, + (Some(old_len), None) => { + diff.bytes_removed = old_len; + diff.items_removed = 1; + }, + (None, None) => (), + } + storage_meter.charge(&diff); + } + + match &new_value { + Some(new_value) => child::put_raw(child_trie_info, key, new_value), + None => child::kill(child_trie_info, key), + } + + Ok(match (old_len, old_value) { + (None, _) => WriteOutcome::New, + (Some(old_len), None) => WriteOutcome::Overwritten(old_len), + (Some(_), Some(old_value)) => WriteOutcome::Taken(old_value), + }) + } + + /// Sets and returns the contract base deposit. + /// + /// The base deposit is updated when the `code_hash` of the contract changes, as it depends on + /// the deposit paid to upload the contract's code. + pub fn update_base_deposit(&mut self, code_info: &CodeInfo) -> BalanceOf { + let info_deposit = + Diff { bytes_added: self.encoded_size() as u32, items_added: 1, ..Default::default() } + .update_contract::(None) + .charge_or_zero(); + + // Instantiating the contract prevents its code to be deleted, therefore the base deposit + // includes a fraction (`T::CodeHashLockupDepositPercent`) of the original storage deposit + // to prevent abuse. + let upload_deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit()); + + let deposit = info_deposit.saturating_add(upload_deposit); + self.storage_base_deposit = deposit; + deposit + } + + /// Adds a new delegate dependency to the contract. + /// The `amount` is the amount of funds that will be reserved for the dependency. + /// + /// Returns an error if the maximum number of delegate_dependencies is reached or if + /// the delegate dependency already exists. + pub fn lock_delegate_dependency( + &mut self, + code_hash: CodeHash, + amount: BalanceOf, + ) -> DispatchResult { + self.delegate_dependencies + .try_insert(code_hash, amount) + .map_err(|_| Error::::MaxDelegateDependenciesReached)? + .map_or(Ok(()), |_| Err(Error::::DelegateDependencyAlreadyExists)) + .map_err(Into::into) + } + + /// Removes the delegate dependency from the contract and returns the deposit held for this + /// dependency. + /// + /// Returns an error if the entry doesn't exist. + pub fn unlock_delegate_dependency( + &mut self, + code_hash: &CodeHash, + ) -> Result, DispatchError> { + self.delegate_dependencies + .remove(code_hash) + .ok_or(Error::::DelegateDependencyNotFound.into()) + } + + /// Returns the delegate_dependencies of the contract. + pub fn delegate_dependencies(&self) -> &DelegateDependencyMap { + &self.delegate_dependencies + } + + /// Push a contract's trie to the deletion queue for lazy removal. + /// + /// You must make sure that the contract is also removed when queuing the trie for deletion. + pub fn queue_trie_for_deletion(&self) { + DeletionQueueManager::::load().insert(self.trie_id.clone()); + } + + /// Calculates the weight that is necessary to remove one key from the trie and how many + /// of those keys can be deleted from the deletion queue given the supplied weight limit. + pub fn deletion_budget(meter: &WeightMeter) -> (Weight, u32) { + let base_weight = T::WeightInfo::on_process_deletion_queue_batch(); + let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1) - + T::WeightInfo::on_initialize_per_trie_key(0); + + // `weight_per_key` being zero makes no sense and would constitute a failure to + // benchmark properly. We opt for not removing any keys at all in this case. + let key_budget = meter + .limit() + .saturating_sub(base_weight) + .checked_div_per_component(&weight_per_key) + .unwrap_or(0) as u32; + + (weight_per_key, key_budget) + } + + /// Delete as many items from the deletion queue possible within the supplied weight limit. + pub fn process_deletion_queue_batch(meter: &mut WeightMeter) { + if meter.try_consume(T::WeightInfo::on_process_deletion_queue_batch()).is_err() { + return + }; + + let mut queue = >::load(); + if queue.is_empty() { + return; + } + + let (weight_per_key, budget) = Self::deletion_budget(&meter); + let mut remaining_key_budget = budget; + while remaining_key_budget > 0 { + let Some(entry) = queue.next() else { break }; + + #[allow(deprecated)] + let outcome = child::kill_storage( + &ChildInfo::new_default(&entry.trie_id), + Some(remaining_key_budget), + ); + + match outcome { + // This happens when our budget wasn't large enough to remove all keys. + KillStorageResult::SomeRemaining(keys_removed) => { + remaining_key_budget.saturating_reduce(keys_removed); + break + }, + KillStorageResult::AllRemoved(keys_removed) => { + entry.remove(); + // charge at least one key even if none were removed. + remaining_key_budget = remaining_key_budget.saturating_sub(keys_removed.max(1)); + }, + }; + } + + meter.consume(weight_per_key.saturating_mul(u64::from(budget - remaining_key_budget))) + } + + /// Returns the code hash of the contract specified by `account` ID. + pub fn load_code_hash(account: &AccountIdOf) -> Option> { + >::get(account).map(|i| i.code_hash) + } +} + +/// Information about what happened to the pre-existing value when calling [`ContractInfo::write`]. +#[cfg_attr(any(test, feature = "runtime-benchmarks"), derive(Debug, PartialEq))] +pub enum WriteOutcome { + /// No value existed at the specified key. + New, + /// A value of the returned length was overwritten. + Overwritten(u32), + /// The returned value was taken out of storage before being overwritten. + /// + /// This is only returned when specifically requested because it causes additional work + /// depending on the size of the pre-existing value. When not requested [`Self::Overwritten`] + /// is returned instead. + Taken(Vec), +} + +impl WriteOutcome { + /// Extracts the size of the overwritten value or `0` if there + /// was no value in storage. + pub fn old_len(&self) -> u32 { + match self { + Self::New => 0, + Self::Overwritten(len) => *len, + Self::Taken(value) => value.len() as u32, + } + } + + /// Extracts the size of the overwritten value or `SENTINEL` if there + /// was no value in storage. + /// + /// # Note + /// + /// We cannot use `0` as sentinel value because there could be a zero sized + /// storage entry which is different from a non existing one. + pub fn old_len_with_sentinel(&self) -> u32 { + match self { + Self::New => SENTINEL, + Self::Overwritten(len) => *len, + Self::Taken(value) => value.len() as u32, + } + } +} + +/// Manage the removal of contracts storage that are marked for deletion. +/// +/// When a contract is deleted by calling `seal_terminate` it becomes inaccessible +/// immediately, but the deletion of the storage items it has accumulated is performed +/// later by pulling the contract from the queue in the `on_idle` hook. +#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, DefaultNoBound, Clone)] +#[scale_info(skip_type_params(T))] +pub struct DeletionQueueManager { + /// Counter used as a key for inserting a new deleted contract in the queue. + /// The counter is incremented after each insertion. + insert_counter: u32, + /// The index used to read the next element to be deleted in the queue. + /// The counter is incremented after each deletion. + delete_counter: u32, + + _phantom: PhantomData, +} + +/// View on a contract that is marked for deletion. +struct DeletionQueueEntry<'a, T: Config> { + /// the trie id of the contract to delete. + trie_id: TrieId, + + /// A mutable reference on the queue so that the contract can be removed, and none can be added + /// or read in the meantime. + queue: &'a mut DeletionQueueManager, +} + +impl<'a, T: Config> DeletionQueueEntry<'a, T> { + /// Remove the contract from the deletion queue. + fn remove(self) { + >::remove(self.queue.delete_counter); + self.queue.delete_counter = self.queue.delete_counter.wrapping_add(1); + >::set(self.queue.clone()); + } +} + +impl DeletionQueueManager { + /// Load the `DeletionQueueCounter`, so we can perform read or write operations on the + /// DeletionQueue storage. + fn load() -> Self { + >::get() + } + + /// Returns `true` if the queue contains no elements. + fn is_empty(&self) -> bool { + self.insert_counter.wrapping_sub(self.delete_counter) == 0 + } + + /// Insert a contract in the deletion queue. + fn insert(&mut self, trie_id: TrieId) { + >::insert(self.insert_counter, trie_id); + self.insert_counter = self.insert_counter.wrapping_add(1); + >::set(self.clone()); + } + + /// Fetch the next contract to be deleted. + /// + /// Note: + /// we use the delete counter to get the next value to read from the queue and thus don't pay + /// the cost of an extra call to `sp_io::storage::next_key` to lookup the next entry in the map + fn next(&mut self) -> Option> { + if self.is_empty() { + return None + } + + let entry = >::get(self.delete_counter); + entry.map(|trie_id| DeletionQueueEntry { trie_id, queue: self }) + } +} + +#[cfg(test)] +#[cfg(feature = "riscv")] +impl DeletionQueueManager { + pub fn from_test_values(insert_counter: u32, delete_counter: u32) -> Self { + Self { insert_counter, delete_counter, _phantom: Default::default() } + } + pub fn as_test_tuple(&self) -> (u32, u32) { + (self.insert_counter, self.delete_counter) + } +} diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs new file mode 100644 index 000000000000..8735aa823421 --- /dev/null +++ b/substrate/frame/revive/src/storage/meter.rs @@ -0,0 +1,888 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains functions to meter the storage deposit. + +use crate::{ + storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, Event, HoldReason, + Inspect, Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET, +}; + +use alloc::vec::Vec; +use core::{fmt::Debug, marker::PhantomData}; +use frame_support::{ + traits::{ + fungible::{Mutate, MutateHold}, + tokens::{Fortitude, Fortitude::Polite, Precision, Preservation, Restriction}, + Get, + }, + DefaultNoBound, RuntimeDebugNoBound, +}; +use sp_runtime::{ + traits::{Saturating, Zero}, + DispatchError, FixedPointNumber, FixedU128, +}; + +/// Deposit that uses the native fungible's balance type. +pub type DepositOf = Deposit>; + +/// A production root storage meter that actually charges from its origin. +pub type Meter = RawMeter; + +/// A production nested storage meter that actually charges from its origin. +pub type NestedMeter = RawMeter; + +/// A production storage meter that actually charges from its origin. +/// +/// This can be used where we want to be generic over the state (Root vs. Nested). +pub type GenericMeter = RawMeter; + +/// A trait that allows to decouple the metering from the charging of balance. +/// +/// This mostly exists for testing so that the charging can be mocked. +pub trait Ext { + /// This checks whether `origin` is able to afford the storage deposit limit. + /// + /// It is necessary to do this check beforehand so that the charge won't fail later on. + /// + /// `origin`: The origin of the call stack from which is responsible for putting down a deposit. + /// `limit`: The limit with which the meter was constructed. + /// `min_leftover`: How much `free_balance` in addition to the existential deposit (ed) should + /// be left inside the `origin` account. + /// + /// Returns the limit that should be used by the meter. If origin can't afford the `limit` + /// it returns `Err`. + fn check_limit( + origin: &T::AccountId, + limit: BalanceOf, + min_leftover: BalanceOf, + ) -> Result, DispatchError>; + /// This is called to inform the implementer that some balance should be charged due to + /// some interaction of the `origin` with a `contract`. + /// + /// The balance transfer can either flow from `origin` to `contract` or the other way + /// around depending on whether `amount` constitutes a `Charge` or a `Refund`. + /// It should be used in combination with `check_limit` to check that no more balance than this + /// limit is ever charged. + fn charge( + origin: &T::AccountId, + contract: &T::AccountId, + amount: &DepositOf, + state: &ContractState, + ) -> Result<(), DispatchError>; +} + +/// This [`Ext`] is used for actual on-chain execution when balance needs to be charged. +/// +/// It uses [`frame_support::traits::fungible::Mutate`] in order to do accomplish the reserves. +pub enum ReservingExt {} + +/// Used to implement a type state pattern for the meter. +/// +/// It is sealed and cannot be implemented outside of this module. +pub trait State: private::Sealed {} + +/// State parameter that constitutes a meter that is in its root state. +#[derive(Default, Debug)] +pub struct Root; + +/// State parameter that constitutes a meter that is in its nested state. +/// Its value indicates whether the nested meter has its own limit. +#[derive(DefaultNoBound, RuntimeDebugNoBound)] +pub enum Nested { + #[default] + DerivedLimit, + OwnLimit, +} + +impl State for Root {} +impl State for Nested {} + +/// A type that allows the metering of consumed or freed storage of a single contract call stack. +#[derive(DefaultNoBound, RuntimeDebugNoBound)] +pub struct RawMeter { + /// The limit of how much balance this meter is allowed to consume. + limit: BalanceOf, + /// The amount of balance that was used in this meter and all of its already absorbed children. + total_deposit: DepositOf, + /// The amount of storage changes that were recorded in this meter alone. + own_contribution: Contribution, + /// List of charges that should be applied at the end of a contract stack execution. + /// + /// We only have one charge per contract hence the size of this vector is + /// limited by the maximum call depth. + charges: Vec>, + /// We store the nested state to determine if it has a special limit for sub-call. + nested: S, + /// Type parameter only used in impls. + _phantom: PhantomData, +} + +/// This type is used to describe a storage change when charging from the meter. +#[derive(Default, RuntimeDebugNoBound)] +pub struct Diff { + /// How many bytes were added to storage. + pub bytes_added: u32, + /// How many bytes were removed from storage. + pub bytes_removed: u32, + /// How many storage items were added to storage. + pub items_added: u32, + /// How many storage items were removed from storage. + pub items_removed: u32, +} + +impl Diff { + /// Calculate how much of a charge or refund results from applying the diff and store it + /// in the passed `info` if any. + /// + /// # Note + /// + /// In case `None` is passed for `info` only charges are calculated. This is because refunds + /// are calculated pro rata of the existing storage within a contract and hence need extract + /// this information from the passed `info`. + pub fn update_contract(&self, info: Option<&mut ContractInfo>) -> DepositOf { + let per_byte = T::DepositPerByte::get(); + let per_item = T::DepositPerItem::get(); + let bytes_added = self.bytes_added.saturating_sub(self.bytes_removed); + let items_added = self.items_added.saturating_sub(self.items_removed); + let mut bytes_deposit = Deposit::Charge(per_byte.saturating_mul((bytes_added).into())); + let mut items_deposit = Deposit::Charge(per_item.saturating_mul((items_added).into())); + + // Without any contract info we can only calculate diffs which add storage + let info = if let Some(info) = info { + info + } else { + debug_assert_eq!(self.bytes_removed, 0); + debug_assert_eq!(self.items_removed, 0); + return bytes_deposit.saturating_add(&items_deposit) + }; + + // Refunds are calculated pro rata based on the accumulated storage within the contract + let bytes_removed = self.bytes_removed.saturating_sub(self.bytes_added); + let items_removed = self.items_removed.saturating_sub(self.items_added); + let ratio = FixedU128::checked_from_rational(bytes_removed, info.storage_bytes) + .unwrap_or_default() + .min(FixedU128::from_u32(1)); + bytes_deposit = bytes_deposit + .saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_byte_deposit))); + let ratio = FixedU128::checked_from_rational(items_removed, info.storage_items) + .unwrap_or_default() + .min(FixedU128::from_u32(1)); + items_deposit = items_deposit + .saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_item_deposit))); + + // We need to update the contract info structure with the new deposits + info.storage_bytes = + info.storage_bytes.saturating_add(bytes_added).saturating_sub(bytes_removed); + info.storage_items = + info.storage_items.saturating_add(items_added).saturating_sub(items_removed); + match &bytes_deposit { + Deposit::Charge(amount) => + info.storage_byte_deposit = info.storage_byte_deposit.saturating_add(*amount), + Deposit::Refund(amount) => + info.storage_byte_deposit = info.storage_byte_deposit.saturating_sub(*amount), + } + match &items_deposit { + Deposit::Charge(amount) => + info.storage_item_deposit = info.storage_item_deposit.saturating_add(*amount), + Deposit::Refund(amount) => + info.storage_item_deposit = info.storage_item_deposit.saturating_sub(*amount), + } + + bytes_deposit.saturating_add(&items_deposit) + } +} + +impl Diff { + fn saturating_add(&self, rhs: &Self) -> Self { + Self { + bytes_added: self.bytes_added.saturating_add(rhs.bytes_added), + bytes_removed: self.bytes_removed.saturating_add(rhs.bytes_removed), + items_added: self.items_added.saturating_add(rhs.items_added), + items_removed: self.items_removed.saturating_add(rhs.items_removed), + } + } +} + +/// The state of a contract. +/// +/// In case of termination the beneficiary is indicated. +#[derive(RuntimeDebugNoBound, Clone, PartialEq, Eq)] +pub enum ContractState { + Alive, + Terminated { beneficiary: AccountIdOf }, +} + +/// Records information to charge or refund a plain account. +/// +/// All the charges are deferred to the end of a whole call stack. Reason is that by doing +/// this we can do all the refunds before doing any charge. This way a plain account can use +/// more deposit than it has balance as along as it is covered by a refund. This +/// essentially makes the order of storage changes irrelevant with regard to the deposit system. +/// The only exception is when a special (tougher) deposit limit is specified for a cross-contract +/// call. In that case the limit is enforced once the call is returned, rolling it back if +/// exhausted. +#[derive(RuntimeDebugNoBound, Clone)] +struct Charge { + contract: T::AccountId, + amount: DepositOf, + state: ContractState, +} + +/// Records the storage changes of a storage meter. +#[derive(RuntimeDebugNoBound)] +enum Contribution { + /// The contract the meter belongs to is alive and accumulates changes using a [`Diff`]. + Alive(Diff), + /// The meter was checked against its limit using [`RawMeter::enforce_limit`] at the end of + /// its execution. In this process the [`Diff`] was converted into a [`Deposit`]. + Checked(DepositOf), + /// The contract was terminated. In this process the [`Diff`] was converted into a [`Deposit`] + /// in order to calculate the refund. Upon termination the `reducible_balance` in the + /// contract's account is transferred to the [`beneficiary`]. + Terminated { deposit: DepositOf, beneficiary: AccountIdOf }, +} + +impl Contribution { + /// See [`Diff::update_contract`]. + fn update_contract(&self, info: Option<&mut ContractInfo>) -> DepositOf { + match self { + Self::Alive(diff) => diff.update_contract::(info), + Self::Terminated { deposit, beneficiary: _ } | Self::Checked(deposit) => + deposit.clone(), + } + } +} + +impl Default for Contribution { + fn default() -> Self { + Self::Alive(Default::default()) + } +} + +/// Functions that apply to all states. +impl RawMeter +where + T: Config, + E: Ext, + S: State + Default + Debug, +{ + /// Create a new child that has its `limit`. + /// Passing `0` as the limit is interpreted as to take whatever is remaining from its parent. + /// + /// This is called whenever a new subcall is initiated in order to track the storage + /// usage for this sub call separately. This is necessary because we want to exchange balance + /// with the current contract we are interacting with. + pub fn nested(&self, limit: BalanceOf) -> RawMeter { + debug_assert!(matches!(self.contract_state(), ContractState::Alive)); + // If a special limit is specified higher than it is available, + // we want to enforce the lesser limit to the nested meter, to fail in the sub-call. + let limit = self.available().min(limit); + if limit.is_zero() { + RawMeter { limit: self.available(), ..Default::default() } + } else { + RawMeter { limit, nested: Nested::OwnLimit, ..Default::default() } + } + } + + /// Absorb a child that was spawned to handle a sub call. + /// + /// This should be called whenever a sub call comes to its end and it is **not** reverted. + /// This does the actual balance transfer from/to `origin` and `contract` based on the + /// overall storage consumption of the call. It also updates the supplied contract info. + /// + /// In case a contract reverted the child meter should just be dropped in order to revert + /// any changes it recorded. + /// + /// # Parameters + /// + /// - `absorbed`: The child storage meter that should be absorbed. + /// - `origin`: The origin that spawned the original root meter. + /// - `contract`: The contract's account that this sub call belongs to. + /// - `info`: The info of the contract in question. `None` if the contract was terminated. + pub fn absorb( + &mut self, + absorbed: RawMeter, + contract: &T::AccountId, + info: Option<&mut ContractInfo>, + ) { + let own_deposit = absorbed.own_contribution.update_contract(info); + self.total_deposit = self + .total_deposit + .saturating_add(&absorbed.total_deposit) + .saturating_add(&own_deposit); + self.charges.extend_from_slice(&absorbed.charges); + if !own_deposit.is_zero() { + self.charges.push(Charge { + contract: contract.clone(), + amount: own_deposit, + state: absorbed.contract_state(), + }); + } + } + + /// The amount of balance that is still available from the original `limit`. + fn available(&self) -> BalanceOf { + self.total_deposit.available(&self.limit) + } + + /// Returns the state of the currently executed contract. + fn contract_state(&self) -> ContractState { + match &self.own_contribution { + Contribution::Terminated { deposit: _, beneficiary } => + ContractState::Terminated { beneficiary: beneficiary.clone() }, + _ => ContractState::Alive, + } + } +} + +/// Functions that only apply to the root state. +impl RawMeter +where + T: Config, + E: Ext, +{ + /// Create new storage meter for the specified `origin` and `limit`. + /// + /// This tries to [`Ext::check_limit`] on `origin` and fails if this is not possible. + pub fn new( + origin: &Origin, + limit: BalanceOf, + min_leftover: BalanceOf, + ) -> Result { + // Check the limit only if the origin is not root. + return match origin { + Origin::Root => Ok(Self { limit, ..Default::default() }), + Origin::Signed(o) => { + let limit = E::check_limit(o, limit, min_leftover)?; + Ok(Self { limit, ..Default::default() }) + }, + } + } + + /// The total amount of deposit that should change hands as result of the execution + /// that this meter was passed into. This will also perform all the charges accumulated + /// in the whole contract stack. + /// + /// This drops the root meter in order to make sure it is only called when the whole + /// execution did finish. + pub fn try_into_deposit(self, origin: &Origin) -> Result, DispatchError> { + // Only refund or charge deposit if the origin is not root. + let origin = match origin { + Origin::Root => return Ok(Deposit::Charge(Zero::zero())), + Origin::Signed(o) => o, + }; + for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) { + E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; + } + for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) { + E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; + } + Ok(self.total_deposit) + } +} + +/// Functions that only apply to the nested state. +impl RawMeter +where + T: Config, + E: Ext, +{ + /// Charges `diff` from the meter. + pub fn charge(&mut self, diff: &Diff) { + match &mut self.own_contribution { + Contribution::Alive(own) => *own = own.saturating_add(diff), + _ => panic!("Charge is never called after termination; qed"), + }; + } + + /// Adds a deposit charge. + /// + /// Use this method instead of [`Self::charge`] when the charge is not the result of a storage + /// change. This is the case when a `delegate_dependency` is added or removed, or when the + /// `code_hash` is updated. [`Self::charge`] cannot be used here because we keep track of the + /// deposit charge separately from the storage charge. + pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf) { + self.total_deposit = self.total_deposit.saturating_add(&amount); + self.charges.push(Charge { contract, amount, state: ContractState::Alive }); + } + + /// Charges from `origin` a storage deposit for contract instantiation. + /// + /// This immediately transfers the balance in order to create the account. + pub fn charge_instantiate( + &mut self, + origin: &T::AccountId, + contract: &T::AccountId, + contract_info: &mut ContractInfo, + code_info: &CodeInfo, + ) -> Result<(), DispatchError> { + debug_assert!(matches!(self.contract_state(), ContractState::Alive)); + + // We need to make sure that the contract's account exists. + let ed = Pallet::::min_balance(); + self.total_deposit = Deposit::Charge(ed); + T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?; + + // A consumer is added at account creation and removed it on termination, otherwise the + // runtime could remove the account. As long as a contract exists its account must exist. + // With the consumer, a correct runtime cannot remove the account. + System::::inc_consumers(contract)?; + + let deposit = contract_info.update_base_deposit(&code_info); + let deposit = Deposit::Charge(deposit); + + self.charge_deposit(contract.clone(), deposit); + Ok(()) + } + + /// Call to tell the meter that the currently executing contract was terminated. + /// + /// This will manipulate the meter so that all storage deposit accumulated in + /// `contract_info` will be refunded to the `origin` of the meter. And the free + /// (`reducible_balance`) will be sent to the `beneficiary`. + pub fn terminate(&mut self, info: &ContractInfo, beneficiary: T::AccountId) { + debug_assert!(matches!(self.contract_state(), ContractState::Alive)); + self.own_contribution = Contribution::Terminated { + deposit: Deposit::Refund(info.total_deposit()), + beneficiary, + }; + } + + /// [`Self::charge`] does not enforce the storage limit since we want to do this check as late + /// as possible to allow later refunds to offset earlier charges. + /// + /// # Note + /// + /// We normally need to call this **once** for every call stack and not for every cross contract + /// call. However, if a dedicated limit is specified for a sub-call, this needs to be called + /// once the sub-call has returned. For this, the [`Self::enforce_subcall_limit`] wrapper is + /// used. + pub fn enforce_limit( + &mut self, + info: Option<&mut ContractInfo>, + ) -> Result<(), DispatchError> { + let deposit = self.own_contribution.update_contract(info); + let total_deposit = self.total_deposit.saturating_add(&deposit); + // We don't want to override a `Terminated` with a `Checked`. + if matches!(self.contract_state(), ContractState::Alive) { + self.own_contribution = Contribution::Checked(deposit); + } + if let Deposit::Charge(amount) = total_deposit { + if amount > self.limit { + return Err(>::StorageDepositLimitExhausted.into()) + } + } + Ok(()) + } + + /// This is a wrapper around [`Self::enforce_limit`] to use on the exit from a sub-call to + /// enforce its special limit if needed. + pub fn enforce_subcall_limit( + &mut self, + info: Option<&mut ContractInfo>, + ) -> Result<(), DispatchError> { + match self.nested { + Nested::OwnLimit => self.enforce_limit(info), + Nested::DerivedLimit => Ok(()), + } + } +} + +impl Ext for ReservingExt { + fn check_limit( + origin: &T::AccountId, + limit: BalanceOf, + min_leftover: BalanceOf, + ) -> Result, DispatchError> { + let limit = T::Currency::reducible_balance(origin, Preservation::Preserve, Polite) + .saturating_sub(min_leftover) + .min(limit); + Ok(limit) + } + + fn charge( + origin: &T::AccountId, + contract: &T::AccountId, + amount: &DepositOf, + state: &ContractState, + ) -> Result<(), DispatchError> { + match amount { + Deposit::Charge(amount) | Deposit::Refund(amount) if amount.is_zero() => return Ok(()), + Deposit::Charge(amount) => { + // This could fail if the `origin` does not have enough liquidity. Ideally, though, + // this should have been checked before with `check_limit`. + T::Currency::transfer_and_hold( + &HoldReason::StorageDepositReserve.into(), + origin, + contract, + *amount, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite, + )?; + + Pallet::::deposit_event(Event::StorageDepositTransferredAndHeld { + from: origin.clone(), + to: contract.clone(), + amount: *amount, + }); + }, + Deposit::Refund(amount) => { + let transferred = T::Currency::transfer_on_hold( + &HoldReason::StorageDepositReserve.into(), + contract, + origin, + *amount, + Precision::BestEffort, + Restriction::Free, + Fortitude::Polite, + )?; + + Pallet::::deposit_event(Event::StorageDepositTransferredAndReleased { + from: contract.clone(), + to: origin.clone(), + amount: transferred, + }); + + if transferred < *amount { + // This should never happen, if it does it means that there is a bug in the + // runtime logic. In the rare case this happens we try to refund as much as we + // can, thus the `Precision::BestEffort`. + log::error!( + target: LOG_TARGET, + "Failed to repatriate full storage deposit {:?} from contract {:?} to origin {:?}. Transferred {:?}.", + amount, contract, origin, transferred, + ); + } + }, + } + if let ContractState::::Terminated { beneficiary } = state { + System::::dec_consumers(&contract); + // Whatever is left in the contract is sent to the termination beneficiary. + T::Currency::transfer( + &contract, + &beneficiary, + T::Currency::reducible_balance(&contract, Preservation::Expendable, Polite), + Preservation::Expendable, + )?; + } + Ok(()) + } +} + +mod private { + pub trait Sealed {} + impl Sealed for super::Root {} + impl Sealed for super::Nested {} +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{exec::AccountIdOf, test_utils::*, tests::Test}; + use frame_support::parameter_types; + use pretty_assertions::assert_eq; + + type TestMeter = RawMeter; + + parameter_types! { + static TestExtTestValue: TestExt = Default::default(); + } + + #[derive(Debug, PartialEq, Eq, Clone)] + struct LimitCheck { + origin: AccountIdOf, + limit: BalanceOf, + min_leftover: BalanceOf, + } + + #[derive(Debug, PartialEq, Eq, Clone)] + struct Charge { + origin: AccountIdOf, + contract: AccountIdOf, + amount: DepositOf, + state: ContractState, + } + + #[derive(Default, Debug, PartialEq, Eq, Clone)] + pub struct TestExt { + limit_checks: Vec, + charges: Vec, + } + + impl TestExt { + fn clear(&mut self) { + self.limit_checks.clear(); + self.charges.clear(); + } + } + + impl Ext for TestExt { + fn check_limit( + origin: &AccountIdOf, + limit: BalanceOf, + min_leftover: BalanceOf, + ) -> Result, DispatchError> { + TestExtTestValue::mutate(|ext| { + ext.limit_checks + .push(LimitCheck { origin: origin.clone(), limit, min_leftover }) + }); + Ok(limit) + } + + fn charge( + origin: &AccountIdOf, + contract: &AccountIdOf, + amount: &DepositOf, + state: &ContractState, + ) -> Result<(), DispatchError> { + TestExtTestValue::mutate(|ext| { + ext.charges.push(Charge { + origin: origin.clone(), + contract: contract.clone(), + amount: amount.clone(), + state: state.clone(), + }) + }); + Ok(()) + } + } + + fn clear_ext() { + TestExtTestValue::mutate(|ext| ext.clear()) + } + + struct ChargingTestCase { + origin: Origin, + deposit: DepositOf, + expected: TestExt, + } + + #[derive(Default)] + struct StorageInfo { + bytes: u32, + items: u32, + bytes_deposit: BalanceOf, + items_deposit: BalanceOf, + } + + fn new_info(info: StorageInfo) -> ContractInfo { + ContractInfo:: { + trie_id: Default::default(), + code_hash: Default::default(), + storage_bytes: info.bytes, + storage_items: info.items, + storage_byte_deposit: info.bytes_deposit, + storage_item_deposit: info.items_deposit, + storage_base_deposit: Default::default(), + delegate_dependencies: Default::default(), + } + } + + #[test] + fn new_reserves_balance_works() { + clear_ext(); + + TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + + assert_eq!( + TestExtTestValue::get(), + TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }], + ..Default::default() + } + ) + } + + #[test] + fn empty_charge_works() { + clear_ext(); + + let mut meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + + // an empty charge does not create a `Charge` entry + let mut nested0 = meter.nested(BalanceOf::::zero()); + nested0.charge(&Default::default()); + meter.absorb(nested0, &BOB, None); + + assert_eq!( + TestExtTestValue::get(), + TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }], + ..Default::default() + } + ) + } + + #[test] + fn charging_works() { + let test_cases = vec![ + ChargingTestCase { + origin: Origin::::from_account_id(ALICE), + deposit: Deposit::Refund(28), + expected: TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 100, min_leftover: 0 }], + charges: vec![ + Charge { + origin: ALICE, + contract: CHARLIE, + amount: Deposit::Refund(10), + state: ContractState::Alive, + }, + Charge { + origin: ALICE, + contract: CHARLIE, + amount: Deposit::Refund(20), + state: ContractState::Alive, + }, + Charge { + origin: ALICE, + contract: BOB, + amount: Deposit::Charge(2), + state: ContractState::Alive, + }, + ], + }, + }, + ChargingTestCase { + origin: Origin::::Root, + deposit: Deposit::Charge(0), + expected: TestExt { limit_checks: vec![], charges: vec![] }, + }, + ]; + + for test_case in test_cases { + clear_ext(); + + let mut meter = TestMeter::new(&test_case.origin, 100, 0).unwrap(); + assert_eq!(meter.available(), 100); + + let mut nested0_info = new_info(StorageInfo { + bytes: 100, + items: 5, + bytes_deposit: 100, + items_deposit: 10, + }); + let mut nested0 = meter.nested(BalanceOf::::zero()); + nested0.charge(&Diff { + bytes_added: 108, + bytes_removed: 5, + items_added: 1, + items_removed: 2, + }); + nested0.charge(&Diff { bytes_removed: 99, ..Default::default() }); + + let mut nested1_info = new_info(StorageInfo { + bytes: 100, + items: 10, + bytes_deposit: 100, + items_deposit: 20, + }); + let mut nested1 = nested0.nested(BalanceOf::::zero()); + nested1.charge(&Diff { items_removed: 5, ..Default::default() }); + nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info)); + + let mut nested2_info = new_info(StorageInfo { + bytes: 100, + items: 7, + bytes_deposit: 100, + items_deposit: 20, + }); + let mut nested2 = nested0.nested(BalanceOf::::zero()); + nested2.charge(&Diff { items_removed: 7, ..Default::default() }); + nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info)); + + nested0.enforce_limit(Some(&mut nested0_info)).unwrap(); + meter.absorb(nested0, &BOB, Some(&mut nested0_info)); + + assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit); + + assert_eq!(nested0_info.extra_deposit(), 112); + assert_eq!(nested1_info.extra_deposit(), 110); + assert_eq!(nested2_info.extra_deposit(), 100); + + assert_eq!(TestExtTestValue::get(), test_case.expected) + } + } + + #[test] + fn termination_works() { + let test_cases = vec![ + ChargingTestCase { + origin: Origin::::from_account_id(ALICE), + deposit: Deposit::Refund(108), + expected: TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }], + charges: vec![ + Charge { + origin: ALICE, + contract: CHARLIE, + amount: Deposit::Refund(120), + state: ContractState::Terminated { beneficiary: CHARLIE }, + }, + Charge { + origin: ALICE, + contract: BOB, + amount: Deposit::Charge(12), + state: ContractState::Alive, + }, + ], + }, + }, + ChargingTestCase { + origin: Origin::::Root, + deposit: Deposit::Charge(0), + expected: TestExt { limit_checks: vec![], charges: vec![] }, + }, + ]; + + for test_case in test_cases { + clear_ext(); + + let mut meter = TestMeter::new(&test_case.origin, 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + + let mut nested0 = meter.nested(BalanceOf::::zero()); + nested0.charge(&Diff { + bytes_added: 5, + bytes_removed: 1, + items_added: 3, + items_removed: 1, + }); + nested0.charge(&Diff { items_added: 2, ..Default::default() }); + + let mut nested1_info = new_info(StorageInfo { + bytes: 100, + items: 10, + bytes_deposit: 100, + items_deposit: 20, + }); + let mut nested1 = nested0.nested(BalanceOf::::zero()); + nested1.charge(&Diff { items_removed: 5, ..Default::default() }); + nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); + nested1.terminate(&nested1_info, CHARLIE); + nested0.enforce_limit(Some(&mut nested1_info)).unwrap(); + nested0.absorb(nested1, &CHARLIE, None); + + meter.absorb(nested0, &BOB, None); + assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit); + assert_eq!(TestExtTestValue::get(), test_case.expected) + } + } +} diff --git a/substrate/frame/revive/src/test_utils.rs b/substrate/frame/revive/src/test_utils.rs new file mode 100644 index 000000000000..2bfe754f86cd --- /dev/null +++ b/substrate/frame/revive/src/test_utils.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Shared utilities for testing contracts. +//! This is not part of the tests module because it is made public for other crates to use. + +#![cfg(feature = "std")] + +pub mod builder; + +use crate::{BalanceOf, Config}; +use frame_support::weights::Weight; +pub use sp_runtime::AccountId32; + +pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); +pub const BOB: AccountId32 = AccountId32::new([2u8; 32]); +pub const CHARLIE: AccountId32 = AccountId32::new([3u8; 32]); +pub const DJANGO: AccountId32 = AccountId32::new([4u8; 32]); + +pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); + +pub fn deposit_limit() -> BalanceOf { + 10_000_000u32.into() +} diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs new file mode 100644 index 000000000000..bf8cbcd5a01f --- /dev/null +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -0,0 +1,218 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{deposit_limit, GAS_LIMIT}; +use crate::{ + AccountIdLookupOf, AccountIdOf, BalanceOf, Code, CodeHash, CollectEvents, Config, + ContractExecResult, ContractInstantiateResult, DebugInfo, EventRecordOf, ExecReturnValue, + InstantiateReturnValue, OriginFor, Pallet, Weight, +}; +use codec::{Encode, HasCompact}; +use core::fmt::Debug; +use frame_support::pallet_prelude::DispatchResultWithPostInfo; +use paste::paste; +use scale_info::TypeInfo; + +/// Helper macro to generate a builder for contract API calls. +macro_rules! builder { + // Entry point to generate a builder for the given method. + ( + $method:ident($($field:ident: $type:ty,)*) -> $result:ty; + $($extra:item)* + ) => { + paste!{ + builder!([< $method:camel Builder >], $method($($field: $type,)* ) -> $result; $($extra)*); + } + }; + // Generate the builder struct and its methods. + ( + $name:ident, + $method:ident($($field:ident: $type:ty,)*) -> $result:ty; + $($extra:item)* + ) => { + #[doc = concat!("A builder to construct a ", stringify!($method), " call")] + pub struct $name { + $($field: $type,)* + } + + #[allow(dead_code)] + impl $name + where + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + { + $( + #[doc = concat!("Set the ", stringify!($field))] + pub fn $field(mut self, value: $type) -> Self { + self.$field = value; + self + } + )* + + #[doc = concat!("Build the ", stringify!($method), " call")] + pub fn build(self) -> $result { + Pallet::::$method( + $(self.$field,)* + ) + } + + $($extra)* + } + } +} + +builder!( + instantiate_with_code( + origin: OriginFor, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: BalanceOf, + code: Vec, + data: Vec, + salt: Vec, + ) -> DispatchResultWithPostInfo; + + /// Create an [`InstantiateWithCodeBuilder`] with default values. + pub fn instantiate_with_code(origin: OriginFor, code: Vec) -> Self { + Self { + origin, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: deposit_limit::(), + code, + data: vec![], + salt: vec![], + } + } +); + +builder!( + instantiate( + origin: OriginFor, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: BalanceOf, + code_hash: CodeHash, + data: Vec, + salt: Vec, + ) -> DispatchResultWithPostInfo; + + /// Create an [`InstantiateBuilder`] with default values. + pub fn instantiate(origin: OriginFor, code_hash: CodeHash) -> Self { + Self { + origin, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: deposit_limit::(), + code_hash, + data: vec![], + salt: vec![], + } + } +); + +builder!( + bare_instantiate( + origin: OriginFor, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: BalanceOf, + code: Code>, + data: Vec, + salt: Vec, + debug: DebugInfo, + collect_events: CollectEvents, + ) -> ContractInstantiateResult, BalanceOf, EventRecordOf>; + + /// Build the instantiate call and unwrap the result. + pub fn build_and_unwrap_result(self) -> InstantiateReturnValue> { + self.build().result.unwrap() + } + + /// Build the instantiate call and unwrap the account id. + pub fn build_and_unwrap_account_id(self) -> AccountIdOf { + self.build().result.unwrap().account_id + } + + pub fn bare_instantiate(origin: OriginFor, code: Code>) -> Self { + Self { + origin, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: deposit_limit::(), + code, + data: vec![], + salt: vec![], + debug: DebugInfo::UnsafeDebug, + collect_events: CollectEvents::Skip, + } + } +); + +builder!( + call( + origin: OriginFor, + dest: AccountIdLookupOf, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: BalanceOf, + data: Vec, + ) -> DispatchResultWithPostInfo; + + /// Create a [`CallBuilder`] with default values. + pub fn call(origin: OriginFor, dest: AccountIdLookupOf) -> Self { + CallBuilder { + origin, + dest, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: deposit_limit::(), + data: vec![], + } + } +); + +builder!( + bare_call( + origin: OriginFor, + dest: AccountIdOf, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: BalanceOf, + data: Vec, + debug: DebugInfo, + collect_events: CollectEvents, + ) -> ContractExecResult, EventRecordOf>; + + /// Build the call and unwrap the result. + pub fn build_and_unwrap_result(self) -> ExecReturnValue { + self.build().result.unwrap() + } + + /// Create a [`BareCallBuilder`] with default values. + pub fn bare_call(origin: OriginFor, dest: AccountIdOf) -> Self { + Self { + origin, + dest, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: deposit_limit::(), + data: vec![], + debug: DebugInfo::UnsafeDebug, + collect_events: CollectEvents::Skip, + } + } +); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs new file mode 100644 index 000000000000..52ee7b310542 --- /dev/null +++ b/substrate/frame/revive/src/tests.rs @@ -0,0 +1,4166 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "riscv"), allow(dead_code, unused_imports, unused_macros))] + +mod pallet_dummy; +mod test_debug; + +use self::{ + test_debug::TestDebug, + test_utils::{ensure_stored, expected_deposit}, +}; +use crate::{ + self as pallet_revive, + chain_extension::{ + ChainExtension, Environment, Ext, RegisteredChainExtension, Result as ExtensionResult, + RetVal, ReturnFlags, + }, + exec::Key, + limits, + migration::codegen::LATEST_MIGRATION_VERSION, + primitives::CodeUploadReturnValue, + storage::DeletionQueueManager, + test_utils::*, + tests::test_utils::{get_contract, get_contract_checked}, + wasm::Memory, + weights::WeightInfo, + BalanceOf, Code, CodeHash, CodeInfoOf, CollectEvents, Config, ContractInfo, ContractInfoOf, + DebugInfo, DefaultAddressGenerator, DeletionQueueCounter, Error, HoldReason, + MigrationInProgress, Origin, Pallet, PristineCode, +}; +use assert_matches::assert_matches; +use codec::{Decode, Encode}; +use frame_support::{ + assert_err, assert_err_ignore_postinfo, assert_err_with_weight, assert_noop, assert_ok, + derive_impl, + dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo}, + pallet_prelude::EnsureOrigin, + parameter_types, + storage::child, + traits::{ + fungible::{BalancedHold, Inspect, Mutate, MutateHold}, + tokens::Preservation, + ConstU32, ConstU64, Contains, OnIdle, OnInitialize, StorageVersion, + }, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight, WeightMeter}, +}; +use frame_system::{EventRecord, Phase}; +use pallet_revive_fixtures::{bench::dummy_unique, compile_module}; +use pallet_revive_uapi::ReturnErrorCode as RuntimeReturnCode; +use sp_core::ByteArray; +use sp_io::hashing::blake2_256; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; +use sp_runtime::{ + testing::H256, + traits::{BlakeTwo256, Convert, Hash, IdentityLookup}, + AccountId32, BuildStorage, DispatchError, Perbill, TokenError, +}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Timestamp: pallet_timestamp, + Utility: pallet_utility, + Contracts: pallet_revive, + Proxy: pallet_proxy, + Dummy: pallet_dummy + } +); + +macro_rules! assert_return_code { + ( $x:expr , $y:expr $(,)? ) => {{ + assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); + }}; +} + +macro_rules! assert_refcount { + ( $code_hash:expr , $should:expr $(,)? ) => {{ + let is = crate::CodeInfoOf::::get($code_hash).map(|m| m.refcount()).unwrap(); + assert_eq!(is, $should); + }}; +} + +pub mod test_utils { + use super::{Contracts, DepositPerByte, DepositPerItem, Test}; + use crate::{ + exec::AccountIdOf, BalanceOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, + ContractInfoOf, PristineCode, + }; + use codec::{Encode, MaxEncodedLen}; + use frame_support::traits::fungible::{InspectHold, Mutate}; + + pub fn place_contract(address: &AccountIdOf, code_hash: CodeHash) { + set_balance(address, Contracts::min_balance() * 10); + >::insert(code_hash, CodeInfo::new(address.clone())); + let contract = >::new(&address, 0, code_hash).unwrap(); + >::insert(address, contract); + } + pub fn set_balance(who: &AccountIdOf, amount: u64) { + let _ = ::Currency::set_balance(who, amount); + } + pub fn get_balance(who: &AccountIdOf) -> u64 { + ::Currency::free_balance(who) + } + pub fn get_balance_on_hold( + reason: &::RuntimeHoldReason, + who: &AccountIdOf, + ) -> u64 { + ::Currency::balance_on_hold(reason.into(), who) + } + pub fn get_contract(addr: &AccountIdOf) -> ContractInfo { + get_contract_checked(addr).unwrap() + } + pub fn get_contract_checked(addr: &AccountIdOf) -> Option> { + ContractInfoOf::::get(addr) + } + pub fn get_code_deposit(code_hash: &CodeHash) -> BalanceOf { + crate::CodeInfoOf::::get(code_hash).unwrap().deposit() + } + pub fn contract_info_storage_deposit( + addr: &::AccountId, + ) -> BalanceOf { + let contract_info = self::get_contract(&addr); + let info_size = contract_info.encoded_size() as u64; + DepositPerByte::get() + .saturating_mul(info_size) + .saturating_add(DepositPerItem::get()) + } + pub fn expected_deposit(code_len: usize) -> u64 { + // For code_info, the deposit for max_encoded_len is taken. + let code_info_len = CodeInfo::::max_encoded_len() as u64; + // Calculate deposit to be reserved. + // We add 2 storage items: one for code, other for code_info + DepositPerByte::get().saturating_mul(code_len as u64 + code_info_len) + + DepositPerItem::get().saturating_mul(2) + } + pub fn ensure_stored(code_hash: CodeHash) -> usize { + // Assert that code_info is stored + assert!(CodeInfoOf::::contains_key(&code_hash)); + // Assert that contract code is stored, and get its size. + PristineCode::::try_get(&code_hash).unwrap().len() + } +} + +mod builder { + use super::Test; + use crate::{ + test_utils::{builder::*, AccountId32, ALICE}, + tests::RuntimeOrigin, + AccountIdLookupOf, Code, CodeHash, + }; + + pub fn bare_instantiate(code: Code>) -> BareInstantiateBuilder { + BareInstantiateBuilder::::bare_instantiate(RuntimeOrigin::signed(ALICE), code) + } + + pub fn bare_call(dest: AccountId32) -> BareCallBuilder { + BareCallBuilder::::bare_call(RuntimeOrigin::signed(ALICE), dest) + } + + pub fn instantiate_with_code(code: Vec) -> InstantiateWithCodeBuilder { + InstantiateWithCodeBuilder::::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + code, + ) + } + + pub fn instantiate(code_hash: CodeHash) -> InstantiateBuilder { + InstantiateBuilder::::instantiate(RuntimeOrigin::signed(ALICE), code_hash) + } + + pub fn call(dest: AccountIdLookupOf) -> CallBuilder { + CallBuilder::::call(RuntimeOrigin::signed(ALICE), dest) + } +} + +impl Test { + pub fn set_unstable_interface(unstable_interface: bool) { + UNSTABLE_INTERFACE.with(|v| *v.borrow_mut() = unstable_interface); + } +} + +parameter_types! { + static TestExtensionTestValue: TestExtension = Default::default(); +} + +#[derive(Clone)] +pub struct TestExtension { + enabled: bool, + last_seen_buffer: Vec, + last_seen_input_len: u32, +} + +#[derive(Default)] +pub struct RevertingExtension; + +#[derive(Default)] +pub struct DisabledExtension; + +#[derive(Default)] +pub struct TempStorageExtension { + storage: u32, +} + +impl TestExtension { + fn disable() { + TestExtensionTestValue::mutate(|e| e.enabled = false) + } + + fn last_seen_buffer() -> Vec { + TestExtensionTestValue::get().last_seen_buffer.clone() + } + + fn last_seen_input_len() -> u32 { + TestExtensionTestValue::get().last_seen_input_len + } +} + +impl Default for TestExtension { + fn default() -> Self { + Self { enabled: true, last_seen_buffer: vec![], last_seen_input_len: 0 } + } +} + +impl ChainExtension for TestExtension { + fn call(&mut self, mut env: Environment) -> ExtensionResult + where + E: Ext, + M: ?Sized + Memory, + { + let func_id = env.func_id(); + let id = env.ext_id() as u32 | func_id as u32; + match func_id { + 0 => { + let input = env.read(8)?; + env.write(&input, false, None)?; + TestExtensionTestValue::mutate(|e| e.last_seen_buffer = input); + Ok(RetVal::Converging(id)) + }, + 1 => { + TestExtensionTestValue::mutate(|e| e.last_seen_input_len = env.in_len()); + Ok(RetVal::Converging(id)) + }, + 2 => { + let mut enc = &env.read(9)?[4..8]; + let weight = Weight::from_parts( + u32::decode(&mut enc).map_err(|_| Error::::ContractTrapped)?.into(), + 0, + ); + env.charge_weight(weight)?; + Ok(RetVal::Converging(id)) + }, + 3 => Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![42, 99] }), + _ => { + panic!("Passed unknown id to test chain extension: {}", func_id); + }, + } + } + + fn enabled() -> bool { + TestExtensionTestValue::get().enabled + } +} + +impl RegisteredChainExtension for TestExtension { + const ID: u16 = 0; +} + +impl ChainExtension for RevertingExtension { + fn call(&mut self, _env: Environment) -> ExtensionResult + where + E: Ext, + M: ?Sized + Memory, + { + Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![0x4B, 0x1D] }) + } + + fn enabled() -> bool { + TestExtensionTestValue::get().enabled + } +} + +impl RegisteredChainExtension for RevertingExtension { + const ID: u16 = 1; +} + +impl ChainExtension for DisabledExtension { + fn call(&mut self, _env: Environment) -> ExtensionResult + where + E: Ext, + M: ?Sized + Memory, + { + panic!("Disabled chain extensions are never called") + } + + fn enabled() -> bool { + false + } +} + +impl RegisteredChainExtension for DisabledExtension { + const ID: u16 = 2; +} + +impl ChainExtension for TempStorageExtension { + fn call(&mut self, env: Environment) -> ExtensionResult + where + E: Ext, + M: ?Sized + Memory, + { + let func_id = env.func_id(); + match func_id { + 0 => self.storage = 42, + 1 => assert_eq!(self.storage, 42, "Storage is preserved inside the same call."), + 2 => { + assert_eq!(self.storage, 0, "Storage is different for different calls."); + self.storage = 99; + }, + 3 => assert_eq!(self.storage, 99, "Storage is preserved inside the same call."), + _ => { + panic!("Passed unknown id to test chain extension: {}", func_id); + }, + } + Ok(RetVal::Converging(0)) + } + + fn enabled() -> bool { + TestExtensionTestValue::get().enabled + } +} + +impl RegisteredChainExtension for TempStorageExtension { + const ID: u16 = 3; +} + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max( + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + ); + pub static ExistentialDeposit: u64 = 1; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type ExistentialDeposit = ExistentialDeposit; + type ReserveIdentifier = [u8; 8]; + type AccountStore = System; +} + +#[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)] +impl pallet_timestamp::Config for Test {} + +impl pallet_utility::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); +} + +impl pallet_proxy::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = (); + type ProxyDepositBase = ConstU64<1>; + type ProxyDepositFactor = ConstU64<1>; + type MaxProxies = ConstU32<32>; + type WeightInfo = (); + type MaxPending = ConstU32<32>; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = ConstU64<1>; + type AnnouncementDepositFactor = ConstU64<1>; +} + +impl pallet_dummy::Config for Test {} + +parameter_types! { + pub static DepositPerByte: BalanceOf = 1; + pub const DepositPerItem: BalanceOf = 2; + pub static CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); +} + +impl Convert> for Test { + fn convert(w: Weight) -> BalanceOf { + w.ref_time() + } +} + +/// A filter whose filter function can be swapped at runtime. +pub struct TestFilter; + +#[derive(Clone)] +pub struct Filters { + filter: fn(&RuntimeCall) -> bool, +} + +impl Default for Filters { + fn default() -> Self { + Filters { filter: (|_| true) } + } +} + +parameter_types! { + static CallFilter: Filters = Default::default(); +} + +impl TestFilter { + pub fn set_filter(filter: fn(&RuntimeCall) -> bool) { + CallFilter::mutate(|fltr| fltr.filter = filter); + } +} + +impl Contains for TestFilter { + fn contains(call: &RuntimeCall) -> bool { + (CallFilter::get().filter)(call) + } +} + +parameter_types! { + pub static UploadAccount: Option<::AccountId> = None; + pub static InstantiateAccount: Option<::AccountId> = None; +} + +pub struct EnsureAccount(core::marker::PhantomData<(T, A)>); +impl>>> + EnsureOrigin<::RuntimeOrigin> for EnsureAccount +where + ::AccountId: From, +{ + type Success = T::AccountId; + + fn try_origin(o: T::RuntimeOrigin) -> Result { + let who = as EnsureOrigin<_>>::try_origin(o.clone())?; + if matches!(A::get(), Some(a) if who != a) { + return Err(o); + } + + Ok(who) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Err(()) + } +} +parameter_types! { + pub static UnstableInterface: bool = true; +} + +#[derive_impl(crate::config_preludes::TestDefaultConfig)] +impl Config for Test { + type Time = Timestamp; + type Currency = Balances; + type CallFilter = TestFilter; + type ChainExtension = + (TestExtension, DisabledExtension, RevertingExtension, TempStorageExtension); + type DepositPerByte = DepositPerByte; + type DepositPerItem = DepositPerItem; + type AddressGenerator = DefaultAddressGenerator; + type UnsafeUnstableInterface = UnstableInterface; + type UploadOrigin = EnsureAccount; + type InstantiateOrigin = EnsureAccount; + type Migrations = crate::migration::codegen::BenchMigrations; + type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; + type Debug = TestDebug; +} + +pub struct ExtBuilder { + existential_deposit: u64, + storage_version: Option, + code_hashes: Vec>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + existential_deposit: ExistentialDeposit::get(), + storage_version: None, + code_hashes: vec![], + } + } +} + +impl ExtBuilder { + pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { + self.existential_deposit = existential_deposit; + self + } + pub fn with_code_hashes(mut self, code_hashes: Vec>) -> Self { + self.code_hashes = code_hashes; + self + } + pub fn set_associated_consts(&self) { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); + } + pub fn set_storage_version(mut self, version: u16) -> Self { + self.storage_version = Some(StorageVersion::new(version)); + self + } + pub fn build(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + self.set_associated_consts(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + pallet_balances::GenesisConfig:: { balances: vec![] } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); + ext.execute_with(|| { + use frame_support::traits::OnGenesis; + + Pallet::::on_genesis(); + if let Some(storage_version) = self.storage_version { + storage_version.put::>(); + } + System::set_block_number(1) + }); + ext.execute_with(|| { + for code_hash in self.code_hashes { + CodeInfoOf::::insert(code_hash, crate::CodeInfo::new(ALICE)); + } + }); + ext + } +} + +fn initialize_block(number: u64) { + System::reset_events(); + System::initialize(&number, &[0u8; 32].into(), &Default::default()); +} + +struct ExtensionInput<'a> { + extension_id: u16, + func_id: u16, + extra: &'a [u8], +} + +impl<'a> ExtensionInput<'a> { + fn to_vec(&self) -> Vec { + ((self.extension_id as u32) << 16 | (self.func_id as u32)) + .to_le_bytes() + .iter() + .chain(self.extra) + .cloned() + .collect() + } +} + +impl<'a> From> for Vec { + fn from(input: ExtensionInput) -> Vec { + input.to_vec() + } +} + +impl Default for Origin { + fn default() -> Self { + Self::Signed(ALICE) + } +} + +/// We can only run the tests if we have a riscv toolchain installed +#[cfg(feature = "riscv")] +mod run_tests { + use super::*; + use pretty_assertions::{assert_eq, assert_ne}; + + // Perform a call to a plain account. + // The actual transfer fails because we can only call contracts. + // Then we check that at least the base costs where charged (no runtime gas costs.) + #[test] + fn calling_plain_account_fails() { + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000); + let base_cost = <::WeightInfo as WeightInfo>::call(); + + assert_eq!( + builder::call(BOB).build(), + Err(DispatchErrorWithPostInfo { + error: Error::::ContractNotFound.into(), + post_info: PostDispatchInfo { + actual_weight: Some(base_cost), + pays_fee: Default::default(), + }, + }) + ); + }); + } + + #[test] + fn migration_on_idle_hooks_works() { + // Defines expectations of how many migration steps can be done given the weight limit. + let tests = [ + (Weight::zero(), LATEST_MIGRATION_VERSION - 2), + (::WeightInfo::migrate() + 1.into(), LATEST_MIGRATION_VERSION - 1), + (Weight::MAX, LATEST_MIGRATION_VERSION), + ]; + + for (weight, expected_version) in tests { + ExtBuilder::default() + .set_storage_version(LATEST_MIGRATION_VERSION - 2) + .build() + .execute_with(|| { + MigrationInProgress::::set(Some(Default::default())); + Contracts::on_idle(System::block_number(), weight); + assert_eq!(StorageVersion::get::>(), expected_version); + }); + } + } + + #[test] + fn migration_in_progress_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + MigrationInProgress::::set(Some(Default::default())); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + vec![], + deposit_limit::(), + ), + Error::::MigrationInProgress, + ); + assert_err!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + Error::::MigrationInProgress, + ); + assert_err!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB.clone(), code_hash), + Error::::MigrationInProgress, + ); + assert_err_ignore_postinfo!( + builder::call(BOB).build(), + Error::::MigrationInProgress + ); + assert_err_ignore_postinfo!( + builder::instantiate_with_code(wasm).value(100_000).build(), + Error::::MigrationInProgress, + ); + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).value(100_000).build(), + Error::::MigrationInProgress, + ); + }); + } + + #[test] + fn instantiate_and_call_and_deposit_event() { + let (wasm, code_hash) = compile_module::("event_and_return_on_deploy").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let value = 100; + + // We determine the storage deposit limit after uploading because it depends on ALICEs + // free balance which is changed by uploading a module. + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + deposit_limit::(), + )); + + // Drop previous events + initialize_block(2); + + // Check at the end to get hash on error easily + let addr = builder::bare_instantiate(Code::Existing(code_hash)) + .value(value) + .build_and_unwrap_account_id(); + assert!(ContractInfoOf::::contains_key(&addr)); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: value, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::ContractEmitted { + contract: addr.clone(), + data: vec![1, 2, 3, 4] + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE, + contract: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE, + to: addr.clone(), + amount: test_utils::contract_info_storage_deposit(&addr), + } + ), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn deposit_event_max_value_limit() { + let (wasm, _code_hash) = compile_module::("event_size").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_account_id(); + + // Call contract with allowed storage value. + assert_ok!(builder::call(addr.clone()) + .gas_limit(GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2)) // we are copying a huge buffer, + .data(limits::PAYLOAD_BYTES.encode()) + .build()); + + // Call contract with too large a storage value. + assert_err_ignore_postinfo!( + builder::call(addr).data((limits::PAYLOAD_BYTES + 1).encode()).build(), + Error::::ValueTooLarge, + ); + }); + } + + // Fail out of fuel (ref_time weight) in the engine. + #[test] + fn run_out_of_fuel_engine() { + let (wasm, _code_hash) = compile_module::("run_out_of_gas").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(100 * min_balance) + .build_and_unwrap_account_id(); + + // Call the contract with a fixed gas limit. It must run out of gas because it just + // loops forever. + assert_err_ignore_postinfo!( + builder::call(addr) + .gas_limit(Weight::from_parts(10_000_000_000, u64::MAX)) + .build(), + Error::::OutOfGas, + ); + }); + } + + // Fail out of fuel (ref_time weight) in the host. + #[test] + fn run_out_of_fuel_host() { + let (code, _hash) = compile_module::("chain_extension").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let gas_limit = Weight::from_parts(u32::MAX as u64, GAS_LIMIT.proof_size()); + + // Use chain extension to charge more ref_time than it is available. + let result = builder::bare_call(addr.clone()) + .gas_limit(gas_limit) + .data( + ExtensionInput { extension_id: 0, func_id: 2, extra: &u32::MAX.encode() } + .into(), + ) + .build() + .result; + assert_err!(result, >::OutOfGas); + }); + } + + #[test] + fn gas_syncs_work() { + let (code, _code_hash) = compile_module::("caller_is_origin_n").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let addr = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_account_id(); + + let result = builder::bare_call(addr.clone()).data(0u32.encode()).build(); + assert_ok!(result.result); + let engine_consumed_noop = result.gas_consumed.ref_time(); + + let result = builder::bare_call(addr.clone()).data(1u32.encode()).build(); + assert_ok!(result.result); + let gas_consumed_once = result.gas_consumed.ref_time(); + let host_consumed_once = + ::WeightInfo::seal_caller_is_origin().ref_time(); + let engine_consumed_once = + gas_consumed_once - host_consumed_once - engine_consumed_noop; + + let result = builder::bare_call(addr).data(2u32.encode()).build(); + assert_ok!(result.result); + let gas_consumed_twice = result.gas_consumed.ref_time(); + let host_consumed_twice = host_consumed_once * 2; + let engine_consumed_twice = + gas_consumed_twice - host_consumed_twice - engine_consumed_noop; + + // Second contract just repeats first contract's instructions twice. + // If runtime syncs gas with the engine properly, this should pass. + assert_eq!(engine_consumed_twice, engine_consumed_once * 2); + }); + } + + /// Check that contracts with the same account id have different trie ids. + /// Check the `Nonce` storage item for more information. + #[test] + fn instantiate_unique_trie_id() { + let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, deposit_limit::()) + .unwrap(); + + // Instantiate the contract and store its trie id for later comparison. + let addr = + builder::bare_instantiate(Code::Existing(code_hash)).build_and_unwrap_account_id(); + let trie_id = get_contract(&addr).trie_id; + + // Try to instantiate it again without termination should yield an error. + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).build(), + >::DuplicateContract, + ); + + // Terminate the contract. + assert_ok!(builder::call(addr.clone()).build()); + + // Re-Instantiate after termination. + assert_ok!(builder::instantiate(code_hash).build()); + + // Trie ids shouldn't match or we might have a collision + assert_ne!(trie_id, get_contract(&addr).trie_id); + }); + } + + #[test] + fn storage_work() { + let (code, _code_hash) = compile_module::("storage").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + builder::bare_call(addr).build_and_unwrap_result(); + }); + } + + #[test] + fn storage_max_value_limit() { + let (wasm, _code_hash) = compile_module::("storage_size").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_account_id(); + get_contract(&addr); + + // Call contract with allowed storage value. + assert_ok!(builder::call(addr.clone()) + .gas_limit(GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2)) // we are copying a huge buffer + .data(limits::PAYLOAD_BYTES.encode()) + .build()); + + // Call contract with too large a storage value. + assert_err_ignore_postinfo!( + builder::call(addr).data((limits::PAYLOAD_BYTES + 1).encode()).build(), + Error::::ValueTooLarge, + ); + }); + } + + #[test] + fn transient_storage_work() { + let (code, _code_hash) = compile_module::("transient_storage").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + builder::bare_call(addr).build_and_unwrap_result(); + }); + } + + #[test] + fn transient_storage_limit_in_call() { + let (wasm_caller, _code_hash_caller) = + compile_module::("create_transient_storage_and_call").unwrap(); + let (wasm_callee, _code_hash_callee) = + compile_module::("set_transient_storage").unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let addr_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + // Call contracts with storage values within the limit. + // Caller and Callee contracts each set a transient storage value of size 100. + assert_ok!(builder::call(addr_caller.clone()) + .data((100u32, 100u32, &addr_callee).encode()) + .build(),); + + // Call a contract with a storage value that is too large. + // Limit exceeded in the caller contract. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .data((4u32 * 1024u32, 200u32, &addr_callee).encode()) + .build(), + >::OutOfTransientStorage, + ); + + // Call a contract with a storage value that is too large. + // Limit exceeded in the callee contract. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .data((50u32, 4 * 1024u32, &addr_callee).encode()) + .build(), + >::ContractTrapped + ); + }); + } + + #[test] + fn deploy_and_call_other_contract() { + let (caller_wasm, _caller_code_hash) = compile_module::("caller_contract").unwrap(); + let (callee_wasm, callee_code_hash) = compile_module::("return_with_data").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + + // Create + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let caller_addr = builder::bare_instantiate(Code::Upload(caller_wasm)) + .value(100_000) + .build_and_unwrap_account_id(); + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + callee_wasm, + deposit_limit::(), + ) + .unwrap(); + + let callee_addr = Contracts::contract_address( + &caller_addr, + &callee_code_hash, + &[0, 1, 34, 51, 68, 85, 102, 119], // hard coded in wasm + &[], + ); + + // Drop previous events + initialize_block(2); + + // Call BOB contract, which attempts to instantiate and call the callee contract and + // makes various assertions on the results from those calls. + assert_ok!(builder::call(caller_addr.clone()) + .data(callee_code_hash.as_ref().to_vec()) + .build()); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: callee_addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: callee_addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: callee_addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: caller_addr.clone(), + to: callee_addr.clone(), + amount: 32768 // hardcoded in wasm + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: caller_addr.clone(), + contract: callee_addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: caller_addr.clone(), + to: callee_addr.clone(), + amount: 32768, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(caller_addr.clone()), + contract: callee_addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: caller_addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE, + to: callee_addr.clone(), + amount: test_utils::contract_info_storage_deposit(&callee_addr), + } + ), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn delegate_call() { + let (caller_wasm, _caller_code_hash) = compile_module::("delegate_call").unwrap(); + let (callee_wasm, callee_code_hash) = compile_module::("delegate_call_lib").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the 'caller' + let caller_addr = builder::bare_instantiate(Code::Upload(caller_wasm)) + .value(300_000) + .build_and_unwrap_account_id(); + // Only upload 'callee' code + assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), callee_wasm, 100_000,)); + + assert_ok!(builder::call(caller_addr.clone()) + .value(1337) + .data(callee_code_hash.as_ref().to_vec()) + .build()); + }); + } + + #[test] + fn transfer_expendable_cannot_kill_account() { + let (wasm, _code_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(1_000) + .build_and_unwrap_account_id(); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + let total_balance = ::Currency::total_balance(&addr); + + assert_eq!( + test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &addr), + test_utils::contract_info_storage_deposit(&addr) + ); + + // Some ot the total balance is held, so it can't be transferred. + assert_err!( + <::Currency as Mutate>::transfer( + &addr, + &ALICE, + total_balance, + Preservation::Expendable, + ), + TokenError::FundsUnavailable, + ); + + assert_eq!(::Currency::total_balance(&addr), total_balance); + }); + } + + #[test] + fn cannot_self_destruct_through_draining() { + let (wasm, _code_hash) = compile_module::("drain").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let value = 1_000; + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(value) + .build_and_unwrap_account_id(); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + // Call BOB which makes it send all funds to the zero address + // The contract code asserts that the transfer fails with the correct error code + assert_ok!(builder::call(addr.clone()).build()); + + // Make sure the account wasn't remove by sending all free balance away. + assert_eq!( + ::Currency::total_balance(&addr), + value + test_utils::contract_info_storage_deposit(&addr) + min_balance, + ); + }); + } + + #[test] + fn cannot_self_destruct_through_storage_refund_after_price_change() { + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!(get_contract(&addr).extra_deposit(), 0); + assert_eq!( + ::Currency::total_balance(&addr), + info_deposit + min_balance + ); + + // Create 100 bytes of storage with a price of per byte and a single storage item of + // price 2 + assert_ok!(builder::call(addr.clone()).data(100u32.to_le_bytes().to_vec()).build()); + assert_eq!(get_contract(&addr).total_deposit(), info_deposit + 102); + + // Increase the byte price and trigger a refund. This should not have any influence + // because the removal is pro rata and exactly those 100 bytes should have been + // removed. + DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 500); + assert_ok!(builder::call(addr.clone()).data(0u32.to_le_bytes().to_vec()).build()); + + // Make sure the account wasn't removed by the refund + assert_eq!( + ::Currency::total_balance(&addr), + get_contract(&addr).total_deposit() + min_balance, + ); + assert_eq!(get_contract(&addr).extra_deposit(), 2); + }); + } + + #[test] + fn cannot_self_destruct_while_live() { + let (wasm, _code_hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_account_id(); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + // Call BOB with input data, forcing it make a recursive call to itself to + // self-destruct, resulting in a trap. + assert_err_ignore_postinfo!( + builder::call(addr.clone()).data(vec![0]).build(), + Error::::ContractTrapped, + ); + + // Check that BOB is still there. + get_contract(&addr); + }); + } + + #[test] + fn self_destruct_works() { + let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(1_000).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&DJANGO, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_account_id(); + + // Check that the BOB contract has been instantiated. + let _ = get_contract(&addr); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + + // Drop all previous events + initialize_block(2); + + // Call BOB without input data which triggers termination. + assert_matches!(builder::call(addr.clone()).build(), Ok(_)); + + // Check that code is still there but refcount dropped to zero. + assert_refcount!(&code_hash, 0); + + // Check that account is gone + assert!(get_contract_checked(&addr).is_none()); + assert_eq!(::Currency::total_balance(&addr), 0); + + // Check that the beneficiary (django) got remaining balance. + assert_eq!( + ::Currency::free_balance(DJANGO), + 1_000_000 + 100_000 + min_balance + ); + + // Check that the Alice is missing Django's benefit. Within ALICE's total balance + // there's also the code upload deposit held. + assert_eq!( + ::Currency::total_balance(&ALICE), + 1_000_000 - (100_000 + min_balance) + ); + + pretty_assertions::assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Terminated { + contract: addr.clone(), + beneficiary: DJANGO + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndReleased { + from: addr.clone(), + to: ALICE, + amount: info_deposit, + } + ), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::KilledAccount { + account: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: addr.clone(), + to: DJANGO, + amount: 100_000 + min_balance, + }), + topics: vec![], + }, + ], + ); + }); + } + + // This tests that one contract cannot prevent another from self-destructing by sending it + // additional funds after it has been drained. + #[test] + fn destroy_contract_and_transfer_funds() { + let (callee_wasm, callee_code_hash) = compile_module::("self_destruct").unwrap(); + let (caller_wasm, _caller_code_hash) = + compile_module::("destroy_and_transfer").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create code hash for bob to instantiate + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + callee_wasm, + deposit_limit::(), + ) + .unwrap(); + + // This deploys the BOB contract, which in turn deploys the CHARLIE contract during + // construction. + let addr_bob = builder::bare_instantiate(Code::Upload(caller_wasm)) + .value(200_000) + .data(callee_code_hash.as_ref().to_vec()) + .build_and_unwrap_account_id(); + + // Check that the CHARLIE contract has been instantiated. + let addr_charlie = + Contracts::contract_address(&addr_bob, &callee_code_hash, &[], &[0x47, 0x11]); + get_contract(&addr_charlie); + + // Call BOB, which calls CHARLIE, forcing CHARLIE to self-destruct. + assert_ok!(builder::call(addr_bob).data(addr_charlie.encode()).build()); + + // Check that CHARLIE has moved on to the great beyond (ie. died). + assert!(get_contract_checked(&addr_charlie).is_none()); + }); + } + + #[test] + fn cannot_self_destruct_in_constructor() { + let (wasm, _) = compile_module::("self_destructing_constructor").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Fail to instantiate the BOB because the constructor calls seal_terminate. + assert_err_ignore_postinfo!( + builder::instantiate_with_code(wasm).value(100_000).build(), + Error::::TerminatedInConstructor, + ); + }); + } + + #[test] + fn crypto_hashes() { + let (wasm, _code_hash) = compile_module::("crypto_hashes").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the CRYPTO_HASHES contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_account_id(); + // Perform the call. + let input = b"_DEAD_BEEF"; + use sp_io::hashing::*; + // Wraps a hash function into a more dynamic form usable for testing. + macro_rules! dyn_hash_fn { + ($name:ident) => { + Box::new(|input| $name(input).as_ref().to_vec().into_boxed_slice()) + }; + } + // All hash functions and their associated output byte lengths. + let test_cases: &[(Box Box<[u8]>>, usize)] = &[ + (dyn_hash_fn!(sha2_256), 32), + (dyn_hash_fn!(keccak_256), 32), + (dyn_hash_fn!(blake2_256), 32), + (dyn_hash_fn!(blake2_128), 16), + ]; + // Test the given hash functions for the input: "_DEAD_BEEF" + for (n, (hash_fn, expected_size)) in test_cases.iter().enumerate() { + // We offset data in the contract tables by 1. + let mut params = vec![(n + 1) as u8]; + params.extend_from_slice(input); + let result = + builder::bare_call(addr.clone()).data(params).build_and_unwrap_result(); + assert!(!result.did_revert()); + let expected = hash_fn(input.as_ref()); + assert_eq!(&result.data[..*expected_size], &*expected); + } + }) + } + + #[test] + fn transfer_return_code() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + // Contract has only the minimal balance so any transfer will fail. + ::Currency::set_balance(&addr, min_balance); + let result = builder::bare_call(addr.clone()).build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); + }); + } + + #[test] + fn call_return_code() { + let (caller_code, _caller_hash) = compile_module::("call_return_code").unwrap(); + let (callee_code, _callee_hash) = compile_module::("ok_trap_revert").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); + + let addr_bob = builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .data(vec![0]) + .build_and_unwrap_account_id(); + ::Currency::set_balance(&addr_bob, min_balance); + + // Contract calls into Django which is no valid contract + let result = builder::bare_call(addr_bob.clone()) + .data(AsRef::<[u8]>::as_ref(&DJANGO).to_vec()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::NotCallable); + + let addr_django = builder::bare_instantiate(Code::Upload(callee_code)) + .origin(RuntimeOrigin::signed(CHARLIE)) + .value(min_balance * 100) + .data(vec![0]) + .build_and_unwrap_account_id(); + ::Currency::set_balance(&addr_django, min_balance); + + // Contract has only the minimal balance so any transfer will fail. + let result = builder::bare_call(addr_bob.clone()) + .data( + AsRef::<[u8]>::as_ref(&addr_django) + .iter() + .chain(&0u32.to_le_bytes()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); + + // Contract has enough balance but callee reverts because "1" is passed. + ::Currency::set_balance(&addr_bob, min_balance + 1000); + let result = builder::bare_call(addr_bob.clone()) + .data( + AsRef::<[u8]>::as_ref(&addr_django) + .iter() + .chain(&1u32.to_le_bytes()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeReverted); + + // Contract has enough balance but callee traps because "2" is passed. + let result = builder::bare_call(addr_bob) + .data( + AsRef::<[u8]>::as_ref(&addr_django) + .iter() + .chain(&2u32.to_le_bytes()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); + }); + } + + #[test] + fn instantiate_return_code() { + let (caller_code, _caller_hash) = + compile_module::("instantiate_return_code").unwrap(); + let (callee_code, callee_hash) = compile_module::("ok_trap_revert").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); + let callee_hash = callee_hash.as_ref().to_vec(); + + assert_ok!(builder::instantiate_with_code(callee_code) + .value(min_balance * 100) + .build()); + + let addr = builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + // Contract has only the minimal balance so any transfer will fail. + ::Currency::set_balance(&addr, min_balance); + let result = builder::bare_call(addr.clone()) + .data(callee_hash.clone()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); + + // Contract has enough balance but the passed code hash is invalid + ::Currency::set_balance(&addr, min_balance + 10_000); + let result = + builder::bare_call(addr.clone()).data(vec![0; 33]).build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CodeNotFound); + + // Contract has enough balance but callee reverts because "1" is passed. + let result = builder::bare_call(addr.clone()) + .data(callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeReverted); + + // Contract has enough balance but callee traps because "2" is passed. + let result = builder::bare_call(addr) + .data(callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); + }); + } + + #[test] + fn disabled_chain_extension_errors_on_call() { + let (code, _hash) = compile_module::("chain_extension").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + TestExtension::disable(); + assert_err_ignore_postinfo!( + builder::call(addr.clone()).data(vec![7u8; 8]).build(), + Error::::NoChainExtension, + ); + }); + } + + #[test] + fn chain_extension_works() { + let (code, _hash) = compile_module::("chain_extension").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + // 0 = read input buffer and pass it through as output + let input: Vec = + ExtensionInput { extension_id: 0, func_id: 0, extra: &[99] }.into(); + let result = builder::bare_call(addr.clone()).data(input.clone()).build(); + assert_eq!(TestExtension::last_seen_buffer(), input); + assert_eq!(result.result.unwrap().data, input); + + // 1 = treat inputs as integer primitives and store the supplied integers + builder::bare_call(addr.clone()) + .data(ExtensionInput { extension_id: 0, func_id: 1, extra: &[] }.into()) + .build_and_unwrap_result(); + assert_eq!(TestExtension::last_seen_input_len(), 4); + + // 2 = charge some extra weight (amount supplied in the fifth byte) + let result = builder::bare_call(addr.clone()) + .data(ExtensionInput { extension_id: 0, func_id: 2, extra: &0u32.encode() }.into()) + .build(); + assert_ok!(result.result); + let gas_consumed = result.gas_consumed; + let result = builder::bare_call(addr.clone()) + .data(ExtensionInput { extension_id: 0, func_id: 2, extra: &42u32.encode() }.into()) + .build(); + assert_ok!(result.result); + assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 42); + let result = builder::bare_call(addr.clone()) + .data(ExtensionInput { extension_id: 0, func_id: 2, extra: &95u32.encode() }.into()) + .build(); + assert_ok!(result.result); + assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 95); + + // 3 = diverging chain extension call that sets flags to 0x1 and returns a fixed buffer + let result = builder::bare_call(addr.clone()) + .data(ExtensionInput { extension_id: 0, func_id: 3, extra: &[] }.into()) + .build_and_unwrap_result(); + assert_eq!(result.flags, ReturnFlags::REVERT); + assert_eq!(result.data, vec![42, 99]); + + // diverging to second chain extension that sets flags to 0x1 and returns a fixed buffer + // We set the MSB part to 1 (instead of 0) which routes the request into the second + // extension + let result = builder::bare_call(addr.clone()) + .data(ExtensionInput { extension_id: 1, func_id: 0, extra: &[] }.into()) + .build_and_unwrap_result(); + assert_eq!(result.flags, ReturnFlags::REVERT); + assert_eq!(result.data, vec![0x4B, 0x1D]); + + // Diverging to third chain extension that is disabled + // We set the MSB part to 2 (instead of 0) which routes the request into the third + // extension + assert_err_ignore_postinfo!( + builder::call(addr.clone()) + .data(ExtensionInput { extension_id: 2, func_id: 0, extra: &[] }.into()) + .build(), + Error::::NoChainExtension, + ); + }); + } + + #[test] + fn chain_extension_temp_storage_works() { + let (code, _hash) = compile_module::("chain_extension_temp_storage").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + // Call func 0 and func 1 back to back. + let stop_recursion = 0u8; + let mut input: Vec = + ExtensionInput { extension_id: 3, func_id: 0, extra: &[] }.into(); + input.extend_from_slice( + ExtensionInput { extension_id: 3, func_id: 1, extra: &[stop_recursion] } + .to_vec() + .as_ref(), + ); + + assert_ok!(builder::bare_call(addr.clone()).data(input.clone()).build().result); + }) + } + + #[test] + fn lazy_removal_works() { + let (code, _hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let info = get_contract(&addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract + assert_ok!(builder::call(addr.clone()).build()); + + // Contract info should be gone + assert!(!>::contains_key(&addr)); + + // But value should be still there as the lazy removal did not run, yet. + assert_matches!(child::get(trie, &[99]), Some(42)); + + // Run the lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); + + // Value should be gone now + assert_matches!(child::get::(trie, &[99]), None); + }); + } + + #[test] + fn lazy_batch_removal_works() { + let (code, _hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let mut tries: Vec = vec![]; + + for i in 0..3u8 { + let addr = builder::bare_instantiate(Code::Upload(code.clone())) + .value(min_balance * 100) + .salt(vec![i]) + .build_and_unwrap_account_id(); + + let info = get_contract(&addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract. Contract info should be gone, but value should be still + // there as the lazy removal did not run, yet. + assert_ok!(builder::call(addr.clone()).build()); + + assert!(!>::contains_key(&addr)); + assert_matches!(child::get(trie, &[99]), Some(42)); + + tries.push(trie.clone()) + } + + // Run single lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); + + // The single lazy removal should have removed all queued tries + for trie in tries.iter() { + assert_matches!(child::get::(trie, &[99]), None); + } + }); + } + + #[test] + fn lazy_removal_partial_remove_works() { + let (code, _hash) = compile_module::("self_destruct").unwrap(); + + // We create a contract with some extra keys above the weight limit + let extra_keys = 7u32; + let mut meter = WeightMeter::with_limit(Weight::from_parts(5_000_000_000, 100 * 1024)); + let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(&meter); + let vals: Vec<_> = (0..max_keys + extra_keys) + .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) + .collect(); + + let mut ext = ExtBuilder::default().existential_deposit(50).build(); + + let trie = ext.execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let info = get_contract(&addr); + + // Put value into the contracts child trie + for val in &vals { + info.write(&Key::Fix(val.0), Some(val.2.clone()), None, false).unwrap(); + } + >::insert(&addr, info.clone()); + + // Terminate the contract + assert_ok!(builder::call(addr.clone()).build()); + + // Contract info should be gone + assert!(!>::contains_key(&addr)); + + let trie = info.child_trie_info(); + + // But value should be still there as the lazy removal did not run, yet. + for val in &vals { + assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); + } + + trie.clone() + }); + + // The lazy removal limit only applies to the backend but not to the overlay. + // This commits all keys from the overlay to the backend. + ext.commit_all().unwrap(); + + ext.execute_with(|| { + // Run the lazy removal + ContractInfo::::process_deletion_queue_batch(&mut meter); + + // Weight should be exhausted because we could not even delete all keys + assert!(!meter.can_consume(weight_per_key)); + + let mut num_deleted = 0u32; + let mut num_remaining = 0u32; + + for val in &vals { + match child::get::(&trie, &blake2_256(&val.0)) { + None => num_deleted += 1, + Some(x) if x == val.1 => num_remaining += 1, + Some(_) => panic!("Unexpected value in contract storage"), + } + } + + // All but one key is removed + assert_eq!(num_deleted + num_remaining, vals.len() as u32); + assert_eq!(num_deleted, max_keys); + assert_eq!(num_remaining, extra_keys); + }); + } + + #[test] + fn lazy_removal_does_no_run_on_low_remaining_weight() { + let (code, _hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let info = get_contract(&addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract + assert_ok!(builder::call(addr.clone()).build()); + + // Contract info should be gone + assert!(!>::contains_key(&addr)); + + // But value should be still there as the lazy removal did not run, yet. + assert_matches!(child::get(trie, &[99]), Some(42)); + + // Assign a remaining weight which is too low for a successful deletion of the contract + let low_remaining_weight = + <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); + + // Run the lazy removal + Contracts::on_idle(System::block_number(), low_remaining_weight); + + // Value should still be there, since remaining weight was too low for removal + assert_matches!(child::get::(trie, &[99]), Some(42)); + + // Run the lazy removal while deletion_queue is not full + Contracts::on_initialize(System::block_number()); + + // Value should still be there, since deletion_queue was not full + assert_matches!(child::get::(trie, &[99]), Some(42)); + + // Run on_idle with max remaining weight, this should remove the value + Contracts::on_idle(System::block_number(), Weight::MAX); + + // Value should be gone + assert_matches!(child::get::(trie, &[99]), None); + }); + } + + #[test] + fn lazy_removal_does_not_use_all_weight() { + let (code, _hash) = compile_module::("self_destruct").unwrap(); + + let mut meter = WeightMeter::with_limit(Weight::from_parts(5_000_000_000, 100 * 1024)); + let mut ext = ExtBuilder::default().existential_deposit(50).build(); + + let (trie, vals, weight_per_key) = ext.execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let info = get_contract(&addr); + let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(&meter); + assert!(max_keys > 0); + + // We create a contract with one less storage item than we can remove within the limit + let vals: Vec<_> = (0..max_keys - 1) + .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) + .collect(); + + // Put value into the contracts child trie + for val in &vals { + info.write(&Key::Fix(val.0), Some(val.2.clone()), None, false).unwrap(); + } + >::insert(&addr, info.clone()); + + // Terminate the contract + assert_ok!(builder::call(addr.clone()).build()); + + // Contract info should be gone + assert!(!>::contains_key(&addr)); + + let trie = info.child_trie_info(); + + // But value should be still there as the lazy removal did not run, yet. + for val in &vals { + assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); + } + + (trie, vals, weight_per_key) + }); + + // The lazy removal limit only applies to the backend but not to the overlay. + // This commits all keys from the overlay to the backend. + ext.commit_all().unwrap(); + + ext.execute_with(|| { + // Run the lazy removal + ContractInfo::::process_deletion_queue_batch(&mut meter); + let base_weight = + <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); + assert_eq!(meter.consumed(), weight_per_key.mul(vals.len() as _) + base_weight); + + // All the keys are removed + for val in vals { + assert_eq!(child::get::(&trie, &blake2_256(&val.0)), None); + } + }); + } + + #[test] + fn deletion_queue_ring_buffer_overflow() { + let (code, _hash) = compile_module::("self_destruct").unwrap(); + let mut ext = ExtBuilder::default().existential_deposit(50).build(); + + // setup the deletion queue with custom counters + ext.execute_with(|| { + let queue = DeletionQueueManager::from_test_values(u32::MAX - 1, u32::MAX - 1); + >::set(queue); + }); + + // commit the changes to the storage + ext.commit_all().unwrap(); + + ext.execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let mut tries: Vec = vec![]; + + // add 3 contracts to the deletion queue + for i in 0..3u8 { + let addr = builder::bare_instantiate(Code::Upload(code.clone())) + .value(min_balance * 100) + .salt(vec![i]) + .build_and_unwrap_account_id(); + + let info = get_contract(&addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract. Contract info should be gone, but value should be still + // there as the lazy removal did not run, yet. + assert_ok!(builder::call(addr.clone()).build()); + + assert!(!>::contains_key(&addr)); + assert_matches!(child::get(trie, &[99]), Some(42)); + + tries.push(trie.clone()) + } + + // Run single lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); + + // The single lazy removal should have removed all queued tries + for trie in tries.iter() { + assert_matches!(child::get::(trie, &[99]), None); + } + + // insert and delete counter values should go from u32::MAX - 1 to 1 + assert_eq!(>::get().as_test_tuple(), (1, 1)); + }) + } + #[test] + fn refcounter() { + let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Create two contracts with the same code and check that they do in fact share it. + let addr0 = builder::bare_instantiate(Code::Upload(wasm.clone())) + .value(min_balance * 100) + .salt(vec![0]) + .build_and_unwrap_account_id(); + let addr1 = builder::bare_instantiate(Code::Upload(wasm.clone())) + .value(min_balance * 100) + .salt(vec![1]) + .build_and_unwrap_account_id(); + assert_refcount!(code_hash, 2); + + // Sharing should also work with the usual instantiate call + let addr2 = builder::bare_instantiate(Code::Existing(code_hash)) + .value(min_balance * 100) + .salt(vec![2]) + .build_and_unwrap_account_id(); + assert_refcount!(code_hash, 3); + + // Terminating one contract should decrement the refcount + assert_ok!(builder::call(addr0).build()); + assert_refcount!(code_hash, 2); + + // remove another one + assert_ok!(builder::call(addr1).build()); + assert_refcount!(code_hash, 1); + + // Pristine code should still be there + PristineCode::::get(code_hash).unwrap(); + + // remove the last contract + assert_ok!(builder::call(addr2).build()); + assert_refcount!(code_hash, 0); + + // refcount is `0` but code should still exists because it needs to be removed manually + assert!(crate::PristineCode::::contains_key(&code_hash)); + }); + } + + #[test] + fn debug_message_works() { + let (wasm, _code_hash) = compile_module::("debug_message_works").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_account_id(); + let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); + + assert_matches!(result.result, Ok(_)); + assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!"); + }); + } + + #[test] + fn debug_message_logging_disabled() { + let (wasm, _code_hash) = compile_module::("debug_message_logging_disabled").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_account_id(); + // the dispatchables always run without debugging + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr, + 0, + GAS_LIMIT, + deposit_limit::(), + vec![] + )); + }); + } + + #[test] + fn debug_message_invalid_utf8() { + let (wasm, _code_hash) = compile_module::("debug_message_invalid_utf8").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_account_id(); + let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); + assert_ok!(result.result); + assert!(result.debug_message.is_empty()); + }); + } + + #[test] + fn gas_estimation_for_subcalls() { + let (caller_code, _caller_hash) = compile_module::("call_with_limit").unwrap(); + let (call_runtime_code, _caller_hash) = compile_module::("call_runtime").unwrap(); + let (dummy_code, _callee_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 2_000 * min_balance); + + let addr_caller = builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let addr_dummy = builder::bare_instantiate(Code::Upload(dummy_code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let addr_call_runtime = builder::bare_instantiate(Code::Upload(call_runtime_code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + // Run the test for all of those weight limits for the subcall + let weights = [ + Weight::zero(), + GAS_LIMIT, + GAS_LIMIT * 2, + GAS_LIMIT / 5, + Weight::from_parts(0, GAS_LIMIT.proof_size()), + Weight::from_parts(GAS_LIMIT.ref_time(), 0), + ]; + + // This call is passed to the sub call in order to create a large `required_weight` + let runtime_call = RuntimeCall::Dummy(pallet_dummy::Call::overestimate_pre_charge { + pre_charge: Weight::from_parts(10_000_000_000, 512 * 1024), + actual_weight: Weight::from_parts(1, 1), + }) + .encode(); + + // Encodes which contract should be sub called with which input + let sub_calls: [(&[u8], Vec<_>, bool); 2] = [ + (addr_dummy.as_ref(), vec![], false), + (addr_call_runtime.as_ref(), runtime_call, true), + ]; + + for weight in weights { + for (sub_addr, sub_input, out_of_gas_in_subcall) in &sub_calls { + let input: Vec = sub_addr + .iter() + .cloned() + .chain(weight.ref_time().to_le_bytes()) + .chain(weight.proof_size().to_le_bytes()) + .chain(sub_input.clone()) + .collect(); + + // Call in order to determine the gas that is required for this call + let result_orig = + builder::bare_call(addr_caller.clone()).data(input.clone()).build(); + assert_ok!(&result_orig.result); + + // If the out of gas happens in the subcall the caller contract + // will just trap. Otherwise we would need to forward an error + // code to signal that the sub contract ran out of gas. + let error: DispatchError = if *out_of_gas_in_subcall { + assert!(result_orig.gas_required.all_gt(result_orig.gas_consumed)); + >::ContractTrapped.into() + } else { + assert_eq!(result_orig.gas_required, result_orig.gas_consumed); + >::OutOfGas.into() + }; + + // Make the same call using the estimated gas. Should succeed. + let result = builder::bare_call(addr_caller.clone()) + .gas_limit(result_orig.gas_required) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .data(input.clone()) + .build(); + assert_ok!(&result.result); + + // Check that it fails with too little ref_time + let result = builder::bare_call(addr_caller.clone()) + .gas_limit(result_orig.gas_required.sub_ref_time(1)) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .data(input.clone()) + .build(); + assert_err!(result.result, error); + + // Check that it fails with too little proof_size + let result = builder::bare_call(addr_caller.clone()) + .gas_limit(result_orig.gas_required.sub_proof_size(1)) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .data(input.clone()) + .build(); + assert_err!(result.result, error); + } + } + }); + } + + #[test] + fn gas_estimation_call_runtime() { + let (caller_code, _caller_hash) = compile_module::("call_runtime").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); + + let addr_caller = builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .salt(vec![0]) + .build_and_unwrap_account_id(); + + // Call something trivial with a huge gas limit so that we can observe the effects + // of pre-charging. This should create a difference between consumed and required. + let call = RuntimeCall::Dummy(pallet_dummy::Call::overestimate_pre_charge { + pre_charge: Weight::from_parts(10_000_000, 1_000), + actual_weight: Weight::from_parts(100, 100), + }); + let result = builder::bare_call(addr_caller.clone()).data(call.encode()).build(); + // contract encodes the result of the dispatch runtime + let outcome = u32::decode(&mut result.result.unwrap().data.as_ref()).unwrap(); + assert_eq!(outcome, 0); + assert!(result.gas_required.all_gt(result.gas_consumed)); + + // Make the same call using the required gas. Should succeed. + assert_ok!( + builder::bare_call(addr_caller) + .gas_limit(result.gas_required) + .data(call.encode()) + .build() + .result + ); + }); + } + + #[test] + fn call_runtime_reentrancy_guarded() { + let (caller_code, _caller_hash) = compile_module::("call_runtime").unwrap(); + let (callee_code, _callee_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); + + let addr_caller = builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .salt(vec![0]) + .build_and_unwrap_account_id(); + + let addr_callee = builder::bare_instantiate(Code::Upload(callee_code)) + .value(min_balance * 100) + .salt(vec![1]) + .build_and_unwrap_account_id(); + + // Call pallet_revive call() dispatchable + let call = RuntimeCall::Contracts(crate::Call::call { + dest: addr_callee, + value: 0, + gas_limit: GAS_LIMIT / 3, + storage_deposit_limit: deposit_limit::(), + data: vec![], + }); + + // Call runtime to re-enter back to contracts engine by + // calling dummy contract + let result = builder::bare_call(addr_caller.clone()) + .data(call.encode()) + .build_and_unwrap_result(); + // Call to runtime should fail because of the re-entrancy guard + assert_return_code!(result, RuntimeReturnCode::CallRuntimeFailed); + }); + } + + #[test] + fn ecdsa_recover() { + let (wasm, _code_hash) = compile_module::("ecdsa_recover").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the ecdsa_recover contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_account_id(); + + #[rustfmt::skip] + let signature: [u8; 65] = [ + 161, 234, 203, 74, 147, 96, 51, 212, 5, 174, 231, 9, 142, 48, 137, 201, + 162, 118, 192, 67, 239, 16, 71, 216, 125, 86, 167, 139, 70, 7, 86, 241, + 33, 87, 154, 251, 81, 29, 160, 4, 176, 239, 88, 211, 244, 232, 232, 52, + 211, 234, 100, 115, 230, 47, 80, 44, 152, 166, 62, 50, 8, 13, 86, 175, + 28, + ]; + #[rustfmt::skip] + let message_hash: [u8; 32] = [ + 162, 28, 244, 179, 96, 76, 244, 178, 188, 83, 230, 248, 143, 106, 77, 117, + 239, 95, 244, 171, 65, 95, 62, 153, 174, 166, 182, 28, 130, 73, 196, 208 + ]; + #[rustfmt::skip] + const EXPECTED_COMPRESSED_PUBLIC_KEY: [u8; 33] = [ + 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, + 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, + 152, + ]; + let mut params = vec![]; + params.extend_from_slice(&signature); + params.extend_from_slice(&message_hash); + assert!(params.len() == 65 + 32); + let result = builder::bare_call(addr.clone()).data(params).build_and_unwrap_result(); + assert!(!result.did_revert()); + assert_eq!(result.data, EXPECTED_COMPRESSED_PUBLIC_KEY); + }) + } + + #[test] + fn bare_instantiate_returns_events() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let result = builder::bare_instantiate(Code::Upload(wasm)) + .value(min_balance * 100) + .collect_events(CollectEvents::UnsafeCollect) + .build(); + + let events = result.events.unwrap(); + assert!(!events.is_empty()); + assert_eq!(events, System::events()); + }); + } + + #[test] + fn bare_instantiate_does_not_return_events() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let result = + builder::bare_instantiate(Code::Upload(wasm)).value(min_balance * 100).build(); + + let events = result.events; + assert!(!System::events().is_empty()); + assert!(events.is_none()); + }); + } + + #[test] + fn bare_call_returns_events() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let result = builder::bare_call(addr.clone()) + .collect_events(CollectEvents::UnsafeCollect) + .build(); + + let events = result.events.unwrap(); + assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); + assert!(!events.is_empty()); + assert_eq!(events, System::events()); + }); + } + + #[test] + fn bare_call_does_not_return_events() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let result = builder::bare_call(addr.clone()).build(); + + let events = result.events; + assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); + assert!(!System::events().is_empty()); + assert!(events.is_none()); + }); + } + + #[test] + fn sr25519_verify() { + let (wasm, _code_hash) = compile_module::("sr25519_verify").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the sr25519_verify contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_account_id(); + + let call_with = |message: &[u8; 11]| { + // Alice's signature for "hello world" + #[rustfmt::skip] + let signature: [u8; 64] = [ + 184, 49, 74, 238, 78, 165, 102, 252, 22, 92, 156, 176, 124, 118, 168, 116, 247, + 99, 0, 94, 2, 45, 9, 170, 73, 222, 182, 74, 60, 32, 75, 64, 98, 174, 69, 55, 83, + 85, 180, 98, 208, 75, 231, 57, 205, 62, 4, 105, 26, 136, 172, 17, 123, 99, 90, 255, + 228, 54, 115, 63, 30, 207, 205, 131, + ]; + + // Alice's public key + #[rustfmt::skip] + let public_key: [u8; 32] = [ + 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, + 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, + ]; + + let mut params = vec![]; + params.extend_from_slice(&signature); + params.extend_from_slice(&public_key); + params.extend_from_slice(message); + + builder::bare_call(addr.clone()).data(params).build_and_unwrap_result() + }; + + // verification should succeed for "hello world" + assert_return_code!(call_with(&b"hello world"), RuntimeReturnCode::Success); + + // verification should fail for other messages + assert_return_code!(call_with(&b"hello worlD"), RuntimeReturnCode::Sr25519VerifyFailed); + }); + } + + #[test] + fn failed_deposit_charge_should_roll_back_call() { + let (wasm_caller, _) = compile_module::("call_runtime_and_call").unwrap(); + let (wasm_callee, _) = compile_module::("store_call").unwrap(); + const ED: u64 = 200; + + let execute = || { + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate both contracts. + let addr_caller = builder::bare_instantiate(Code::Upload(wasm_caller.clone())) + .build_and_unwrap_account_id(); + let addr_callee = builder::bare_instantiate(Code::Upload(wasm_callee.clone())) + .build_and_unwrap_account_id(); + + // Give caller proxy access to Alice. + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(ALICE), + addr_caller.clone(), + (), + 0 + )); + + // Create a Proxy call that will attempt to transfer away Alice's balance. + let transfer_call = + Box::new(RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { + dest: CHARLIE, + value: pallet_balances::Pallet::::free_balance(&ALICE) - 2 * ED, + })); + + // Wrap the transfer call in a proxy call. + let transfer_proxy_call = RuntimeCall::Proxy(pallet_proxy::Call::proxy { + real: ALICE, + force_proxy_type: Some(()), + call: transfer_call, + }); + + let data = ( + (ED - DepositPerItem::get()) as u32, // storage length + addr_callee, + transfer_proxy_call, + ); + + builder::call(addr_caller).data(data.encode()).build() + }) + }; + + // With a low enough deposit per byte, the call should succeed. + let result = execute().unwrap(); + + // Bump the deposit per byte to a high value to trigger a FundsUnavailable error. + DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 20); + assert_err_with_weight!(execute(), TokenError::FundsUnavailable, result.actual_weight); + } + + #[test] + fn upload_code_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert!(!PristineCode::::contains_key(&code_hash)); + + assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); + + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE + }), + topics: vec![], + },] + ); + }); + } + + #[test] + fn upload_code_limit_too_low() { + let (wasm, _code_hash) = compile_module::("dummy").unwrap(); + let deposit_expected = expected_deposit(wasm.len()); + let deposit_insufficient = deposit_expected.saturating_sub(1); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, deposit_insufficient,), + >::StorageDepositLimitExhausted, + ); + + assert_eq!(System::events(), vec![]); + }); + } + + #[test] + fn upload_code_not_enough_balance() { + let (wasm, _code_hash) = compile_module::("dummy").unwrap(); + let deposit_expected = expected_deposit(wasm.len()); + let deposit_insufficient = deposit_expected.saturating_sub(1); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, deposit_insufficient); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,), + >::StorageDepositNotEnoughFunds, + ); + + assert_eq!(System::events(), vec![]); + }); + } + + #[test] + fn remove_code_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); + + assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeRemoved { + code_hash, + deposit_released: deposit_expected, + remover: ALICE + }), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn remove_code_wrong_origin() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash), + sp_runtime::traits::BadOrigin, + ); + + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE + }), + topics: vec![], + },] + ); + }); + } + + #[test] + fn remove_code_in_use() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + assert_ok!(builder::instantiate_with_code(wasm).build()); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + >::CodeInUse, + ); + + assert_eq!(System::events(), vec![]); + }); + } + + #[test] + fn remove_code_not_found() { + let (_wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + >::CodeNotFound, + ); + + assert_eq!(System::events(), vec![]); + }); + } + + #[test] + fn instantiate_with_zero_balance_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Drop previous events + initialize_block(2); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); + + // Make sure the account exists even though no free balance was send + assert_eq!(::Currency::free_balance(&addr), min_balance); + assert_eq!( + ::Currency::total_balance(&addr), + min_balance + test_utils::contract_info_storage_deposit(&addr) + ); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE, + contract: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE, + to: addr.clone(), + amount: test_utils::contract_info_storage_deposit(&addr), + } + ), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn instantiate_with_below_existential_deposit_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let value = 50; + + // Drop previous events + initialize_block(2); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(value) + .build_and_unwrap_account_id(); + + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); + // Make sure the account exists even though not enough free balance was send + assert_eq!(::Currency::free_balance(&addr), min_balance + value); + assert_eq!( + ::Currency::total_balance(&addr), + min_balance + value + test_utils::contract_info_storage_deposit(&addr) + ); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: 50, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE, + contract: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE, + to: addr.clone(), + amount: test_utils::contract_info_storage_deposit(&addr), + } + ), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn storage_deposit_works() { + let (wasm, _code_hash) = compile_module::("multi_store").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + let mut deposit = test_utils::contract_info_storage_deposit(&addr); + + // Drop previous events + initialize_block(2); + + // Create storage + assert_ok!(builder::call(addr.clone()).value(42).data((50u32, 20u32).encode()).build()); + // 4 is for creating 2 storage items + let charged0 = 4 + 50 + 20; + deposit += charged0; + assert_eq!(get_contract(&addr).total_deposit(), deposit); + + // Add more storage (but also remove some) + assert_ok!(builder::call(addr.clone()).data((100u32, 10u32).encode()).build()); + let charged1 = 50 - 10; + deposit += charged1; + assert_eq!(get_contract(&addr).total_deposit(), deposit); + + // Remove more storage (but also add some) + assert_ok!(builder::call(addr.clone()).data((10u32, 20u32).encode()).build()); + // -1 for numeric instability + let refunded0 = 90 - 10 - 1; + deposit -= refunded0; + assert_eq!(get_contract(&addr).total_deposit(), deposit); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: 42, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE, + to: addr.clone(), + amount: charged0, + } + ), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE, + to: addr.clone(), + amount: charged1, + } + ), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndReleased { + from: addr.clone(), + to: ALICE, + amount: refunded0, + } + ), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn storage_deposit_callee_works() { + let (wasm_caller, _code_hash_caller) = compile_module::("call").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Create both contracts: Constructors do nothing. + let addr_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + assert_ok!(builder::call(addr_caller).data((100u32, &addr_callee).encode()).build()); + + let callee = get_contract(&addr_callee); + let deposit = DepositPerByte::get() * 100 + DepositPerItem::get() * 1; + + assert_eq!(test_utils::get_balance(&addr_callee), min_balance); + assert_eq!( + callee.total_deposit(), + deposit + test_utils::contract_info_storage_deposit(&addr_callee) + ); + }); + } + + #[test] + fn set_code_extrinsic() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + let (new_wasm, new_code_hash) = compile_module::("crypto_hashes").unwrap(); + + assert_ne!(code_hash, new_code_hash); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + new_wasm, + deposit_limit::(), + )); + + // Drop previous events + initialize_block(2); + + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + + // only root can execute this extrinsic + assert_noop!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), addr.clone(), new_code_hash), + sp_runtime::traits::BadOrigin, + ); + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![]); + + // contract must exist + assert_noop!( + Contracts::set_code(RuntimeOrigin::root(), BOB, new_code_hash), + >::ContractNotFound, + ); + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![]); + + // new code hash must exist + assert_noop!( + Contracts::set_code(RuntimeOrigin::root(), addr.clone(), Default::default()), + >::CodeNotFound, + ); + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![]); + + // successful call + assert_ok!(Contracts::set_code(RuntimeOrigin::root(), addr.clone(), new_code_hash)); + assert_eq!(get_contract(&addr).code_hash, new_code_hash); + assert_refcount!(&code_hash, 0); + assert_refcount!(&new_code_hash, 1); + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(pallet_revive::Event::ContractCodeUpdated { + contract: addr.clone(), + new_code_hash, + old_code_hash: code_hash, + }), + topics: vec![], + },] + ); + }); + } + + #[test] + fn slash_cannot_kill_account() { + let (wasm, _code_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let value = 700; + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(value) + .build_and_unwrap_account_id(); + + // Drop previous events + initialize_block(2); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + + assert_eq!( + test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &addr), + info_deposit + ); + + assert_eq!( + ::Currency::total_balance(&addr), + info_deposit + value + min_balance + ); + + // Try to destroy the account of the contract by slashing the total balance. + // The account does not get destroyed because slashing only affects the balance held + // under certain `reason`. Slashing can for example happen if the contract takes part + // in staking. + let _ = ::Currency::slash( + &HoldReason::StorageDepositReserve.into(), + &addr, + ::Currency::total_balance(&addr), + ); + + // Slashing only removed the balance held. + assert_eq!(::Currency::total_balance(&addr), value + min_balance); + }); + } + + #[test] + fn contract_reverted() { + let (wasm, code_hash) = compile_module::("return_with_data").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let flags = ReturnFlags::REVERT; + let buffer = [4u8, 8, 15, 16, 23, 42]; + let input = (flags.bits(), buffer).encode(); + + // We just upload the code for later use + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + deposit_limit::(), + )); + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).data(input.clone()).build(), + >::ContractReverted, + ); + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + builder::instantiate_with_code(wasm).data(input.clone()).build(), + >::ContractReverted, + ); + + // Calling directly: revert leads to success but the flags indicate the error + // This is just a different way of transporting the error that allows the read out + // the `data` which is only there on success. Obviously, the contract isn't + // instantiated. + let result = builder::bare_instantiate(Code::Existing(code_hash)) + .data(input.clone()) + .build_and_unwrap_result(); + assert_eq!(result.result.flags, flags); + assert_eq!(result.result.data, buffer); + assert!(!>::contains_key(result.account_id)); + + // Pass empty flags and therefore successfully instantiate the contract for later use. + let addr = builder::bare_instantiate(Code::Existing(code_hash)) + .data(ReturnFlags::empty().bits().encode()) + .build_and_unwrap_account_id(); + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + builder::call(addr.clone()).data(input.clone()).build(), + >::ContractReverted, + ); + + // Calling directly: revert leads to success but the flags indicate the error + let result = builder::bare_call(addr.clone()).data(input).build_and_unwrap_result(); + assert_eq!(result.flags, flags); + assert_eq!(result.data, buffer); + }); + } + + #[test] + fn set_code_hash() { + let (wasm, code_hash) = compile_module::("set_code_hash").unwrap(); + let (new_wasm, new_code_hash) = + compile_module::("new_set_code_hash_contract").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the 'caller' + let contract_addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(300_000) + .build_and_unwrap_account_id(); + // upload new code + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + new_wasm.clone(), + deposit_limit::(), + )); + + System::reset_events(); + + // First call sets new code_hash and returns 1 + let result = builder::bare_call(contract_addr.clone()) + .data(new_code_hash.as_ref().to_vec()) + .debug(DebugInfo::UnsafeDebug) + .build_and_unwrap_result(); + assert_return_code!(result, 1); + + // Second calls new contract code that returns 2 + let result = builder::bare_call(contract_addr.clone()) + .debug(DebugInfo::UnsafeDebug) + .build_and_unwrap_result(); + assert_return_code!(result, 2); + + // Checking for the last event only + assert_eq!( + &System::events(), + &[ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::ContractCodeUpdated { + contract: contract_addr.clone(), + new_code_hash, + old_code_hash: code_hash, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: contract_addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: contract_addr.clone(), + }), + topics: vec![], + }, + ], + ); + }); + } + + #[test] + fn storage_deposit_limit_is_enforced() { + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Setting insufficient storage_deposit should fail. + assert_err!( + builder::bare_instantiate(Code::Upload(wasm.clone())) + // expected deposit is 2 * ed + 3 for the call + .storage_deposit_limit((2 * min_balance + 3 - 1).into()) + .build() + .result, + >::StorageDepositLimitExhausted, + ); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + // Check that the BOB contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!( + ::Currency::total_balance(&addr), + info_deposit + min_balance + ); + + // Create 1 byte of storage with a price of per byte, + // setting insufficient deposit limit, as it requires 3 Balance: + // 2 for the item added + 1 for the new storage item. + assert_err_ignore_postinfo!( + builder::call(addr.clone()) + .storage_deposit_limit(2) + .data(1u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // Create 1 byte of storage, should cost 3 Balance: + // 2 for the item added + 1 for the new storage item. + // Should pass as it fallbacks to DefaultDepositLimit. + assert_ok!(builder::call(addr.clone()) + .storage_deposit_limit(3) + .data(1u32.to_le_bytes().to_vec()) + .build()); + + // Use 4 more bytes of the storage for the same item, which requires 4 Balance. + // Should fail as DefaultDepositLimit is 3 and hence isn't enough. + assert_err_ignore_postinfo!( + builder::call(addr.clone()) + .storage_deposit_limit(3) + .data(5u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + }); + } + + #[test] + fn deposit_limit_in_nested_calls() { + let (wasm_caller, _code_hash_caller) = + compile_module::("create_storage_and_call").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let addr_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + // Create 100 bytes of storage with a price of per byte + // This is 100 Balance + 2 Balance for the item + assert_ok!(builder::call(addr_callee.clone()) + .storage_deposit_limit(102) + .data(100u32.to_le_bytes().to_vec()) + .build()); + + // We do not remove any storage but add a storage item of 12 bytes in the caller + // contract. This would cost 12 + 2 = 14 Balance. + // The nested call doesn't get a special limit, which is set by passing 0 to it. + // This should fail as the specified parent's limit is less than the cost: 13 < + // 14. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .storage_deposit_limit(13) + .data((100u32, &addr_callee, 0u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // Now we specify the parent's limit high enough to cover the caller's storage + // additions. However, we use a single byte more in the callee, hence the storage + // deposit should be 15 Balance. + // The nested call doesn't get a special limit, which is set by passing 0 to it. + // This should fail as the specified parent's limit is less than the cost: 14 + // < 15. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .storage_deposit_limit(14) + .data((101u32, &addr_callee, 0u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // Now we specify the parent's limit high enough to cover both the caller's and callee's + // storage additions. However, we set a special deposit limit of 1 Balance for the + // nested call. This should fail as callee adds up 2 bytes to the storage, meaning + // that the nested call should have a deposit limit of at least 2 Balance. The + // sub-call should be rolled back, which is covered by the next test case. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .storage_deposit_limit(16) + .data((102u32, &addr_callee, 1u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // Refund in the callee contract but not enough to cover the 14 Balance required by the + // caller. Note that if previous sub-call wouldn't roll back, this call would pass + // making the test case fail. We don't set a special limit for the nested call here. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .storage_deposit_limit(0) + .data((87u32, &addr_callee, 0u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + let _ = ::Currency::set_balance(&ALICE, 511); + + // Require more than the sender's balance. + // We don't set a special limit for the nested call. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .data((512u32, &addr_callee, 1u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // Same as above but allow for the additional deposit of 1 Balance in parent. + // We set the special deposit limit of 1 Balance for the nested call, which isn't + // enforced as callee frees up storage. This should pass. + assert_ok!(builder::call(addr_caller.clone()) + .storage_deposit_limit(1) + .data((87u32, &addr_callee, 1u64).encode()) + .build()); + }); + } + + #[test] + fn deposit_limit_in_nested_instantiate() { + let (wasm_caller, _code_hash_caller) = + compile_module::("create_storage_and_instantiate").unwrap(); + let (wasm_callee, code_hash_callee) = compile_module::("store_deploy").unwrap(); + const ED: u64 = 5; + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&BOB, 1_000_000); + // Create caller contract + let addr_caller = builder::bare_instantiate(Code::Upload(wasm_caller)) + .value(10_000u64) // this balance is later passed to the deployed contract + .build_and_unwrap_account_id(); + // Deploy a contract to get its occupied storage size + let addr = builder::bare_instantiate(Code::Upload(wasm_callee)) + .data(vec![0, 0, 0, 0]) + .build_and_unwrap_account_id(); + + let callee_info_len = ContractInfoOf::::get(&addr).unwrap().encoded_size() as u64; + + // We don't set a special deposit limit for the nested instantiation. + // + // The deposit limit set for the parent is insufficient for the instantiation, which + // requires: + // - callee_info_len + 2 for storing the new contract info, + // - ED for deployed contract account, + // - 2 for the storage item of 0 bytes being created in the callee constructor + // or (callee_info_len + 2 + ED + 2) Balance in total. + // + // Provided the limit is set to be 1 Balance less, + // this call should fail on the return from the caller contract. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 1) + .data((0u32, &code_hash_callee, 0u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + // The charges made on instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Now we give enough limit for the instantiation itself, but require for 1 more storage + // byte in the constructor. Hence +1 Balance to the limit is needed. This should fail on + // the return from constructor. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 2) + .data((1u32, &code_hash_callee, 0u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Now we set enough limit in parent call, but an insufficient limit for child + // instantiate. This should fail during the charging for the instantiation in + // `RawMeter::charge_instantiate()` + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 2) + .data((0u32, &code_hash_callee, callee_info_len + 2 + ED + 1).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Same as above but requires for single added storage + // item of 1 byte to be covered by the limit, which implies 3 more Balance. + // Now we set enough limit for the parent call, but insufficient limit for child + // instantiate. This should fail right after the constructor execution. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 3) // enough parent limit + .data((1u32, &code_hash_callee, callee_info_len + 2 + ED + 2).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Set enough deposit limit for the child instantiate. This should succeed. + let result = builder::bare_call(addr_caller.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 4) + .data((1u32, &code_hash_callee, callee_info_len + 2 + ED + 3).encode()) + .build(); + + let returned = result.result.unwrap(); + // All balance of the caller except ED has been transferred to the callee. + // No deposit has been taken from it. + assert_eq!(::Currency::free_balance(&addr_caller), ED); + // Get address of the deployed contract. + let addr_callee = AccountId32::from_slice(&returned.data[0..32]).unwrap(); + // 10_000 should be sent to callee from the caller contract, plus ED to be sent from the + // origin. + assert_eq!(::Currency::free_balance(&addr_callee), 10_000 + ED); + // The origin should be charged with: + // - callee instantiation deposit = (callee_info_len + 2) + // - callee account ED + // - for writing an item of 1 byte to storage = 3 Balance + assert_eq!( + ::Currency::free_balance(&BOB), + 1_000_000 - (callee_info_len + 2 + ED + 3) + ); + // Check that deposit due to be charged still includes these 3 Balance + assert_eq!(result.storage_deposit.charge_or_zero(), (callee_info_len + 2 + ED + 3)) + }); + } + + #[test] + fn deposit_limit_honors_liquidity_restrictions() { + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let bobs_balance = 1_000; + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&BOB, bobs_balance); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!( + ::Currency::total_balance(&addr), + info_deposit + min_balance + ); + + // check that the hold is honored + ::Currency::hold( + &HoldReason::CodeUploadDepositReserve.into(), + &BOB, + bobs_balance - min_balance, + ) + .unwrap(); + assert_err_ignore_postinfo!( + builder::call(addr.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(10_000) + .data(100u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + assert_eq!(::Currency::free_balance(&BOB), min_balance); + }); + } + + #[test] + fn deposit_limit_honors_existential_deposit() { + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&BOB, 300); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!( + ::Currency::total_balance(&addr), + min_balance + info_deposit + ); + + // check that the deposit can't bring the account below the existential deposit + assert_err_ignore_postinfo!( + builder::call(addr.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(10_000) + .data(100u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + assert_eq!(::Currency::free_balance(&BOB), 300); + }); + } + + #[test] + fn deposit_limit_honors_min_leftover() { + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&BOB, 1_000); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + + // Check that the contract has been instantiated and has the minimum balance and the + // storage deposit + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!( + ::Currency::total_balance(&addr), + info_deposit + min_balance + ); + + // check that the minimum leftover (value send) is considered + // given the minimum deposit of 200 sending 750 will only leave + // 50 for the storage deposit. Which is not enough to store the 50 bytes + // as we also need 2 bytes for the item + assert_err_ignore_postinfo!( + builder::call(addr.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .value(750) + .storage_deposit_limit(10_000) + .data(50u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + assert_eq!(::Currency::free_balance(&BOB), 1_000); + }); + } + + #[test] + fn locking_delegate_dependency_works() { + // set hash lock up deposit to 30%, to test deposit calculation. + CODE_HASH_LOCKUP_DEPOSIT_PERCENT.with(|c| *c.borrow_mut() = Perbill::from_percent(30)); + + let (wasm_caller, self_code_hash) = + compile_module::("locking_delegate_dependency").unwrap(); + let callee_codes: Vec<_> = + (0..limits::DELEGATE_DEPENDENCIES + 1).map(|idx| dummy_unique(idx)).collect(); + let callee_hashes: Vec<_> = callee_codes + .iter() + .map(|c| ::Hashing::hash(c)) + .collect(); + + // Define inputs with various actions to test locking / unlocking delegate_dependencies. + // See the contract for more details. + let noop_input = (0u32, callee_hashes[0]); + let lock_delegate_dependency_input = (1u32, callee_hashes[0]); + let unlock_delegate_dependency_input = (2u32, callee_hashes[0]); + let terminate_input = (3u32, callee_hashes[0]); + + // Instantiate the caller contract with the given input. + let instantiate = |input: &(u32, H256)| { + builder::bare_instantiate(Code::Upload(wasm_caller.clone())) + .data(input.encode()) + .build() + }; + + // Call contract with the given input. + let call = |addr_caller: &AccountId32, input: &(u32, H256)| { + builder::bare_call(addr_caller.clone()).data(input.encode()).build() + }; + const ED: u64 = 2000; + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + + // Instantiate with lock_delegate_dependency should fail since the code is not yet on + // chain. + assert_err!( + instantiate(&lock_delegate_dependency_input).result, + Error::::CodeNotFound + ); + + // Upload all the delegated codes (they all have the same size) + let mut deposit = Default::default(); + for code in callee_codes.iter() { + let CodeUploadReturnValue { deposit: deposit_per_code, .. } = + Contracts::bare_upload_code( + RuntimeOrigin::signed(ALICE), + code.clone(), + deposit_limit::(), + ) + .unwrap(); + deposit = deposit_per_code; + } + + // Instantiate should now work. + let addr_caller = + instantiate(&lock_delegate_dependency_input).result.unwrap().account_id; + + // There should be a dependency and a deposit. + let contract = test_utils::get_contract(&addr_caller); + + let dependency_deposit = &CodeHashLockupDepositPercent::get().mul_ceil(deposit); + assert_eq!( + contract.delegate_dependencies().get(&callee_hashes[0]), + Some(dependency_deposit) + ); + assert_eq!( + test_utils::get_balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &addr_caller + ), + dependency_deposit + contract.storage_base_deposit() + ); + + // Removing the code should fail, since we have added a dependency. + assert_err!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), callee_hashes[0]), + >::CodeInUse + ); + + // Locking an already existing dependency should fail. + assert_err!( + call(&addr_caller, &lock_delegate_dependency_input).result, + Error::::DelegateDependencyAlreadyExists + ); + + // Locking self should fail. + assert_err!( + call(&addr_caller, &(1u32, self_code_hash)).result, + Error::::CannotAddSelfAsDelegateDependency + ); + + // Locking more than the maximum allowed delegate_dependencies should fail. + for hash in &callee_hashes[1..callee_hashes.len() - 1] { + call(&addr_caller, &(1u32, *hash)).result.unwrap(); + } + assert_err!( + call(&addr_caller, &(1u32, *callee_hashes.last().unwrap())).result, + Error::::MaxDelegateDependenciesReached + ); + + // Unlocking all dependency should work. + for hash in &callee_hashes[..callee_hashes.len() - 1] { + call(&addr_caller, &(2u32, *hash)).result.unwrap(); + } + + // Dependency should be removed, and deposit should be returned. + let contract = test_utils::get_contract(&addr_caller); + assert!(contract.delegate_dependencies().is_empty()); + assert_eq!( + test_utils::get_balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &addr_caller + ), + contract.storage_base_deposit() + ); + + // Removing a nonexistent dependency should fail. + assert_err!( + call(&addr_caller, &unlock_delegate_dependency_input).result, + Error::::DelegateDependencyNotFound + ); + + // Locking a dependency with a storage limit too low should fail. + assert_err!( + builder::bare_call(addr_caller.clone()) + .storage_deposit_limit(dependency_deposit - 1) + .data(lock_delegate_dependency_input.encode()) + .build() + .result, + Error::::StorageDepositLimitExhausted + ); + + // Since we unlocked the dependency we should now be able to remove the code. + assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), callee_hashes[0])); + + // Calling should fail since the delegated contract is not on chain anymore. + assert_err!(call(&addr_caller, &noop_input).result, Error::::ContractTrapped); + + // Add the dependency back. + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + callee_codes[0].clone(), + deposit_limit::(), + ) + .unwrap(); + call(&addr_caller, &lock_delegate_dependency_input).result.unwrap(); + + // Call terminate should work, and return the deposit. + let balance_before = test_utils::get_balance(&ALICE); + assert_ok!(call(&addr_caller, &terminate_input).result); + assert_eq!( + test_utils::get_balance(&ALICE), + ED + balance_before + contract.storage_base_deposit() + dependency_deposit + ); + + // Terminate should also remove the dependency, so we can remove the code. + assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), callee_hashes[0])); + }); + } + + #[test] + fn native_dependency_deposit_works() { + let (wasm, code_hash) = compile_module::("set_code_hash").unwrap(); + let (dummy_wasm, dummy_code_hash) = compile_module::("dummy").unwrap(); + + // Set hash lock up deposit to 30%, to test deposit calculation. + CODE_HASH_LOCKUP_DEPOSIT_PERCENT.with(|c| *c.borrow_mut() = Perbill::from_percent(30)); + + // Test with both existing and uploaded code + for code in [Code::Upload(wasm.clone()), Code::Existing(code_hash)] { + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + let lockup_deposit_percent = CodeHashLockupDepositPercent::get(); + + // Upload the dummy contract, + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + dummy_wasm.clone(), + deposit_limit::(), + ) + .unwrap(); + + // Upload `set_code_hash` contracts if using Code::Existing. + let add_upload_deposit = match code { + Code::Existing(_) => { + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + deposit_limit::(), + ) + .unwrap(); + false + }, + Code::Upload(_) => true, + }; + + // Instantiate the set_code_hash contract. + let res = builder::bare_instantiate(code).build(); + + let addr = res.result.unwrap().account_id; + let base_deposit = test_utils::contract_info_storage_deposit(&addr); + let upload_deposit = test_utils::get_code_deposit(&code_hash); + let extra_deposit = add_upload_deposit.then(|| upload_deposit).unwrap_or_default(); + + // Check initial storage_deposit + // The base deposit should be: contract_info_storage_deposit + 30% * deposit + let deposit = + extra_deposit + base_deposit + lockup_deposit_percent.mul_ceil(upload_deposit); + + assert_eq!( + res.storage_deposit.charge_or_zero(), + deposit + Contracts::min_balance() + ); + + // call set_code_hash + builder::bare_call(addr.clone()) + .data(dummy_code_hash.encode()) + .build_and_unwrap_result(); + + // Check updated storage_deposit + let code_deposit = test_utils::get_code_deposit(&dummy_code_hash); + let deposit = base_deposit + lockup_deposit_percent.mul_ceil(code_deposit); + assert_eq!(test_utils::get_contract(&addr).storage_base_deposit(), deposit); + + assert_eq!( + test_utils::get_balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &addr + ), + deposit + ); + }); + } + } + + #[test] + fn root_cannot_upload_code() { + let (wasm, _) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::upload_code(RuntimeOrigin::root(), wasm, deposit_limit::()), + DispatchError::BadOrigin, + ); + }); + } + + #[test] + fn root_cannot_remove_code() { + let (_, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::remove_code(RuntimeOrigin::root(), code_hash), + DispatchError::BadOrigin, + ); + }); + } + + #[test] + fn signed_cannot_set_code() { + let (_, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB, code_hash), + DispatchError::BadOrigin, + ); + }); + } + + #[test] + fn none_cannot_call_code() { + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + builder::call(BOB).origin(RuntimeOrigin::none()).build(), + DispatchError::BadOrigin, + ); + }); + } + + #[test] + fn root_can_call() { + let (wasm, _) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + // Call the contract. + assert_ok!(builder::call(addr.clone()).origin(RuntimeOrigin::root()).build()); + }); + } + + #[test] + fn root_cannot_instantiate_with_code() { + let (wasm, _) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + builder::instantiate_with_code(wasm).origin(RuntimeOrigin::root()).build(), + DispatchError::BadOrigin + ); + }); + } + + #[test] + fn root_cannot_instantiate() { + let (_, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).origin(RuntimeOrigin::root()).build(), + DispatchError::BadOrigin + ); + }); + } + + #[test] + fn only_upload_origin_can_upload() { + let (wasm, _) = compile_module::("dummy").unwrap(); + UploadAccount::set(Some(ALICE)); + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + let _ = Balances::set_balance(&BOB, 1_000_000); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::root(), + wasm.clone(), + deposit_limit::(), + ), + DispatchError::BadOrigin + ); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(BOB), + wasm.clone(), + deposit_limit::(), + ), + DispatchError::BadOrigin + ); + + // Only alice is allowed to upload contract code. + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + deposit_limit::(), + )); + }); + } + + #[test] + fn only_instantiation_origin_can_instantiate() { + let (code, code_hash) = compile_module::("dummy").unwrap(); + InstantiateAccount::set(Some(ALICE)); + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + let _ = Balances::set_balance(&BOB, 1_000_000); + + assert_err_ignore_postinfo!( + builder::instantiate_with_code(code.clone()) + .origin(RuntimeOrigin::root()) + .build(), + DispatchError::BadOrigin + ); + + assert_err_ignore_postinfo!( + builder::instantiate_with_code(code.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .build(), + DispatchError::BadOrigin + ); + + // Only Alice can instantiate + assert_ok!(builder::instantiate_with_code(code).build()); + + // Bob cannot instantiate with either `instantiate_with_code` or `instantiate`. + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).origin(RuntimeOrigin::signed(BOB)).build(), + DispatchError::BadOrigin + ); + }); + } + + #[test] + fn balance_api_returns_free_balance() { + let (wasm, _code_hash) = compile_module::("balance").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the BOB contract without any extra balance. + let addr = builder::bare_instantiate(Code::Upload(wasm.to_vec())) + .build_and_unwrap_account_id(); + + let value = 0; + // Call BOB which makes it call the balance runtime API. + // The contract code asserts that the returned balance is 0. + assert_ok!(builder::call(addr.clone()).value(value).build()); + + let value = 1; + // Calling with value will trap the contract. + assert_err_ignore_postinfo!( + builder::call(addr.clone()).value(value).build(), + >::ContractTrapped + ); + }); + } + + #[test] + fn gas_consumed_is_linear_for_nested_calls() { + let (code, _code_hash) = compile_module::("recurse").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let addr = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_account_id(); + + let [gas_0, gas_1, gas_2, gas_max] = { + [0u32, 1u32, 2u32, limits::CALL_STACK_DEPTH] + .iter() + .map(|i| { + let result = builder::bare_call(addr.clone()).data(i.encode()).build(); + assert_ok!(result.result); + result.gas_consumed + }) + .collect::>() + .try_into() + .unwrap() + }; + + let gas_per_recursion = gas_2.checked_sub(&gas_1).unwrap(); + assert_eq!(gas_max, gas_0 + gas_per_recursion * limits::CALL_STACK_DEPTH as u64); + }); + } + + #[test] + fn read_only_call_cannot_store() { + let (wasm_caller, _code_hash_caller) = compile_module::("read_only_call").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let addr_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + // Read-only call fails when modifying storage. + assert_err_ignore_postinfo!( + builder::call(addr_caller).data((&addr_callee, 100u32).encode()).build(), + >::ContractTrapped + ); + }); + } + + #[test] + fn read_only_call_cannot_transfer() { + let (wasm_caller, _code_hash_caller) = + compile_module::("call_with_flags_and_value").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let addr_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + // Read-only call fails when a non-zero value is set. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .data( + (addr_callee, pallet_revive_uapi::CallFlags::READ_ONLY.bits(), 100u64) + .encode() + ) + .build(), + >::StateChangeDenied + ); + }); + } + + #[test] + fn read_only_subsequent_call_cannot_store() { + let (wasm_read_only_caller, _code_hash_caller) = + compile_module::("read_only_call").unwrap(); + let (wasm_caller, _code_hash_caller) = + compile_module::("call_with_flags_and_value").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create contracts: Constructors do nothing. + let addr_caller = builder::bare_instantiate(Code::Upload(wasm_read_only_caller)) + .build_and_unwrap_account_id(); + let addr_subsequent_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + // Subsequent call input. + let input = (&addr_callee, pallet_revive_uapi::CallFlags::empty().bits(), 0u64, 100u32); + + // Read-only call fails when modifying storage. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .data((&addr_subsequent_caller, input).encode()) + .build(), + >::ContractTrapped + ); + }); + } + + #[test] + fn read_only_call_works() { + let (wasm_caller, _code_hash_caller) = compile_module::("read_only_call").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let addr_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + assert_ok!(builder::call(addr_caller.clone()).data(addr_callee.encode()).build()); + }); + } +} diff --git a/substrate/frame/revive/src/tests/pallet_dummy.rs b/substrate/frame/revive/src/tests/pallet_dummy.rs new file mode 100644 index 000000000000..2af8475d17ed --- /dev/null +++ b/substrate/frame/revive/src/tests/pallet_dummy.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub use pallet::*; + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::{ + dispatch::{Pays, PostDispatchInfo}, + ensure, + pallet_prelude::DispatchResultWithPostInfo, + weights::Weight, + }; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + /// Dummy function that overcharges the predispatch weight, allowing us to test the correct + /// values of [`ContractResult::gas_consumed`] and [`ContractResult::gas_required`] in + /// tests. + #[pallet::call_index(1)] + #[pallet::weight(*pre_charge)] + pub fn overestimate_pre_charge( + origin: OriginFor, + pre_charge: Weight, + actual_weight: Weight, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + ensure!(pre_charge.any_gt(actual_weight), "pre_charge must be > actual_weight"); + Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) + } + } +} diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs new file mode 100644 index 000000000000..166a0a8606a6 --- /dev/null +++ b/substrate/frame/revive/src/tests/test_debug.rs @@ -0,0 +1,243 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate::{ + debug::{CallInterceptor, CallSpan, ExecResult, ExportedFunction, Tracing}, + primitives::ExecReturnValue, + test_utils::*, + AccountIdOf, +}; +use frame_support::traits::Currency; +use std::cell::RefCell; + +#[derive(Clone, PartialEq, Eq, Debug)] +struct DebugFrame { + contract_account: AccountId32, + call: ExportedFunction, + input: Vec, + result: Option>, +} + +thread_local! { + static DEBUG_EXECUTION_TRACE: RefCell> = RefCell::new(Vec::new()); + static INTERCEPTED_ADDRESS: RefCell> = RefCell::new(None); +} + +pub struct TestDebug; +pub struct TestCallSpan { + contract_account: AccountId32, + call: ExportedFunction, + input: Vec, +} + +impl Tracing for TestDebug { + type CallSpan = TestCallSpan; + + fn new_call_span( + contract_account: &AccountIdOf, + entry_point: ExportedFunction, + input_data: &[u8], + ) -> TestCallSpan { + DEBUG_EXECUTION_TRACE.with(|d| { + d.borrow_mut().push(DebugFrame { + contract_account: contract_account.clone(), + call: entry_point, + input: input_data.to_vec(), + result: None, + }) + }); + TestCallSpan { + contract_account: contract_account.clone(), + call: entry_point, + input: input_data.to_vec(), + } + } +} + +impl CallInterceptor for TestDebug { + fn intercept_call( + contract_address: &::AccountId, + _entry_point: ExportedFunction, + _input_data: &[u8], + ) -> Option { + INTERCEPTED_ADDRESS.with(|i| { + if i.borrow().as_ref() == Some(contract_address) { + Some(Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![] })) + } else { + None + } + }) + } +} + +impl CallSpan for TestCallSpan { + fn after_call(self, output: &ExecReturnValue) { + DEBUG_EXECUTION_TRACE.with(|d| { + d.borrow_mut().push(DebugFrame { + contract_account: self.contract_account, + call: self.call, + input: self.input, + result: Some(output.data.clone()), + }) + }); + } +} + +/// We can only run the tests if we have a riscv toolchain installed +#[cfg(feature = "riscv")] +mod run_tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn debugging_works() { + let (wasm_caller, _) = compile_module::("call").unwrap(); + let (wasm_callee, _) = compile_module::("store_call").unwrap(); + + fn current_stack() -> Vec { + DEBUG_EXECUTION_TRACE.with(|stack| stack.borrow().clone()) + } + + fn deploy(wasm: Vec) -> AccountId32 { + Contracts::bare_instantiate( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + deposit_limit::(), + Code::Upload(wasm), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id + } + + fn constructor_frame(contract_account: &AccountId32, after: bool) -> DebugFrame { + DebugFrame { + contract_account: contract_account.clone(), + call: ExportedFunction::Constructor, + input: vec![], + result: if after { Some(vec![]) } else { None }, + } + } + + fn call_frame(contract_account: &AccountId32, args: Vec, after: bool) -> DebugFrame { + DebugFrame { + contract_account: contract_account.clone(), + call: ExportedFunction::Call, + input: args, + result: if after { Some(vec![]) } else { None }, + } + } + + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + assert_eq!(current_stack(), vec![]); + + let addr_caller = deploy(wasm_caller); + let addr_callee = deploy(wasm_callee); + + assert_eq!( + current_stack(), + vec![ + constructor_frame(&addr_caller, false), + constructor_frame(&addr_caller, true), + constructor_frame(&addr_callee, false), + constructor_frame(&addr_callee, true), + ] + ); + + let main_args = (100u32, &addr_callee.clone()).encode(); + let inner_args = (100u32).encode(); + + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr_caller.clone(), + 0, + GAS_LIMIT, + deposit_limit::(), + main_args.clone() + )); + + let stack_top = current_stack()[4..].to_vec(); + assert_eq!( + stack_top, + vec![ + call_frame(&addr_caller, main_args.clone(), false), + call_frame(&addr_callee, inner_args.clone(), false), + call_frame(&addr_callee, inner_args, true), + call_frame(&addr_caller, main_args, true), + ] + ); + }); + } + + #[test] + fn call_interception_works() { + let (wasm, _) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + let account_id = Contracts::bare_instantiate( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + deposit_limit::(), + Code::Upload(wasm), + vec![], + // some salt to ensure that the address of this contract is unique among all tests + vec![0x41, 0x41, 0x41, 0x41], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + // no interception yet + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + account_id.clone(), + 0, + GAS_LIMIT, + deposit_limit::(), + vec![], + )); + + // intercept calls to this contract + INTERCEPTED_ADDRESS.with(|i| *i.borrow_mut() = Some(account_id.clone())); + + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + account_id, + 0, + GAS_LIMIT, + deposit_limit::(), + vec![], + ), + >::ContractReverted, + ); + }); + } +} diff --git a/substrate/frame/revive/src/transient_storage.rs b/substrate/frame/revive/src/transient_storage.rs new file mode 100644 index 000000000000..298e0296fe69 --- /dev/null +++ b/substrate/frame/revive/src/transient_storage.rs @@ -0,0 +1,691 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains routines for accessing and altering a contract transient storage. + +use crate::{ + exec::{AccountIdOf, Key}, + storage::WriteOutcome, + Config, Error, +}; +use codec::Encode; +use core::marker::PhantomData; +use frame_support::DefaultNoBound; +use sp_runtime::{DispatchError, DispatchResult, Saturating}; +use sp_std::{collections::btree_map::BTreeMap, mem, vec::Vec}; + +/// Meter entry tracks transaction allocations. +#[derive(Default, Debug)] +pub struct MeterEntry { + /// Allocations made in the current transaction. + pub amount: u32, + /// Allocations limit in the current transaction. + pub limit: u32, +} + +impl MeterEntry { + /// Create a new entry. + fn new(limit: u32) -> Self { + Self { limit, amount: Default::default() } + } + + /// Check if the allocated amount exceeds the limit. + fn exceeds_limit(&self, amount: u32) -> bool { + self.amount.saturating_add(amount) > self.limit + } + + /// Absorb the allocation amount of the nested entry into the current entry. + fn absorb(&mut self, rhs: Self) { + self.amount.saturating_accrue(rhs.amount) + } +} + +// The storage meter enforces a limit for each transaction, +// which is calculated as free_storage * (1 - 1/16) for each subsequent frame. +#[derive(DefaultNoBound)] +pub struct StorageMeter { + nested_meters: Vec, + root_meter: MeterEntry, + _phantom: PhantomData, +} + +impl StorageMeter { + const STORAGE_FRACTION_DENOMINATOR: u32 = 16; + /// Create a new storage allocation meter. + fn new(memory_limit: u32) -> Self { + Self { root_meter: MeterEntry::new(memory_limit), ..Default::default() } + } + + /// Charge the allocated amount of transaction storage from the meter. + fn charge(&mut self, amount: u32) -> DispatchResult { + let meter = self.current_mut(); + if meter.exceeds_limit(amount) { + return Err(Error::::OutOfTransientStorage.into()); + } + meter.amount.saturating_accrue(amount); + Ok(()) + } + + /// Revert a transaction meter. + fn revert(&mut self) { + self.nested_meters.pop().expect( + "A call to revert a meter must be preceded by a corresponding call to start a meter; + the code within this crate makes sure that this is always the case; qed", + ); + } + + /// Start a transaction meter. + fn start(&mut self) { + let meter = self.current(); + let mut transaction_limit = meter.limit.saturating_sub(meter.amount); + if !self.nested_meters.is_empty() { + // Allow use of (1 - 1/STORAGE_FRACTION_DENOMINATOR) of free storage for subsequent + // calls. + transaction_limit.saturating_reduce( + transaction_limit.saturating_div(Self::STORAGE_FRACTION_DENOMINATOR), + ); + } + + self.nested_meters.push(MeterEntry::new(transaction_limit)); + } + + /// Commit a transaction meter. + fn commit(&mut self) { + let transaction_meter = self.nested_meters.pop().expect( + "A call to commit a meter must be preceded by a corresponding call to start a meter; + the code within this crate makes sure that this is always the case; qed", + ); + self.current_mut().absorb(transaction_meter) + } + + /// The total allocated amount of memory. + #[cfg(test)] + fn total_amount(&self) -> u32 { + self.nested_meters + .iter() + .fold(self.root_meter.amount, |acc, e| acc.saturating_add(e.amount)) + } + + /// A mutable reference to the current meter entry. + pub fn current_mut(&mut self) -> &mut MeterEntry { + self.nested_meters.last_mut().unwrap_or(&mut self.root_meter) + } + + /// A reference to the current meter entry. + pub fn current(&self) -> &MeterEntry { + self.nested_meters.last().unwrap_or(&self.root_meter) + } +} + +/// An entry representing a journal change. +struct JournalEntry { + key: Vec, + prev_value: Option>, +} + +impl JournalEntry { + /// Create a new change. + fn new(key: Vec, prev_value: Option>) -> Self { + Self { key, prev_value } + } + + /// Revert the change. + fn revert(self, storage: &mut Storage) { + storage.write(&self.key, self.prev_value); + } +} + +/// A journal containing transient storage modifications. +struct Journal(Vec); + +impl Journal { + /// Create a new journal. + fn new() -> Self { + Self(Default::default()) + } + + /// Add a change to the journal. + fn push(&mut self, entry: JournalEntry) { + self.0.push(entry); + } + + /// Length of the journal. + fn len(&self) -> usize { + self.0.len() + } + + /// Roll back all journal changes until the chackpoint + fn rollback(&mut self, storage: &mut Storage, checkpoint: usize) { + self.0.drain(checkpoint..).rev().for_each(|entry| entry.revert(storage)); + } +} + +/// Storage for maintaining the current transaction state. +#[derive(Default)] +struct Storage(BTreeMap, Vec>); + +impl Storage { + /// Read the storage entry. + fn read(&self, key: &Vec) -> Option> { + self.0.get(key).cloned() + } + + /// Write the storage entry. + fn write(&mut self, key: &Vec, value: Option>) -> Option> { + if let Some(value) = value { + // Insert storage entry. + self.0.insert(key.clone(), value) + } else { + // Remove storage entry. + self.0.remove(key) + } + } +} + +/// Transient storage behaves almost identically to regular storage but is discarded after each +/// transaction. It consists of a `BTreeMap` for the current state, a journal of all changes, and a +/// list of checkpoints. On entry to the `start_transaction` function, a marker (checkpoint) is +/// added to the list. New values are written to the current state, and the previous value is +/// recorded in the journal (`write`). When the `commit_transaction` function is called, the marker +/// to the journal index (checkpoint) of when that call was entered is discarded. +/// On `rollback_transaction`, all entries are reverted up to the last checkpoint. +pub struct TransientStorage { + // The storage and journal size is limited by the storage meter. + storage: Storage, + journal: Journal, + // The size of the StorageMeter is limited by the stack depth. + meter: StorageMeter, + // The size of the checkpoints is limited by the stack depth. + checkpoints: Vec, +} + +impl TransientStorage { + /// Create new transient storage with the supplied memory limit. + pub fn new(memory_limit: u32) -> Self { + TransientStorage { + storage: Default::default(), + journal: Journal::new(), + checkpoints: Default::default(), + meter: StorageMeter::new(memory_limit), + } + } + + /// Read the storage value. If the entry does not exist, `None` is returned. + pub fn read(&self, account: &AccountIdOf, key: &Key) -> Option> { + self.storage.read(&Self::storage_key(&account.encode(), &key.hash())) + } + + /// Write a value to storage. + /// + /// If the `value` is `None`, then the entry is removed. If `take` is true, + /// a [`WriteOutcome::Taken`] is returned instead of a [`WriteOutcome::Overwritten`]. + /// If the entry did not exist, [`WriteOutcome::New`] is returned. + pub fn write( + &mut self, + account: &AccountIdOf, + key: &Key, + value: Option>, + take: bool, + ) -> Result { + let key = Self::storage_key(&account.encode(), &key.hash()); + let prev_value = self.storage.read(&key); + // Skip if the same value is being set. + if prev_value != value { + // Calculate the allocation size. + if let Some(value) = &value { + // Charge the key, value and journal entry. + // If a new value is written, a new journal entry is created. The previous value is + // moved to the journal along with its key, and the new value is written to + // storage. + let key_len = key.capacity(); + let mut amount = value + .capacity() + .saturating_add(key_len) + .saturating_add(mem::size_of::()); + if prev_value.is_none() { + // Charge a new storage entry. + // If there was no previous value, a new entry is added to storage (BTreeMap) + // containing a Vec for the key and a Vec for the value. The value was already + // included in the amount. + amount.saturating_accrue(key_len.saturating_add(mem::size_of::>())); + } + self.meter.charge(amount as _)?; + } + self.storage.write(&key, value); + // Update the journal. + self.journal.push(JournalEntry::new(key, prev_value.clone())); + } + + Ok(match (take, prev_value) { + (_, None) => WriteOutcome::New, + (false, Some(prev_value)) => WriteOutcome::Overwritten(prev_value.len() as _), + (true, Some(prev_value)) => WriteOutcome::Taken(prev_value), + }) + } + + /// Start a new nested transaction. + /// + /// This allows to either commit or roll back all changes that are made after this call. + /// For every transaction there must be a matching call to either `rollback_transaction` + /// or `commit_transaction`. + pub fn start_transaction(&mut self) { + self.meter.start(); + self.checkpoints.push(self.journal.len()); + } + + /// Rollback the last transaction started by `start_transaction`. + /// + /// Any changes made during that transaction are discarded. + /// + /// # Panics + /// + /// Will panic if there is no open transaction. + pub fn rollback_transaction(&mut self) { + let checkpoint = self + .checkpoints + .pop() + .expect( + "A call to rollback_transaction must be preceded by a corresponding call to start_transaction; + the code within this crate makes sure that this is always the case; qed" + ); + self.meter.revert(); + self.journal.rollback(&mut self.storage, checkpoint); + } + + /// Commit the last transaction started by `start_transaction`. + /// + /// Any changes made during that transaction are committed. + /// + /// # Panics + /// + /// Will panic if there is no open transaction. + pub fn commit_transaction(&mut self) { + self.checkpoints + .pop() + .expect( + "A call to commit_transaction must be preceded by a corresponding call to start_transaction; + the code within this crate makes sure that this is always the case; qed" + ); + self.meter.commit(); + } + + /// The storage allocation meter used for transaction metering. + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub fn meter(&mut self) -> &mut StorageMeter { + return &mut self.meter + } + + fn storage_key(account: &[u8], key: &[u8]) -> Vec { + let mut storage_key = Vec::with_capacity(account.len() + key.len()); + storage_key.extend_from_slice(&account); + storage_key.extend_from_slice(&key); + storage_key + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{test_utils::*, tests::Test, Error}; + use core::u32::MAX; + + // Calculate the allocation size for the given entry. + fn allocation_size(account: &AccountIdOf, key: &Key, value: Option>) -> u32 { + let mut storage: TransientStorage = TransientStorage::::new(MAX); + storage + .write(account, key, value, false) + .expect("Could not write to transient storage."); + storage.meter().current().amount + } + + #[test] + fn read_write_works() { + let mut storage: TransientStorage = TransientStorage::::new(2048); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![2]), true), + Ok(WriteOutcome::New) + ); + assert_eq!( + storage.write(&BOB, &Key::Fix([3; 32]), Some(vec![3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1])); + assert_eq!(storage.read(&ALICE, &Key::Fix([2; 32])), Some(vec![2])); + assert_eq!(storage.read(&BOB, &Key::Fix([3; 32])), Some(vec![3])); + // Overwrite values. + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![4, 5]), false), + Ok(WriteOutcome::Overwritten(1)) + ); + assert_eq!( + storage.write(&BOB, &Key::Fix([3; 32]), Some(vec![6, 7]), true), + Ok(WriteOutcome::Taken(vec![3])) + ); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1])); + assert_eq!(storage.read(&ALICE, &Key::Fix([2; 32])), Some(vec![4, 5])); + assert_eq!(storage.read(&BOB, &Key::Fix([3; 32])), Some(vec![6, 7])); + + // Check for an empty value. + assert_eq!( + storage.write(&BOB, &Key::Fix([3; 32]), Some(vec![]), true), + Ok(WriteOutcome::Taken(vec![6, 7])) + ); + assert_eq!(storage.read(&BOB, &Key::Fix([3; 32])), Some(vec![])); + + assert_eq!( + storage.write(&BOB, &Key::Fix([3; 32]), None, true), + Ok(WriteOutcome::Taken(vec![])) + ); + assert_eq!(storage.read(&BOB, &Key::Fix([3; 32])), None); + } + + #[test] + fn read_write_with_var_sized_keys_works() { + let mut storage = TransientStorage::::new(2048); + assert_eq!( + storage.write( + &ALICE, + &Key::try_from_var([1; 64].to_vec()).unwrap(), + Some(vec![1]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + storage.write( + &BOB, + &Key::try_from_var([2; 64].to_vec()).unwrap(), + Some(vec![2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + storage.read(&ALICE, &Key::try_from_var([1; 64].to_vec()).unwrap()), + Some(vec![1]) + ); + assert_eq!( + storage.read(&BOB, &Key::try_from_var([2; 64].to_vec()).unwrap()), + Some(vec![2, 3]) + ); + // Overwrite values. + assert_eq!( + storage.write( + &ALICE, + &Key::try_from_var([1; 64].to_vec()).unwrap(), + Some(vec![4, 5]), + false + ), + Ok(WriteOutcome::Overwritten(1)) + ); + assert_eq!( + storage.read(&ALICE, &Key::try_from_var([1; 64].to_vec()).unwrap()), + Some(vec![4, 5]) + ); + } + + #[test] + fn rollback_transaction_works() { + let mut storage = TransientStorage::::new(1024); + + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + storage.rollback_transaction(); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), None) + } + + #[test] + fn commit_transaction_works() { + let mut storage = TransientStorage::::new(1024); + + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + storage.commit_transaction(); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1])) + } + + #[test] + fn overwrite_and_commmit_transaction_works() { + let mut storage = TransientStorage::::new(1024); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1, 2]), false), + Ok(WriteOutcome::Overwritten(1)) + ); + storage.commit_transaction(); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1, 2])) + } + + #[test] + fn rollback_in_nested_transaction_works() { + let mut storage = TransientStorage::::new(1024); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&BOB, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + storage.rollback_transaction(); + storage.commit_transaction(); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1])); + assert_eq!(storage.read(&BOB, &Key::Fix([1; 32])), None) + } + + #[test] + fn commit_in_nested_transaction_works() { + let mut storage = TransientStorage::::new(1024); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&BOB, &Key::Fix([1; 32]), Some(vec![2]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&CHARLIE, &Key::Fix([1; 32]), Some(vec![3]), false), + Ok(WriteOutcome::New) + ); + storage.commit_transaction(); + storage.commit_transaction(); + storage.commit_transaction(); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1])); + assert_eq!(storage.read(&BOB, &Key::Fix([1; 32])), Some(vec![2])); + assert_eq!(storage.read(&CHARLIE, &Key::Fix([1; 32])), Some(vec![3])); + } + + #[test] + fn rollback_all_transactions_works() { + let mut storage = TransientStorage::::new(1024); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&BOB, &Key::Fix([1; 32]), Some(vec![2]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&CHARLIE, &Key::Fix([1; 32]), Some(vec![3]), false), + Ok(WriteOutcome::New) + ); + storage.commit_transaction(); + storage.commit_transaction(); + storage.rollback_transaction(); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), None); + assert_eq!(storage.read(&BOB, &Key::Fix([1; 32])), None); + assert_eq!(storage.read(&CHARLIE, &Key::Fix([1; 32])), None); + } + + #[test] + fn metering_transactions_works() { + let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096])); + let mut storage = TransientStorage::::new(size * 2); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + let limit = storage.meter().current().limit; + storage.commit_transaction(); + + storage.start_transaction(); + assert_eq!(storage.meter().current().limit, limit - size); + assert_eq!(storage.meter().current().limit - storage.meter().current().amount, size); + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(storage.meter().current().amount, size); + storage.commit_transaction(); + assert_eq!(storage.meter().total_amount(), size * 2); + } + + #[test] + fn metering_nested_transactions_works() { + let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096])); + let mut storage = TransientStorage::::new(size * 3); + + storage.start_transaction(); + let limit = storage.meter().current().limit; + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!(storage.meter().total_amount(), size); + assert!(storage.meter().current().limit < limit - size); + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + storage.commit_transaction(); + assert_eq!(storage.meter().current().limit, limit); + assert_eq!(storage.meter().total_amount(), storage.meter().current().amount); + storage.commit_transaction(); + } + + #[test] + fn metering_transaction_fails() { + let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096])); + let mut storage = TransientStorage::::new(size - 1); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Err(Error::::OutOfTransientStorage.into()) + ); + assert_eq!(storage.meter.current().amount, 0); + storage.commit_transaction(); + assert_eq!(storage.meter.total_amount(), 0); + } + + #[test] + fn metering_nested_transactions_fails() { + let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096])); + let mut storage = TransientStorage::::new(size * 2); + + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false), + Err(Error::::OutOfTransientStorage.into()) + ); + storage.commit_transaction(); + storage.commit_transaction(); + assert_eq!(storage.meter.total_amount(), size); + } + + #[test] + fn metering_nested_transaction_with_rollback_works() { + let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096])); + let mut storage = TransientStorage::::new(size * 2); + + storage.start_transaction(); + let limit = storage.meter.current().limit; + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + storage.rollback_transaction(); + + assert_eq!(storage.meter.total_amount(), 0); + assert_eq!(storage.meter.current().limit, limit); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + let amount = storage.meter().current().amount; + assert_eq!(storage.meter().total_amount(), amount); + storage.commit_transaction(); + } + + #[test] + fn metering_with_rollback_works() { + let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096])); + let mut storage = TransientStorage::::new(size * 5); + + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + let amount = storage.meter.total_amount(); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&BOB, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + storage.commit_transaction(); + storage.rollback_transaction(); + assert_eq!(storage.meter.total_amount(), amount); + storage.commit_transaction(); + } +} diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs new file mode 100644 index 000000000000..784993ca793d --- /dev/null +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -0,0 +1,350 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module provides a means for executing contracts +//! represented in wasm. + +mod runtime; + +#[cfg(doc)] +pub use crate::wasm::runtime::SyscallDoc; + +#[cfg(test)] +pub use runtime::HIGHEST_API_VERSION; + +#[cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] +pub use crate::wasm::runtime::{ReturnData, TrapReason}; + +pub use crate::wasm::runtime::{ApiVersion, Memory, Runtime, RuntimeCosts}; + +use crate::{ + exec::{ExecResult, Executable, ExportedFunction, Ext}, + gas::{GasMeter, Token}, + storage::meter::Diff, + weights::WeightInfo, + AccountIdOf, BadOrigin, BalanceOf, CodeHash, CodeInfoOf, CodeVec, Config, Error, Event, + ExecError, HoldReason, Pallet, PristineCode, Weight, API_VERSION, LOG_TARGET, +}; +use alloc::vec::Vec; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + dispatch::DispatchResult, + ensure, + traits::{fungible::MutateHold, tokens::Precision::BestEffort}, +}; +use sp_core::Get; +use sp_runtime::{traits::Hash, DispatchError}; + +/// Validated Wasm module ready for execution. +/// This data structure is immutable once created and stored. +#[derive(Encode, Decode, scale_info::TypeInfo)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(T))] +pub struct WasmBlob { + code: CodeVec, + // This isn't needed for contract execution and is not stored alongside it. + #[codec(skip)] + code_info: CodeInfo, + // This is for not calculating the hash every time we need it. + #[codec(skip)] + code_hash: CodeHash, +} + +/// Contract code related data, such as: +/// +/// - owner of the contract, i.e. account uploaded its code, +/// - storage deposit amount, +/// - reference count, +/// +/// It is stored in a separate storage entry to avoid loading the code when not necessary. +#[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(T))] +pub struct CodeInfo { + /// The account that has uploaded the contract code and hence is allowed to remove it. + owner: AccountIdOf, + /// The amount of balance that was deposited by the owner in order to store it on-chain. + #[codec(compact)] + deposit: BalanceOf, + /// The number of instantiated contracts that use this as their code. + #[codec(compact)] + refcount: u64, + /// Length of the code in bytes. + code_len: u32, + /// The API version that this contract operates under. + /// + /// This determines which host functions are available to the contract. This + /// prevents that new host functions become available to already deployed contracts. + api_version: u16, + /// The behaviour version that this contract operates under. + /// + /// Whenever any observeable change (with the exception of weights) are made we need + /// to make sure that already deployed contracts will not be affected. We do this by + /// exposing the old behaviour depending on the set behaviour version of the contract. + /// + /// As of right now this is a reserved field that is always set to 0. + behaviour_version: u16, +} + +impl ExportedFunction { + /// The wasm export name for the function. + fn identifier(&self) -> &str { + match self { + Self::Constructor => "deploy", + Self::Call => "call", + } + } +} + +/// Cost of code loading from storage. +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Clone, Copy)] +struct CodeLoadToken(u32); + +impl Token for CodeLoadToken { + fn weight(&self) -> Weight { + T::WeightInfo::call_with_code_per_byte(self.0) + .saturating_sub(T::WeightInfo::call_with_code_per_byte(0)) + } +} + +impl WasmBlob { + /// We only check for size and nothing else when the code is uploaded. + pub fn from_code( + code: Vec, + owner: AccountIdOf, + ) -> Result { + let code: CodeVec = + code.try_into().map_err(|_| (>::CodeTooLarge.into(), ""))?; + let code_len = code.len() as u32; + let bytes_added = code_len.saturating_add(>::max_encoded_len() as u32); + let deposit = Diff { bytes_added, items_added: 2, ..Default::default() } + .update_contract::(None) + .charge_or_zero(); + let code_info = CodeInfo { + owner, + deposit, + refcount: 0, + code_len, + api_version: API_VERSION, + behaviour_version: Default::default(), + }; + let code_hash = T::Hashing::hash(&code); + Ok(WasmBlob { code, code_info, code_hash }) + } + + /// Remove the code from storage and refund the deposit to its owner. + /// + /// Applies all necessary checks before removing the code. + pub fn remove(origin: &T::AccountId, code_hash: CodeHash) -> DispatchResult { + >::try_mutate_exists(&code_hash, |existing| { + if let Some(code_info) = existing { + ensure!(code_info.refcount == 0, >::CodeInUse); + ensure!(&code_info.owner == origin, BadOrigin); + let _ = T::Currency::release( + &HoldReason::CodeUploadDepositReserve.into(), + &code_info.owner, + code_info.deposit, + BestEffort, + ); + let deposit_released = code_info.deposit; + let remover = code_info.owner.clone(); + + *existing = None; + >::remove(&code_hash); + >::deposit_event(Event::CodeRemoved { + code_hash, + deposit_released, + remover, + }); + Ok(()) + } else { + Err(>::CodeNotFound.into()) + } + }) + } + + /// Puts the module blob into storage, and returns the deposit collected for the storage. + pub fn store_code(&mut self) -> Result, Error> { + let code_hash = *self.code_hash(); + >::mutate(code_hash, |stored_code_info| { + match stored_code_info { + // Contract code is already stored in storage. Nothing to be done here. + Some(_) => Ok(Default::default()), + // Upload a new contract code. + // We need to store the code and its code_info, and collect the deposit. + // This `None` case happens only with freshly uploaded modules. This means that + // the `owner` is always the origin of the current transaction. + None => { + let deposit = self.code_info.deposit; + T::Currency::hold( + &HoldReason::CodeUploadDepositReserve.into(), + &self.code_info.owner, + deposit, + ) + .map_err(|_| >::StorageDepositNotEnoughFunds)?; + + self.code_info.refcount = 0; + >::insert(code_hash, &self.code); + *stored_code_info = Some(self.code_info.clone()); + >::deposit_event(Event::CodeStored { + code_hash, + deposit_held: deposit, + uploader: self.code_info.owner.clone(), + }); + Ok(deposit) + }, + } + }) + } +} + +impl CodeInfo { + #[cfg(test)] + pub fn new(owner: T::AccountId) -> Self { + CodeInfo { + owner, + deposit: Default::default(), + refcount: 0, + code_len: 0, + api_version: API_VERSION, + behaviour_version: Default::default(), + } + } + + /// Returns reference count of the module. + pub fn refcount(&self) -> u64 { + self.refcount + } + + /// Return mutable reference to the refcount of the module. + pub fn refcount_mut(&mut self) -> &mut u64 { + &mut self.refcount + } + + /// Returns the deposit of the module. + pub fn deposit(&self) -> BalanceOf { + self.deposit + } +} + +pub struct PreparedCall<'a, E: Ext> { + module: polkavm::Module, + instance: polkavm::RawInstance, + runtime: Runtime<'a, E, polkavm::RawInstance>, + api_version: ApiVersion, +} + +impl<'a, E: Ext> PreparedCall<'a, E> { + pub fn call(mut self) -> ExecResult { + let exec_result = loop { + let interrupt = self.instance.run(); + if let Some(exec_result) = self.runtime.handle_interrupt( + interrupt, + &self.module, + &mut self.instance, + self.api_version, + ) { + break exec_result + } + }; + let _ = self.runtime.ext().gas_meter_mut().sync_from_executor(self.instance.gas())?; + exec_result + } +} + +impl WasmBlob { + pub fn prepare_call>( + self, + mut runtime: Runtime, + entry_point: ExportedFunction, + api_version: ApiVersion, + ) -> Result, ExecError> { + let code = self.code.as_slice(); + + let mut config = polkavm::Config::default(); + config.set_backend(Some(polkavm::BackendKind::Interpreter)); + let engine = + polkavm::Engine::new(&config).expect("interpreter is available on all plattforms; qed"); + + let mut module_config = polkavm::ModuleConfig::new(); + module_config.set_gas_metering(Some(polkavm::GasMeteringKind::Sync)); + let module = polkavm::Module::new(&engine, &module_config, code.into()).map_err(|err| { + log::debug!(target: LOG_TARGET, "failed to create polkavm module: {err:?}"); + Error::::CodeRejected + })?; + + let entry_program_counter = module + .exports() + .find(|export| export.symbol().as_bytes() == entry_point.identifier().as_bytes()) + .ok_or_else(|| >::CodeRejected)? + .program_counter(); + + let gas_limit_polkavm: polkavm::Gas = runtime.ext().gas_meter_mut().engine_fuel_left()?; + + let mut instance = module.instantiate().map_err(|err| { + log::debug!(target: LOG_TARGET, "failed to instantiate polkavm module: {err:?}"); + Error::::CodeRejected + })?; + + // Increment before execution so that the constructor sees the correct refcount + if let ExportedFunction::Constructor = entry_point { + E::increment_refcount(self.code_hash)?; + } + + instance.set_gas(gas_limit_polkavm); + instance.prepare_call_untyped(entry_program_counter, &[]); + + Ok(PreparedCall { module, instance, runtime, api_version }) + } +} + +impl Executable for WasmBlob { + fn from_storage( + code_hash: CodeHash, + gas_meter: &mut GasMeter, + ) -> Result { + let code_info = >::get(code_hash).ok_or(Error::::CodeNotFound)?; + gas_meter.charge(CodeLoadToken(code_info.code_len))?; + let code = >::get(code_hash).ok_or(Error::::CodeNotFound)?; + Ok(Self { code, code_info, code_hash }) + } + + fn execute>( + self, + ext: &mut E, + function: ExportedFunction, + input_data: Vec, + ) -> ExecResult { + let api_version = if ::UnsafeUnstableInterface::get() { + ApiVersion::UnsafeNewest + } else { + ApiVersion::Versioned(self.code_info.api_version) + }; + let prepared_call = + self.prepare_call(Runtime::new(ext, input_data), function, api_version)?; + prepared_call.call() + } + + fn code_info(&self) -> &CodeInfo { + &self.code_info + } + + fn code_hash(&self) -> &CodeHash { + &self.code_hash + } +} diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs new file mode 100644 index 000000000000..de910e79e73e --- /dev/null +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -0,0 +1,1936 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Environment definition of the wasm smart-contract runtime. + +use crate::{ + exec::{ExecError, ExecResult, Ext, Key, TopicOf}, + gas::{ChargedAmount, Token}, + limits, + primitives::ExecReturnValue, + weights::WeightInfo, + BalanceOf, CodeHash, Config, Error, LOG_TARGET, SENTINEL, +}; +use alloc::{boxed::Box, vec, vec::Vec}; +use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; +use core::{fmt, marker::PhantomData}; +use frame_support::{ + dispatch::DispatchInfo, ensure, pallet_prelude::DispatchResultWithPostInfo, parameter_types, + traits::Get, weights::Weight, +}; +use pallet_revive_proc_macro::define_env; +use pallet_revive_uapi::{CallFlags, ReturnErrorCode, ReturnFlags, StorageFlags}; +use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256}; +use sp_runtime::{traits::Zero, DispatchError, RuntimeDebug}; + +type CallOf = ::RuntimeCall; + +/// The maximum nesting depth a contract can use when encoding types. +const MAX_DECODE_NESTING: u32 = 256; + +#[derive(Clone, Copy)] +pub enum ApiVersion { + /// Expose all APIs even unversioned ones. Only used for testing and benchmarking. + UnsafeNewest, + /// Only expose API's up to and including the specified version. + Versioned(u16), +} + +/// Abstraction over the memory access within syscalls. +/// +/// The reason for this abstraction is that we run syscalls on the host machine when +/// benchmarking them. In that case we have direct access to the contract's memory. However, when +/// running within PolkaVM we need to resort to copying as we can't map the contracts memory into +/// the host (as of now). +pub trait Memory { + /// Read designated chunk from the sandbox memory into the supplied buffer. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - requested buffer is not within the bounds of the sandbox memory. + fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError>; + + /// Write the given buffer to the designated location in the sandbox memory. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - designated area is not within the bounds of the sandbox memory. + fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError>; + + /// Read designated chunk from the sandbox memory. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - requested buffer is not within the bounds of the sandbox memory. + fn read(&self, ptr: u32, len: u32) -> Result, DispatchError> { + let mut buf = vec![0u8; len as usize]; + self.read_into_buf(ptr, buf.as_mut_slice())?; + Ok(buf) + } + + /// Read designated chunk from the sandbox memory and attempt to decode into the specified type. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - requested buffer is not within the bounds of the sandbox memory. + /// - the buffer contents cannot be decoded as the required type. + /// + /// # Note + /// + /// There must be an extra benchmark for determining the influence of `len` with + /// regard to the overall weight. + fn read_as_unbounded(&self, ptr: u32, len: u32) -> Result { + let buf = self.read(ptr, len)?; + let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut buf.as_ref()) + .map_err(|_| DispatchError::from(Error::::DecodingFailed))?; + Ok(decoded) + } + + /// Reads and decodes a type with a size fixed at compile time from contract memory. + /// + /// # Only use on fixed size types + /// + /// Don't use this for types where the encoded size is not fixed but merely bounded. Otherwise + /// this implementation will out of bound access the buffer declared by the guest. Some examples + /// of those bounded but not fixed types: Enums with data, `BoundedVec` or any compact encoded + /// integer. + /// + /// # Note + /// + /// The weight of reading a fixed value is included in the overall weight of any + /// contract callable function. + fn read_as(&self, ptr: u32) -> Result { + let buf = self.read(ptr, D::max_encoded_len() as u32)?; + let decoded = D::decode_with_depth_limit(MAX_DECODE_NESTING, &mut buf.as_ref()) + .map_err(|_| DispatchError::from(Error::::DecodingFailed))?; + Ok(decoded) + } +} + +/// Allows syscalls access to the PolkaVM instance they are executing in. +/// +/// In case a contract is executing within PolkaVM its `memory` argument will also implement +/// this trait. The benchmarking implementation of syscalls will only require `Memory` +/// to be implemented. +pub trait PolkaVmInstance: Memory { + fn gas(&self) -> polkavm::Gas; + fn set_gas(&mut self, gas: polkavm::Gas); + fn read_input_regs(&self) -> (u32, u32, u32, u32, u32, u32); + fn write_output(&mut self, output: u32); +} + +// Memory implementation used in benchmarking where guest memory is mapped into the host. +// +// Please note that we could optimize the `read_as_*` functions by decoding directly from +// memory without a copy. However, we don't do that because as it would change the behaviour +// of those functions: A `read_as` with a `len` larger than the actual type can succeed +// in the streaming implementation while it could fail with a segfault in the copy implementation. +#[cfg(feature = "runtime-benchmarks")] +impl Memory for [u8] { + fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError> { + let ptr = ptr as usize; + let bound_checked = + self.get(ptr..ptr + buf.len()).ok_or_else(|| Error::::OutOfBounds)?; + buf.copy_from_slice(bound_checked); + Ok(()) + } + + fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> { + let ptr = ptr as usize; + let bound_checked = + self.get_mut(ptr..ptr + buf.len()).ok_or_else(|| Error::::OutOfBounds)?; + bound_checked.copy_from_slice(buf); + Ok(()) + } +} + +impl Memory for polkavm::RawInstance { + fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError> { + self.read_memory_into(ptr, buf) + .map(|_| ()) + .map_err(|_| Error::::OutOfBounds.into()) + } + + fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> { + self.write_memory(ptr, buf).map_err(|_| Error::::OutOfBounds.into()) + } +} + +impl PolkaVmInstance for polkavm::RawInstance { + fn gas(&self) -> polkavm::Gas { + self.gas() + } + + fn set_gas(&mut self, gas: polkavm::Gas) { + self.set_gas(gas) + } + + fn read_input_regs(&self) -> (u32, u32, u32, u32, u32, u32) { + ( + self.reg(polkavm::Reg::A0), + self.reg(polkavm::Reg::A1), + self.reg(polkavm::Reg::A2), + self.reg(polkavm::Reg::A3), + self.reg(polkavm::Reg::A4), + self.reg(polkavm::Reg::A5), + ) + } + + fn write_output(&mut self, output: u32) { + self.set_reg(polkavm::Reg::A0, output); + } +} + +parameter_types! { + /// Getter types used by [`crate::SyscallDoc:call_runtime`] + const CallRuntimeFailed: ReturnErrorCode = ReturnErrorCode::CallRuntimeFailed; + /// Getter types used by [`crate::SyscallDoc::xcm_execute`] + const XcmExecutionFailed: ReturnErrorCode = ReturnErrorCode::XcmExecutionFailed; +} + +impl From for ReturnErrorCode { + fn from(from: ExecReturnValue) -> Self { + if from.flags.contains(ReturnFlags::REVERT) { + Self::CalleeReverted + } else { + Self::Success + } + } +} + +/// The data passed through when a contract uses `seal_return`. +#[derive(RuntimeDebug)] +pub struct ReturnData { + /// The flags as passed through by the contract. They are still unchecked and + /// will later be parsed into a `ReturnFlags` bitflags struct. + flags: u32, + /// The output buffer passed by the contract as return data. + data: Vec, +} + +/// Enumerates all possible reasons why a trap was generated. +/// +/// This is either used to supply the caller with more information about why an error +/// occurred (the SupervisorError variant). +/// The other case is where the trap does not constitute an error but rather was invoked +/// as a quick way to terminate the application (all other variants). +#[derive(RuntimeDebug)] +pub enum TrapReason { + /// The supervisor trapped the contract because of an error condition occurred during + /// execution in privileged code. + SupervisorError(DispatchError), + /// Signals that trap was generated in response to call `seal_return` host function. + Return(ReturnData), + /// Signals that a trap was generated in response to a successful call to the + /// `seal_terminate` host function. + Termination, +} + +impl> From for TrapReason { + fn from(from: T) -> Self { + Self::SupervisorError(from.into()) + } +} + +impl fmt::Display for TrapReason { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + Ok(()) + } +} + +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Copy, Clone)] +pub enum RuntimeCosts { + /// Base Weight of calling a host function. + HostFn, + /// Weight charged for copying data from the sandbox. + CopyFromContract(u32), + /// Weight charged for copying data to the sandbox. + CopyToContract(u32), + /// Weight of calling `seal_caller`. + Caller, + /// Weight of calling `seal_is_contract`. + IsContract, + /// Weight of calling `seal_code_hash`. + CodeHash, + /// Weight of calling `seal_own_code_hash`. + OwnCodeHash, + /// Weight of calling `seal_caller_is_origin`. + CallerIsOrigin, + /// Weight of calling `caller_is_root`. + CallerIsRoot, + /// Weight of calling `seal_address`. + Address, + /// Weight of calling `seal_gas_left`. + GasLeft, + /// Weight of calling `seal_balance`. + Balance, + /// Weight of calling `seal_value_transferred`. + ValueTransferred, + /// Weight of calling `seal_minimum_balance`. + MinimumBalance, + /// Weight of calling `seal_block_number`. + BlockNumber, + /// Weight of calling `seal_now`. + Now, + /// Weight of calling `seal_weight_to_fee`. + WeightToFee, + /// Weight of calling `seal_terminate`, passing the number of locked dependencies. + Terminate(u32), + /// Weight of calling `seal_deposit_event` with the given number of topics and event size. + DepositEvent { num_topic: u32, len: u32 }, + /// Weight of calling `seal_debug_message` per byte of passed message. + DebugMessage(u32), + /// Weight of calling `seal_set_storage` for the given storage item sizes. + SetStorage { old_bytes: u32, new_bytes: u32 }, + /// Weight of calling `seal_clear_storage` per cleared byte. + ClearStorage(u32), + /// Weight of calling `seal_contains_storage` per byte of the checked item. + ContainsStorage(u32), + /// Weight of calling `seal_get_storage` with the specified size in storage. + GetStorage(u32), + /// Weight of calling `seal_take_storage` for the given size. + TakeStorage(u32), + /// Weight of calling `seal_set_transient_storage` for the given storage item sizes. + SetTransientStorage { old_bytes: u32, new_bytes: u32 }, + /// Weight of calling `seal_clear_transient_storage` per cleared byte. + ClearTransientStorage(u32), + /// Weight of calling `seal_contains_transient_storage` per byte of the checked item. + ContainsTransientStorage(u32), + /// Weight of calling `seal_get_transient_storage` with the specified size in storage. + GetTransientStorage(u32), + /// Weight of calling `seal_take_transient_storage` for the given size. + TakeTransientStorage(u32), + /// Weight of calling `seal_transfer`. + Transfer, + /// Base weight of calling `seal_call`. + CallBase, + /// Weight of calling `seal_delegate_call` for the given input size. + DelegateCallBase, + /// Weight of the transfer performed during a call. + CallTransferSurcharge, + /// Weight per byte that is cloned by supplying the `CLONE_INPUT` flag. + CallInputCloned(u32), + /// Weight of calling `seal_instantiate` for the given input length and salt. + Instantiate { input_data_len: u32, salt_len: u32 }, + /// Weight of calling `seal_hash_sha_256` for the given input size. + HashSha256(u32), + /// Weight of calling `seal_hash_keccak_256` for the given input size. + HashKeccak256(u32), + /// Weight of calling `seal_hash_blake2_256` for the given input size. + HashBlake256(u32), + /// Weight of calling `seal_hash_blake2_128` for the given input size. + HashBlake128(u32), + /// Weight of calling `seal_ecdsa_recover`. + EcdsaRecovery, + /// Weight of calling `seal_sr25519_verify` for the given input size. + Sr25519Verify(u32), + /// Weight charged by a chain extension through `seal_call_chain_extension`. + ChainExtension(Weight), + /// Weight charged for calling into the runtime. + CallRuntime(Weight), + /// Weight charged for calling xcm_execute. + CallXcmExecute(Weight), + /// Weight of calling `seal_set_code_hash` + SetCodeHash, + /// Weight of calling `ecdsa_to_eth_address` + EcdsaToEthAddress, + /// Weight of calling `lock_delegate_dependency` + LockDelegateDependency, + /// Weight of calling `unlock_delegate_dependency` + UnlockDelegateDependency, +} + +/// For functions that modify storage, benchmarks are performed with one item in the +/// storage. To account for the worst-case scenario, the weight of the overhead of +/// writing to or reading from full storage is included. For transient storage writes, +/// the rollback weight is added to reflect the worst-case scenario for this operation. +macro_rules! cost_storage { + (write_transient, $name:ident $(, $arg:expr )*) => { + T::WeightInfo::$name($( $arg ),*) + .saturating_add(T::WeightInfo::rollback_transient_storage()) + .saturating_add(T::WeightInfo::set_transient_storage_full() + .saturating_sub(T::WeightInfo::set_transient_storage_empty())) + }; + + (read_transient, $name:ident $(, $arg:expr )*) => { + T::WeightInfo::$name($( $arg ),*) + .saturating_add(T::WeightInfo::get_transient_storage_full() + .saturating_sub(T::WeightInfo::get_transient_storage_empty())) + }; + + (write, $name:ident $(, $arg:expr )*) => { + T::WeightInfo::$name($( $arg ),*) + .saturating_add(T::WeightInfo::set_storage_full() + .saturating_sub(T::WeightInfo::set_storage_empty())) + }; + + (read, $name:ident $(, $arg:expr )*) => { + T::WeightInfo::$name($( $arg ),*) + .saturating_add(T::WeightInfo::get_storage_full() + .saturating_sub(T::WeightInfo::get_storage_empty())) + }; +} + +macro_rules! cost_args { + // cost_args!(name, a, b, c) -> T::WeightInfo::name(a, b, c).saturating_sub(T::WeightInfo::name(0, 0, 0)) + ($name:ident, $( $arg: expr ),+) => { + (T::WeightInfo::$name($( $arg ),+).saturating_sub(cost_args!(@call_zero $name, $( $arg ),+))) + }; + // Transform T::WeightInfo::name(a, b, c) into T::WeightInfo::name(0, 0, 0) + (@call_zero $name:ident, $( $arg:expr ),*) => { + T::WeightInfo::$name($( cost_args!(@replace_token $arg) ),*) + }; + // Replace the token with 0. + (@replace_token $_in:tt) => { 0 }; +} + +impl Token for RuntimeCosts { + fn influence_lowest_gas_limit(&self) -> bool { + match self { + &Self::CallXcmExecute(_) => false, + _ => true, + } + } + + fn weight(&self) -> Weight { + use self::RuntimeCosts::*; + match *self { + HostFn => cost_args!(noop_host_fn, 1), + CopyToContract(len) => T::WeightInfo::seal_input(len), + CopyFromContract(len) => T::WeightInfo::seal_return(len), + Caller => T::WeightInfo::seal_caller(), + IsContract => T::WeightInfo::seal_is_contract(), + CodeHash => T::WeightInfo::seal_code_hash(), + OwnCodeHash => T::WeightInfo::seal_own_code_hash(), + CallerIsOrigin => T::WeightInfo::seal_caller_is_origin(), + CallerIsRoot => T::WeightInfo::seal_caller_is_root(), + Address => T::WeightInfo::seal_address(), + GasLeft => T::WeightInfo::seal_gas_left(), + Balance => T::WeightInfo::seal_balance(), + ValueTransferred => T::WeightInfo::seal_value_transferred(), + MinimumBalance => T::WeightInfo::seal_minimum_balance(), + BlockNumber => T::WeightInfo::seal_block_number(), + Now => T::WeightInfo::seal_now(), + WeightToFee => T::WeightInfo::seal_weight_to_fee(), + Terminate(locked_dependencies) => T::WeightInfo::seal_terminate(locked_dependencies), + DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len), + DebugMessage(len) => T::WeightInfo::seal_debug_message(len), + SetStorage { new_bytes, old_bytes } => + cost_storage!(write, seal_set_storage, new_bytes, old_bytes), + ClearStorage(len) => cost_storage!(write, seal_clear_storage, len), + ContainsStorage(len) => cost_storage!(read, seal_contains_storage, len), + GetStorage(len) => cost_storage!(read, seal_get_storage, len), + TakeStorage(len) => cost_storage!(write, seal_take_storage, len), + SetTransientStorage { new_bytes, old_bytes } => + cost_storage!(write_transient, seal_set_transient_storage, new_bytes, old_bytes), + ClearTransientStorage(len) => + cost_storage!(write_transient, seal_clear_transient_storage, len), + ContainsTransientStorage(len) => + cost_storage!(read_transient, seal_contains_transient_storage, len), + GetTransientStorage(len) => + cost_storage!(read_transient, seal_get_transient_storage, len), + TakeTransientStorage(len) => + cost_storage!(write_transient, seal_take_transient_storage, len), + Transfer => T::WeightInfo::seal_transfer(), + CallBase => T::WeightInfo::seal_call(0, 0), + DelegateCallBase => T::WeightInfo::seal_delegate_call(), + CallTransferSurcharge => cost_args!(seal_call, 1, 0), + CallInputCloned(len) => cost_args!(seal_call, 0, len), + Instantiate { input_data_len, salt_len } => + T::WeightInfo::seal_instantiate(input_data_len, salt_len), + HashSha256(len) => T::WeightInfo::seal_hash_sha2_256(len), + HashKeccak256(len) => T::WeightInfo::seal_hash_keccak_256(len), + HashBlake256(len) => T::WeightInfo::seal_hash_blake2_256(len), + HashBlake128(len) => T::WeightInfo::seal_hash_blake2_128(len), + EcdsaRecovery => T::WeightInfo::seal_ecdsa_recover(), + Sr25519Verify(len) => T::WeightInfo::seal_sr25519_verify(len), + ChainExtension(weight) | CallRuntime(weight) | CallXcmExecute(weight) => weight, + SetCodeHash => T::WeightInfo::seal_set_code_hash(), + EcdsaToEthAddress => T::WeightInfo::seal_ecdsa_to_eth_address(), + LockDelegateDependency => T::WeightInfo::lock_delegate_dependency(), + UnlockDelegateDependency => T::WeightInfo::unlock_delegate_dependency(), + } + } +} + +/// Same as [`Runtime::charge_gas`]. +/// +/// We need this access as a macro because sometimes hiding the lifetimes behind +/// a function won't work out. +macro_rules! charge_gas { + ($runtime:expr, $costs:expr) => {{ + $runtime.ext.gas_meter_mut().charge($costs) + }}; +} + +/// The kind of call that should be performed. +enum CallType { + /// Execute another instantiated contract + Call { callee_ptr: u32, value_ptr: u32, deposit_ptr: u32, weight: Weight }, + /// Execute deployed code in the context (storage, account ID, value) of the caller contract + DelegateCall { code_hash_ptr: u32 }, +} + +impl CallType { + fn cost(&self) -> RuntimeCosts { + match self { + CallType::Call { .. } => RuntimeCosts::CallBase, + CallType::DelegateCall { .. } => RuntimeCosts::DelegateCallBase, + } + } +} + +/// This is only appropriate when writing out data of constant size that does not depend on user +/// input. In this case the costs for this copy was already charged as part of the token at +/// the beginning of the API entry point. +fn already_charged(_: u32) -> Option { + None +} + +/// Can only be used for one call. +pub struct Runtime<'a, E: Ext, M: ?Sized> { + ext: &'a mut E, + input_data: Option>, + chain_extension: Option::ChainExtension>>, + _phantom_data: PhantomData, +} + +impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { + pub fn handle_interrupt( + &mut self, + interrupt: Result, + module: &polkavm::Module, + instance: &mut M, + api_version: ApiVersion, + ) -> Option { + use polkavm::InterruptKind::*; + + match interrupt { + Err(error) => { + // in contrast to the other returns this "should" not happen: log level error + log::error!(target: LOG_TARGET, "polkavm execution error: {error}"); + Some(Err(Error::::ExecutionFailed.into())) + }, + Ok(Finished) => + Some(Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })), + Ok(Trap) => Some(Err(Error::::ContractTrapped.into())), + Ok(Segfault(_)) => Some(Err(Error::::ExecutionFailed.into())), + Ok(NotEnoughGas) => Some(Err(Error::::OutOfGas.into())), + Ok(Step) => None, + Ok(Ecalli(idx)) => { + let Some(syscall_symbol) = module.imports().get(idx) else { + return Some(Err(>::InvalidSyscall.into())) + }; + match self.handle_ecall(instance, syscall_symbol.as_bytes(), api_version) { + Ok(None) => None, + Ok(Some(return_value)) => { + instance.write_output(return_value); + None + }, + Err(TrapReason::Return(ReturnData { flags, data })) => + match ReturnFlags::from_bits(flags) { + None => Some(Err(Error::::InvalidCallFlags.into())), + Some(flags) => Some(Ok(ExecReturnValue { flags, data })), + }, + Err(TrapReason::Termination) => + Some(Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })), + Err(TrapReason::SupervisorError(error)) => Some(Err(error.into())), + } + }, + } + } +} + +impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { + pub fn new(ext: &'a mut E, input_data: Vec) -> Self { + Self { + ext, + input_data: Some(input_data), + chain_extension: Some(Box::new(Default::default())), + _phantom_data: Default::default(), + } + } + + /// Get a mutable reference to the inner `Ext`. + /// + /// This is mainly for the chain extension to have access to the environment the + /// contract is executing in. + pub fn ext(&mut self) -> &mut E { + self.ext + } + + /// Charge the gas meter with the specified token. + /// + /// Returns `Err(HostError)` if there is not enough gas. + pub fn charge_gas(&mut self, costs: RuntimeCosts) -> Result { + charge_gas!(self, costs) + } + + /// Adjust a previously charged amount down to its actual amount. + /// + /// This is when a maximum a priori amount was charged and then should be partially + /// refunded to match the actual amount. + pub fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) { + self.ext.gas_meter_mut().adjust_gas(charged, actual_costs); + } + + /// Charge, Run and adjust gas, for executing the given dispatchable. + fn call_dispatchable>( + &mut self, + dispatch_info: DispatchInfo, + runtime_cost: impl Fn(Weight) -> RuntimeCosts, + run: impl FnOnce(&mut Self) -> DispatchResultWithPostInfo, + ) -> Result { + use frame_support::dispatch::extract_actual_weight; + let charged = self.charge_gas(runtime_cost(dispatch_info.weight))?; + let result = run(self); + let actual_weight = extract_actual_weight(&result, &dispatch_info); + self.adjust_gas(charged, runtime_cost(actual_weight)); + match result { + Ok(_) => Ok(ReturnErrorCode::Success), + Err(e) => { + if self.ext.debug_buffer_enabled() { + self.ext.append_debug_buffer("call failed with: "); + self.ext.append_debug_buffer(e.into()); + }; + Ok(ErrorReturnCode::get()) + }, + } + } + + /// Write the given buffer and its length to the designated locations in sandbox memory and + /// charge gas according to the token returned by `create_token`. + // + /// `out_ptr` is the location in sandbox memory where `buf` should be written to. + /// `out_len_ptr` is an in-out location in sandbox memory. It is read to determine the + /// length of the buffer located at `out_ptr`. If that buffer is large enough the actual + /// `buf.len()` is written to this location. + /// + /// If `out_ptr` is set to the sentinel value of `SENTINEL` and `allow_skip` is true the + /// operation is skipped and `Ok` is returned. This is supposed to help callers to make copying + /// output optional. For example to skip copying back the output buffer of an `seal_call` + /// when the caller is not interested in the result. + /// + /// `create_token` can optionally instruct this function to charge the gas meter with the token + /// it returns. `create_token` receives the variable amount of bytes that are about to be copied + /// by this function. + /// + /// In addition to the error conditions of `Memory::write` this functions returns + /// `Err` if the size of the buffer located at `out_ptr` is too small to fit `buf`. + pub fn write_sandbox_output( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + buf: &[u8], + allow_skip: bool, + create_token: impl FnOnce(u32) -> Option, + ) -> Result<(), DispatchError> { + if allow_skip && out_ptr == SENTINEL { + return Ok(()) + } + + let buf_len = buf.len() as u32; + let len: u32 = memory.read_as(out_len_ptr)?; + + if len < buf_len { + return Err(Error::::OutputBufferTooSmall.into()) + } + + if let Some(costs) = create_token(buf_len) { + self.charge_gas(costs)?; + } + + memory.write(out_ptr, buf)?; + memory.write(out_len_ptr, &buf_len.encode()) + } + + /// Computes the given hash function on the supplied input. + /// + /// Reads from the sandboxed input buffer into an intermediate buffer. + /// Returns the result directly to the output buffer of the sandboxed memory. + /// + /// It is the callers responsibility to provide an output buffer that + /// is large enough to hold the expected amount of bytes returned by the + /// chosen hash function. + /// + /// # Note + /// + /// The `input` and `output` buffers may overlap. + fn compute_hash_on_intermediate_buffer( + &self, + memory: &mut M, + hash_fn: F, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), DispatchError> + where + F: FnOnce(&[u8]) -> R, + R: AsRef<[u8]>, + { + // Copy input into supervisor memory. + let input = memory.read(input_ptr, input_len)?; + // Compute the hash on the input buffer using the given hash function. + let hash = hash_fn(&input); + // Write the resulting hash back into the sandboxed output buffer. + memory.write(output_ptr, hash.as_ref())?; + Ok(()) + } + + /// Fallible conversion of `DispatchError` to `ReturnErrorCode`. + fn err_into_return_code(from: DispatchError) -> Result { + use ReturnErrorCode::*; + + let transfer_failed = Error::::TransferFailed.into(); + let no_code = Error::::CodeNotFound.into(); + let not_found = Error::::ContractNotFound.into(); + + match from { + x if x == transfer_failed => Ok(TransferFailed), + x if x == no_code => Ok(CodeNotFound), + x if x == not_found => Ok(NotCallable), + err => Err(err), + } + } + + /// Fallible conversion of a `ExecResult` to `ReturnErrorCode`. + fn exec_into_return_code(from: ExecResult) -> Result { + use crate::exec::ErrorOrigin::Callee; + + let ExecError { error, origin } = match from { + Ok(retval) => return Ok(retval.into()), + Err(err) => err, + }; + + match (error, origin) { + (_, Callee) => Ok(ReturnErrorCode::CalleeTrapped), + (err, _) => Self::err_into_return_code(err), + } + } + fn decode_key(&self, memory: &M, key_ptr: u32, key_len: u32) -> Result { + let res = match key_len { + SENTINEL => { + let mut buffer = [0u8; 32]; + memory.read_into_buf(key_ptr, buffer.as_mut())?; + Ok(Key::from_fixed(buffer)) + }, + len => { + ensure!(len <= limits::STORAGE_KEY_BYTES, Error::::DecodingFailed); + let key = memory.read(key_ptr, len)?; + Key::try_from_var(key) + }, + }; + + res.map_err(|_| Error::::DecodingFailed.into()) + } + + fn is_transient(flags: u32) -> Result { + StorageFlags::from_bits(flags) + .ok_or_else(|| >::InvalidStorageFlags.into()) + .map(|flags| flags.contains(StorageFlags::TRANSIENT)) + } + + fn set_storage( + &mut self, + memory: &M, + flags: u32, + key_ptr: u32, + key_len: u32, + value_ptr: u32, + value_len: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |new_bytes: u32, old_bytes: u32| { + if transient { + RuntimeCosts::SetTransientStorage { new_bytes, old_bytes } + } else { + RuntimeCosts::SetStorage { new_bytes, old_bytes } + } + }; + let max_size = self.ext.max_value_size(); + let charged = self.charge_gas(costs(value_len, self.ext.max_value_size()))?; + if value_len > max_size { + return Err(Error::::ValueTooLarge.into()) + } + let key = self.decode_key(memory, key_ptr, key_len)?; + let value = Some(memory.read(value_ptr, value_len)?); + let write_outcome = if transient { + self.ext.set_transient_storage(&key, value, false)? + } else { + self.ext.set_storage(&key, value, false)? + }; + self.adjust_gas(charged, costs(value_len, write_outcome.old_len())); + Ok(write_outcome.old_len_with_sentinel()) + } + + fn clear_storage( + &mut self, + memory: &M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |len| { + if transient { + RuntimeCosts::ClearTransientStorage(len) + } else { + RuntimeCosts::ClearStorage(len) + } + }; + let charged = self.charge_gas(costs(self.ext.max_value_size()))?; + let key = self.decode_key(memory, key_ptr, key_len)?; + let outcome = if transient { + self.ext.set_transient_storage(&key, None, false)? + } else { + self.ext.set_storage(&key, None, false)? + }; + self.adjust_gas(charged, costs(outcome.old_len())); + Ok(outcome.old_len_with_sentinel()) + } + + fn get_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |len| { + if transient { + RuntimeCosts::GetTransientStorage(len) + } else { + RuntimeCosts::GetStorage(len) + } + }; + let charged = self.charge_gas(costs(self.ext.max_value_size()))?; + let key = self.decode_key(memory, key_ptr, key_len)?; + let outcome = if transient { + self.ext.get_transient_storage(&key) + } else { + self.ext.get_storage(&key) + }; + if let Some(value) = outcome { + self.adjust_gas(charged, costs(value.len() as u32)); + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &value, + false, + already_charged, + )?; + Ok(ReturnErrorCode::Success) + } else { + self.adjust_gas(charged, costs(0)); + Ok(ReturnErrorCode::KeyNotFound) + } + } + + fn contains_storage( + &mut self, + memory: &M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |len| { + if transient { + RuntimeCosts::ContainsTransientStorage(len) + } else { + RuntimeCosts::ContainsStorage(len) + } + }; + let charged = self.charge_gas(costs(self.ext.max_value_size()))?; + let key = self.decode_key(memory, key_ptr, key_len)?; + let outcome = if transient { + self.ext.get_transient_storage_size(&key) + } else { + self.ext.get_storage_size(&key) + }; + self.adjust_gas(charged, costs(outcome.unwrap_or(0))); + Ok(outcome.unwrap_or(SENTINEL)) + } + + fn take_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |len| { + if transient { + RuntimeCosts::TakeTransientStorage(len) + } else { + RuntimeCosts::TakeStorage(len) + } + }; + let charged = self.charge_gas(costs(self.ext.max_value_size()))?; + let key = self.decode_key(memory, key_ptr, key_len)?; + let outcome = if transient { + self.ext.set_transient_storage(&key, None, true)? + } else { + self.ext.set_storage(&key, None, true)? + }; + + if let crate::storage::WriteOutcome::Taken(value) = outcome { + self.adjust_gas(charged, costs(value.len() as u32)); + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &value, + false, + already_charged, + )?; + Ok(ReturnErrorCode::Success) + } else { + self.adjust_gas(charged, costs(0)); + Ok(ReturnErrorCode::KeyNotFound) + } + } + + fn call( + &mut self, + memory: &mut M, + flags: CallFlags, + call_type: CallType, + input_data_ptr: u32, + input_data_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + self.charge_gas(call_type.cost())?; + + let input_data = if flags.contains(CallFlags::CLONE_INPUT) { + let input = self.input_data.as_ref().ok_or(Error::::InputForwarded)?; + charge_gas!(self, RuntimeCosts::CallInputCloned(input.len() as u32))?; + input.clone() + } else if flags.contains(CallFlags::FORWARD_INPUT) { + self.input_data.take().ok_or(Error::::InputForwarded)? + } else { + self.charge_gas(RuntimeCosts::CopyFromContract(input_data_len))?; + memory.read(input_data_ptr, input_data_len)? + }; + + let call_outcome = match call_type { + CallType::Call { callee_ptr, value_ptr, deposit_ptr, weight } => { + let callee: <::T as frame_system::Config>::AccountId = + memory.read_as(callee_ptr)?; + let deposit_limit: BalanceOf<::T> = if deposit_ptr == SENTINEL { + BalanceOf::<::T>::zero() + } else { + memory.read_as(deposit_ptr)? + }; + let read_only = flags.contains(CallFlags::READ_ONLY); + let value: BalanceOf<::T> = memory.read_as(value_ptr)?; + if value > 0u32.into() { + // If the call value is non-zero and state change is not allowed, issue an + // error. + if read_only || self.ext.is_read_only() { + return Err(Error::::StateChangeDenied.into()); + } + self.charge_gas(RuntimeCosts::CallTransferSurcharge)?; + } + self.ext.call( + weight, + deposit_limit, + callee, + value, + input_data, + flags.contains(CallFlags::ALLOW_REENTRY), + read_only, + ) + }, + CallType::DelegateCall { code_hash_ptr } => { + if flags.intersects(CallFlags::ALLOW_REENTRY | CallFlags::READ_ONLY) { + return Err(Error::::InvalidCallFlags.into()) + } + let code_hash = memory.read_as(code_hash_ptr)?; + self.ext.delegate_call(code_hash, input_data) + }, + }; + + // `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to + // a halt anyways without anymore code being executed. + if flags.contains(CallFlags::TAIL_CALL) { + if let Ok(return_value) = call_outcome { + return Err(TrapReason::Return(ReturnData { + flags: return_value.flags.bits(), + data: return_value.data, + })) + } + } + + if let Ok(output) = &call_outcome { + self.write_sandbox_output( + memory, + output_ptr, + output_len_ptr, + &output.data, + true, + |len| Some(RuntimeCosts::CopyToContract(len)), + )?; + } + Ok(Self::exec_into_return_code(call_outcome)?) + } + + fn instantiate( + &mut self, + memory: &mut M, + code_hash_ptr: u32, + weight: Weight, + deposit_ptr: u32, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + address_ptr: u32, + address_len_ptr: u32, + output_ptr: u32, + output_len_ptr: u32, + salt_ptr: u32, + salt_len: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::Instantiate { input_data_len, salt_len })?; + let deposit_limit: BalanceOf<::T> = if deposit_ptr == SENTINEL { + BalanceOf::<::T>::zero() + } else { + memory.read_as(deposit_ptr)? + }; + let value: BalanceOf<::T> = memory.read_as(value_ptr)?; + let code_hash: CodeHash<::T> = memory.read_as(code_hash_ptr)?; + let input_data = memory.read(input_data_ptr, input_data_len)?; + let salt = memory.read(salt_ptr, salt_len)?; + let instantiate_outcome = + self.ext.instantiate(weight, deposit_limit, code_hash, value, input_data, &salt); + if let Ok((address, output)) = &instantiate_outcome { + if !output.flags.contains(ReturnFlags::REVERT) { + self.write_sandbox_output( + memory, + address_ptr, + address_len_ptr, + &address.encode(), + true, + already_charged, + )?; + } + self.write_sandbox_output( + memory, + output_ptr, + output_len_ptr, + &output.data, + true, + |len| Some(RuntimeCosts::CopyToContract(len)), + )?; + } + Ok(Self::exec_into_return_code(instantiate_outcome.map(|(_, retval)| retval))?) + } + + fn terminate(&mut self, memory: &M, beneficiary_ptr: u32) -> Result<(), TrapReason> { + let count = self.ext.locked_delegate_dependencies_count() as _; + self.charge_gas(RuntimeCosts::Terminate(count))?; + + let beneficiary: <::T as frame_system::Config>::AccountId = + memory.read_as(beneficiary_ptr)?; + self.ext.terminate(&beneficiary)?; + Err(TrapReason::Termination) + } +} + +// This is the API exposed to contracts. +// +// # Note +// +// Any input that leads to a out of bound error (reading or writing) or failing to decode +// data passed to the supervisor will lead to a trap. This is not documented explicitly +// for every function. +#[define_env] +pub mod env { + /// Noop function used to benchmark the time it takes to execute an empty function. + #[cfg(feature = "runtime-benchmarks")] + fn noop(&mut self, memory: &mut M) -> Result<(), TrapReason> { + Ok(()) + } + + /// Set the value at the given key in the contract storage. + /// See [`pallet_revive_uapi::HostFn::set_storage_v2`] + #[api_version(0)] + #[mutating] + fn set_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + value_ptr: u32, + value_len: u32, + ) -> Result { + self.set_storage(memory, flags, key_ptr, key_len, value_ptr, value_len) + } + + /// Clear the value at the given key in the contract storage. + /// See [`pallet_revive_uapi::HostFn::clear_storage`] + #[api_version(0)] + #[mutating] + fn clear_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + self.clear_storage(memory, flags, key_ptr, key_len) + } + + /// Retrieve the value under the given key from storage. + /// See [`pallet_revive_uapi::HostFn::get_storage`] + #[api_version(0)] + fn get_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + self.get_storage(memory, flags, key_ptr, key_len, out_ptr, out_len_ptr) + } + + /// Checks whether there is a value stored under the given key. + /// See [`pallet_revive_uapi::HostFn::contains_storage`] + #[api_version(0)] + fn contains_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + self.contains_storage(memory, flags, key_ptr, key_len) + } + + /// Retrieve and remove the value under the given key from storage. + /// See [`pallet_revive_uapi::HostFn::take_storage`] + #[api_version(0)] + #[mutating] + fn take_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + self.take_storage(memory, flags, key_ptr, key_len, out_ptr, out_len_ptr) + } + + /// Transfer some value to another account. + /// See [`pallet_revive_uapi::HostFn::transfer`]. + #[api_version(0)] + #[mutating] + fn transfer( + &mut self, + memory: &mut M, + account_ptr: u32, + value_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::Transfer)?; + let callee: <::T as frame_system::Config>::AccountId = + memory.read_as(account_ptr)?; + let value: BalanceOf<::T> = memory.read_as(value_ptr)?; + let result = self.ext.transfer(&callee, value); + match result { + Ok(()) => Ok(ReturnErrorCode::Success), + Err(err) => { + let code = Self::err_into_return_code(err)?; + Ok(code) + }, + } + } + + /// Make a call to another contract. + /// See [`pallet_revive_uapi::HostFn::call`]. + #[api_version(0)] + fn call( + &mut self, + memory: &mut M, + flags: u32, + callee_ptr: u32, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_ptr: u32, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + self.call( + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::Call { + callee_ptr, + value_ptr, + deposit_ptr, + weight: Weight::from_parts(ref_time_limit, proof_size_limit), + }, + input_data_ptr, + input_data_len, + output_ptr, + output_len_ptr, + ) + } + + /// Execute code in the context (storage, caller, value) of the current contract. + /// See [`pallet_revive_uapi::HostFn::delegate_call`]. + #[api_version(0)] + fn delegate_call( + &mut self, + memory: &mut M, + flags: u32, + code_hash_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + self.call( + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::DelegateCall { code_hash_ptr }, + input_data_ptr, + input_data_len, + output_ptr, + output_len_ptr, + ) + } + + /// Instantiate a contract with the specified code hash. + /// See [`pallet_revive_uapi::HostFn::instantiate`]. + #[api_version(0)] + #[mutating] + fn instantiate( + &mut self, + memory: &mut M, + code_hash_ptr: u32, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_ptr: u32, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + address_ptr: u32, + address_len_ptr: u32, + output_ptr: u32, + output_len_ptr: u32, + salt_ptr: u32, + salt_len: u32, + ) -> Result { + self.instantiate( + memory, + code_hash_ptr, + Weight::from_parts(ref_time_limit, proof_size_limit), + deposit_ptr, + value_ptr, + input_data_ptr, + input_data_len, + address_ptr, + address_len_ptr, + output_ptr, + output_len_ptr, + salt_ptr, + salt_len, + ) + } + + /// Remove the calling account and transfer remaining **free** balance. + /// See [`pallet_revive_uapi::HostFn::terminate`]. + #[api_version(0)] + #[mutating] + fn terminate(&mut self, memory: &mut M, beneficiary_ptr: u32) -> Result<(), TrapReason> { + self.terminate(memory, beneficiary_ptr) + } + + /// Stores the input passed by the caller into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::input`]. + #[api_version(0)] + fn input(&mut self, memory: &mut M, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + if let Some(input) = self.input_data.take() { + self.write_sandbox_output(memory, out_ptr, out_len_ptr, &input, false, |len| { + Some(RuntimeCosts::CopyToContract(len)) + })?; + self.input_data = Some(input); + Ok(()) + } else { + Err(Error::::InputForwarded.into()) + } + } + + /// Cease contract execution and save a data buffer as a result of the execution. + /// See [`pallet_revive_uapi::HostFn::return_value`]. + #[api_version(0)] + fn seal_return( + &mut self, + memory: &mut M, + flags: u32, + data_ptr: u32, + data_len: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::CopyFromContract(data_len))?; + Err(TrapReason::Return(ReturnData { flags, data: memory.read(data_ptr, data_len)? })) + } + + /// Stores the address of the caller into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::caller`]. + #[api_version(0)] + fn caller(&mut self, memory: &mut M, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Caller)?; + let caller = self.ext.caller().account_id()?.clone(); + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &caller.encode(), + false, + already_charged, + )?) + } + + /// Checks whether a specified address belongs to a contract. + /// See [`pallet_revive_uapi::HostFn::is_contract`]. + #[api_version(0)] + fn is_contract(&mut self, memory: &mut M, account_ptr: u32) -> Result { + self.charge_gas(RuntimeCosts::IsContract)?; + let address: <::T as frame_system::Config>::AccountId = + memory.read_as(account_ptr)?; + + Ok(self.ext.is_contract(&address) as u32) + } + + /// Retrieve the code hash for a specified contract address. + /// See [`pallet_revive_uapi::HostFn::code_hash`]. + #[api_version(0)] + fn code_hash( + &mut self, + memory: &mut M, + account_ptr: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::CodeHash)?; + let address: <::T as frame_system::Config>::AccountId = + memory.read_as(account_ptr)?; + if let Some(value) = self.ext.code_hash(&address) { + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &value.encode(), + false, + already_charged, + )?; + Ok(ReturnErrorCode::Success) + } else { + Ok(ReturnErrorCode::KeyNotFound) + } + } + + /// Retrieve the code hash of the currently executing contract. + /// See [`pallet_revive_uapi::HostFn::own_code_hash`]. + #[api_version(0)] + fn own_code_hash( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::OwnCodeHash)?; + let code_hash_encoded = &self.ext.own_code_hash().encode(); + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + code_hash_encoded, + false, + already_charged, + )?) + } + + /// Checks whether the caller of the current contract is the origin of the whole call stack. + /// See [`pallet_revive_uapi::HostFn::caller_is_origin`]. + #[api_version(0)] + fn caller_is_origin(&mut self, _memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::CallerIsOrigin)?; + Ok(self.ext.caller_is_origin() as u32) + } + + /// Checks whether the caller of the current contract is root. + /// See [`pallet_revive_uapi::HostFn::caller_is_root`]. + #[api_version(0)] + fn caller_is_root(&mut self, _memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::CallerIsRoot)?; + Ok(self.ext.caller_is_root() as u32) + } + + /// Stores the address of the current contract into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::address`]. + #[api_version(0)] + fn address( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Address)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.address().encode(), + false, + already_charged, + )?) + } + + /// Stores the price for the specified amount of weight into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::weight_to_fee`]. + #[api_version(0)] + fn weight_to_fee( + &mut self, + memory: &mut M, + ref_time_limit: u64, + proof_size_limit: u64, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + let weight = Weight::from_parts(ref_time_limit, proof_size_limit); + self.charge_gas(RuntimeCosts::WeightToFee)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.get_weight_price(weight).encode(), + false, + already_charged, + )?) + } + + /// Stores the amount of weight left into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::weight_left`]. + #[api_version(0)] + fn weight_left( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::GasLeft)?; + let gas_left = &self.ext.gas_meter().gas_left().encode(); + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + gas_left, + false, + already_charged, + )?) + } + + /// Stores the *free* balance of the current account into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::balance`]. + #[api_version(0)] + fn balance( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Balance)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.balance().encode(), + false, + already_charged, + )?) + } + + /// Stores the value transferred along with this call/instantiate into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::value_transferred`]. + #[api_version(0)] + fn value_transferred( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::ValueTransferred)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.value_transferred().encode(), + false, + already_charged, + )?) + } + + /// Load the latest block timestamp into the supplied buffer + /// See [`pallet_revive_uapi::HostFn::now`]. + #[api_version(0)] + fn now(&mut self, memory: &mut M, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Now)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.now().encode(), + false, + already_charged, + )?) + } + + /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::minimum_balance`]. + #[api_version(0)] + fn minimum_balance( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::MinimumBalance)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.minimum_balance().encode(), + false, + already_charged, + )?) + } + + /// Deposit a contract event with the data buffer and optional list of topics. + /// See [pallet_revive_uapi::HostFn::deposit_event] + #[api_version(0)] + #[mutating] + fn deposit_event( + &mut self, + memory: &mut M, + topics_ptr: u32, + topics_len: u32, + data_ptr: u32, + data_len: u32, + ) -> Result<(), TrapReason> { + let num_topic = topics_len + .checked_div(core::mem::size_of::>() as u32) + .ok_or("Zero sized topics are not allowed")?; + self.charge_gas(RuntimeCosts::DepositEvent { num_topic, len: data_len })?; + if data_len > self.ext.max_value_size() { + return Err(Error::::ValueTooLarge.into()); + } + + let topics: Vec::T>> = match topics_len { + 0 => Vec::new(), + _ => memory.read_as_unbounded(topics_ptr, topics_len)?, + }; + + // If there are more than `event_topics`, then trap. + if topics.len() as u32 > limits::NUM_EVENT_TOPICS { + return Err(Error::::TooManyTopics.into()); + } + + let event_data = memory.read(data_ptr, data_len)?; + + self.ext.deposit_event(topics, event_data); + + Ok(()) + } + + /// Stores the current block number of the current contract into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::block_number`]. + #[api_version(0)] + fn block_number( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::BlockNumber)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.block_number().encode(), + false, + already_charged, + )?) + } + + /// Computes the SHA2 256-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_sha2_256`]. + #[api_version(0)] + fn hash_sha2_256( + &mut self, + memory: &mut M, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::HashSha256(input_len))?; + Ok(self.compute_hash_on_intermediate_buffer( + memory, sha2_256, input_ptr, input_len, output_ptr, + )?) + } + + /// Computes the KECCAK 256-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_keccak_256`]. + #[api_version(0)] + fn hash_keccak_256( + &mut self, + memory: &mut M, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::HashKeccak256(input_len))?; + Ok(self.compute_hash_on_intermediate_buffer( + memory, keccak_256, input_ptr, input_len, output_ptr, + )?) + } + + /// Computes the BLAKE2 256-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_blake2_256`]. + #[api_version(0)] + fn hash_blake2_256( + &mut self, + memory: &mut M, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::HashBlake256(input_len))?; + Ok(self.compute_hash_on_intermediate_buffer( + memory, blake2_256, input_ptr, input_len, output_ptr, + )?) + } + + /// Computes the BLAKE2 128-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_blake2_128`]. + #[api_version(0)] + fn hash_blake2_128( + &mut self, + memory: &mut M, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::HashBlake128(input_len))?; + Ok(self.compute_hash_on_intermediate_buffer( + memory, blake2_128, input_ptr, input_len, output_ptr, + )?) + } + + /// Call into the chain extension provided by the chain if any. + /// See [`pallet_revive_uapi::HostFn::call_chain_extension`]. + fn call_chain_extension( + &mut self, + memory: &mut M, + id: u32, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + use crate::chain_extension::{ChainExtension, Environment, RetVal}; + if !::ChainExtension::enabled() { + return Err(Error::::NoChainExtension.into()); + } + let mut chain_extension = self.chain_extension.take().expect( + "Constructor initializes with `Some`. This is the only place where it is set to `None`.\ + It is always reset to `Some` afterwards. qed", + ); + let env = + Environment::new(self, memory, id, input_ptr, input_len, output_ptr, output_len_ptr); + let ret = match chain_extension.call(env)? { + RetVal::Converging(val) => Ok(val), + RetVal::Diverging { flags, data } => + Err(TrapReason::Return(ReturnData { flags: flags.bits(), data })), + }; + self.chain_extension = Some(chain_extension); + ret + } + + /// Emit a custom debug message. + /// See [`pallet_revive_uapi::HostFn::debug_message`]. + #[api_version(0)] + fn debug_message( + &mut self, + memory: &mut M, + str_ptr: u32, + str_len: u32, + ) -> Result { + let str_len = str_len.min(limits::DEBUG_BUFFER_BYTES); + self.charge_gas(RuntimeCosts::DebugMessage(str_len))?; + if self.ext.append_debug_buffer("") { + let data = memory.read(str_ptr, str_len)?; + if let Some(msg) = core::str::from_utf8(&data).ok() { + self.ext.append_debug_buffer(msg); + } + Ok(ReturnErrorCode::Success) + } else { + Ok(ReturnErrorCode::LoggingDisabled) + } + } + + /// Call some dispatchable of the runtime. + /// See [`frame_support::traits::call_runtime`]. + #[mutating] + fn call_runtime( + &mut self, + memory: &mut M, + call_ptr: u32, + call_len: u32, + ) -> Result { + use frame_support::dispatch::GetDispatchInfo; + self.charge_gas(RuntimeCosts::CopyFromContract(call_len))?; + let call: ::RuntimeCall = memory.read_as_unbounded(call_ptr, call_len)?; + self.call_dispatchable::( + call.get_dispatch_info(), + RuntimeCosts::CallRuntime, + |runtime| runtime.ext.call_runtime(call), + ) + } + + /// Execute an XCM program locally, using the contract's address as the origin. + /// See [`pallet_revive_uapi::HostFn::execute_xcm`]. + #[mutating] + fn xcm_execute( + &mut self, + memory: &mut M, + msg_ptr: u32, + msg_len: u32, + ) -> Result { + use frame_support::dispatch::DispatchInfo; + use xcm::VersionedXcm; + use xcm_builder::{ExecuteController, ExecuteControllerWeightInfo}; + + self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; + let message: VersionedXcm> = memory.read_as_unbounded(msg_ptr, msg_len)?; + + let execute_weight = + <::Xcm as ExecuteController<_, _>>::WeightInfo::execute(); + let weight = self.ext.gas_meter().gas_left().max(execute_weight); + let dispatch_info = DispatchInfo { weight, ..Default::default() }; + + self.call_dispatchable::( + dispatch_info, + RuntimeCosts::CallXcmExecute, + |runtime| { + let origin = crate::RawOrigin::Signed(runtime.ext.address().clone()).into(); + let weight_used = <::Xcm>::execute( + origin, + Box::new(message), + weight.saturating_sub(execute_weight), + )?; + + Ok(Some(weight_used.saturating_add(execute_weight)).into()) + }, + ) + } + + /// Send an XCM program from the contract to the specified destination. + /// See [`pallet_revive_uapi::HostFn::send_xcm`]. + #[mutating] + fn xcm_send( + &mut self, + memory: &mut M, + dest_ptr: u32, + msg_ptr: u32, + msg_len: u32, + output_ptr: u32, + ) -> Result { + use xcm::{VersionedLocation, VersionedXcm}; + use xcm_builder::{SendController, SendControllerWeightInfo}; + + self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; + let dest: VersionedLocation = memory.read_as(dest_ptr)?; + + let message: VersionedXcm<()> = memory.read_as_unbounded(msg_ptr, msg_len)?; + let weight = <::Xcm as SendController<_>>::WeightInfo::send(); + self.charge_gas(RuntimeCosts::CallRuntime(weight))?; + let origin = crate::RawOrigin::Signed(self.ext.address().clone()).into(); + + match <::Xcm>::send(origin, dest.into(), message.into()) { + Ok(message_id) => { + memory.write(output_ptr, &message_id.encode())?; + Ok(ReturnErrorCode::Success) + }, + Err(e) => { + if self.ext.append_debug_buffer("") { + self.ext.append_debug_buffer("seal0::xcm_send failed with: "); + self.ext.append_debug_buffer(e.into()); + }; + Ok(ReturnErrorCode::XcmSendFailed) + }, + } + } + + /// Recovers the ECDSA public key from the given message hash and signature. + /// See [`pallet_revive_uapi::HostFn::ecdsa_recover`]. + #[api_version(0)] + fn ecdsa_recover( + &mut self, + memory: &mut M, + signature_ptr: u32, + message_hash_ptr: u32, + output_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::EcdsaRecovery)?; + + let mut signature: [u8; 65] = [0; 65]; + memory.read_into_buf(signature_ptr, &mut signature)?; + let mut message_hash: [u8; 32] = [0; 32]; + memory.read_into_buf(message_hash_ptr, &mut message_hash)?; + + let result = self.ext.ecdsa_recover(&signature, &message_hash); + + match result { + Ok(pub_key) => { + // Write the recovered compressed ecdsa public key back into the sandboxed output + // buffer. + memory.write(output_ptr, pub_key.as_ref())?; + + Ok(ReturnErrorCode::Success) + }, + Err(_) => Ok(ReturnErrorCode::EcdsaRecoveryFailed), + } + } + + /// Verify a sr25519 signature + /// See [`pallet_revive_uapi::HostFn::sr25519_verify`]. + #[api_version(0)] + fn sr25519_verify( + &mut self, + memory: &mut M, + signature_ptr: u32, + pub_key_ptr: u32, + message_len: u32, + message_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::Sr25519Verify(message_len))?; + + let mut signature: [u8; 64] = [0; 64]; + memory.read_into_buf(signature_ptr, &mut signature)?; + + let mut pub_key: [u8; 32] = [0; 32]; + memory.read_into_buf(pub_key_ptr, &mut pub_key)?; + + let message: Vec = memory.read(message_ptr, message_len)?; + + if self.ext.sr25519_verify(&signature, &message, &pub_key) { + Ok(ReturnErrorCode::Success) + } else { + Ok(ReturnErrorCode::Sr25519VerifyFailed) + } + } + + /// Replace the contract code at the specified address with new code. + /// See [`pallet_revive_uapi::HostFn::set_code_hash`]. + #[api_version(0)] + #[mutating] + fn set_code_hash( + &mut self, + memory: &mut M, + code_hash_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::SetCodeHash)?; + let code_hash: CodeHash<::T> = memory.read_as(code_hash_ptr)?; + match self.ext.set_code_hash(code_hash) { + Err(err) => { + let code = Self::err_into_return_code(err)?; + Ok(code) + }, + Ok(()) => Ok(ReturnErrorCode::Success), + } + } + + /// Calculates Ethereum address from the ECDSA compressed public key and stores + /// See [`pallet_revive_uapi::HostFn::ecdsa_to_eth_address`]. + #[api_version(0)] + fn ecdsa_to_eth_address( + &mut self, + memory: &mut M, + key_ptr: u32, + out_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::EcdsaToEthAddress)?; + let mut compressed_key: [u8; 33] = [0; 33]; + memory.read_into_buf(key_ptr, &mut compressed_key)?; + let result = self.ext.ecdsa_to_eth_address(&compressed_key); + match result { + Ok(eth_address) => { + memory.write(out_ptr, eth_address.as_ref())?; + Ok(ReturnErrorCode::Success) + }, + Err(_) => Ok(ReturnErrorCode::EcdsaRecoveryFailed), + } + } + + /// Adds a new delegate dependency to the contract. + /// See [`pallet_revive_uapi::HostFn::lock_delegate_dependency`]. + #[api_version(0)] + #[mutating] + fn lock_delegate_dependency( + &mut self, + memory: &mut M, + code_hash_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::LockDelegateDependency)?; + let code_hash = memory.read_as(code_hash_ptr)?; + self.ext.lock_delegate_dependency(code_hash)?; + Ok(()) + } + + /// Removes the delegate dependency from the contract. + /// see [`pallet_revive_uapi::HostFn::unlock_delegate_dependency`]. + #[api_version(0)] + #[mutating] + fn unlock_delegate_dependency( + &mut self, + memory: &mut M, + code_hash_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::UnlockDelegateDependency)?; + let code_hash = memory.read_as(code_hash_ptr)?; + self.ext.unlock_delegate_dependency(&code_hash)?; + Ok(()) + } +} diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs new file mode 100644 index 000000000000..6a0d27529d87 --- /dev/null +++ b/substrate/frame/revive/src/weights.rs @@ -0,0 +1,2120 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_revive` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-07-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-yaoqqom-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/production/substrate-node +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_revive +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/contracts/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_revive`. +pub trait WeightInfo { + fn on_process_deletion_queue_batch() -> Weight; + fn on_initialize_per_trie_key(k: u32, ) -> Weight; + fn v9_migration_step(c: u32, ) -> Weight; + fn v10_migration_step() -> Weight; + fn v11_migration_step(k: u32, ) -> Weight; + fn v12_migration_step(c: u32, ) -> Weight; + fn v13_migration_step() -> Weight; + fn v14_migration_step() -> Weight; + fn v15_migration_step() -> Weight; + fn v16_migration_step() -> Weight; + fn migration_noop() -> Weight; + fn migrate() -> Weight; + fn on_runtime_upgrade_noop() -> Weight; + fn on_runtime_upgrade_in_progress() -> Weight; + fn on_runtime_upgrade() -> Weight; + fn call_with_code_per_byte(c: u32, ) -> Weight; + fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight; + fn instantiate(i: u32, s: u32, ) -> Weight; + fn call() -> Weight; + fn upload_code_determinism_enforced(c: u32, ) -> Weight; + fn upload_code_determinism_relaxed(c: u32, ) -> Weight; + fn remove_code() -> Weight; + fn set_code() -> Weight; + fn noop_host_fn(r: u32, ) -> Weight; + fn seal_caller() -> Weight; + fn seal_is_contract() -> Weight; + fn seal_code_hash() -> Weight; + fn seal_own_code_hash() -> Weight; + fn seal_caller_is_origin() -> Weight; + fn seal_caller_is_root() -> Weight; + fn seal_address() -> Weight; + fn seal_gas_left() -> Weight; + fn seal_balance() -> Weight; + fn seal_value_transferred() -> Weight; + fn seal_minimum_balance() -> Weight; + fn seal_block_number() -> Weight; + fn seal_now() -> Weight; + fn seal_weight_to_fee() -> Weight; + fn seal_input(n: u32, ) -> Weight; + fn seal_return(n: u32, ) -> Weight; + fn seal_terminate(n: u32, ) -> Weight; + fn seal_random() -> Weight; + fn seal_deposit_event(t: u32, n: u32, ) -> Weight; + fn seal_debug_message(i: u32, ) -> Weight; + fn get_storage_empty() -> Weight; + fn get_storage_full() -> Weight; + fn set_storage_empty() -> Weight; + fn set_storage_full() -> Weight; + fn seal_set_storage(n: u32, o: u32, ) -> Weight; + fn seal_clear_storage(n: u32, ) -> Weight; + fn seal_get_storage(n: u32, ) -> Weight; + fn seal_contains_storage(n: u32, ) -> Weight; + fn seal_take_storage(n: u32, ) -> Weight; + fn set_transient_storage_empty() -> Weight; + fn set_transient_storage_full() -> Weight; + fn get_transient_storage_empty() -> Weight; + fn get_transient_storage_full() -> Weight; + fn rollback_transient_storage() -> Weight; + fn seal_set_transient_storage(n: u32, o: u32, ) -> Weight; + fn seal_clear_transient_storage(n: u32, ) -> Weight; + fn seal_get_transient_storage(n: u32, ) -> Weight; + fn seal_contains_transient_storage(n: u32, ) -> Weight; + fn seal_take_transient_storage(n: u32, ) -> Weight; + fn seal_transfer() -> Weight; + fn seal_call(t: u32, i: u32, ) -> Weight; + fn seal_delegate_call() -> Weight; + fn seal_instantiate(i: u32, s: u32, ) -> Weight; + fn seal_hash_sha2_256(n: u32, ) -> Weight; + fn seal_hash_keccak_256(n: u32, ) -> Weight; + fn seal_hash_blake2_256(n: u32, ) -> Weight; + fn seal_hash_blake2_128(n: u32, ) -> Weight; + fn seal_sr25519_verify(n: u32, ) -> Weight; + fn seal_ecdsa_recover() -> Weight; + fn seal_ecdsa_to_eth_address() -> Weight; + fn seal_set_code_hash() -> Weight; + fn lock_delegate_dependency() -> Weight; + fn unlock_delegate_dependency() -> Weight; + fn seal_reentrance_count() -> Weight; + fn seal_account_reentrance_count() -> Weight; + fn seal_instantiation_nonce() -> Weight; + fn instr_i64_load_store(r: u32, ) -> Weight; +} + +/// Weights for `pallet_revive` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Contracts::DeletionQueueCounter` (r:1 w:0) + /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + fn on_process_deletion_queue_batch() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `1627` + // Minimum execution time: 1_915_000 picoseconds. + Weight::from_parts(1_986_000, 1627) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `k` is `[0, 1024]`. + fn on_initialize_per_trie_key(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `452 + k * (69 ±0)` + // Estimated: `442 + k * (70 ±0)` + // Minimum execution time: 11_103_000 picoseconds. + Weight::from_parts(11_326_000, 442) + // Standard Error: 2_291 + .saturating_add(Weight::from_parts(1_196_329, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 70).saturating_mul(k.into())) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) + /// The range of component `c` is `[0, 125952]`. + fn v9_migration_step(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `211 + c * (1 ±0)` + // Estimated: `6149 + c * (1 ±0)` + // Minimum execution time: 7_783_000 picoseconds. + Weight::from_parts(4_462_075, 6149) + // Standard Error: 5 + .saturating_add(Weight::from_parts(1_634, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn v10_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `510` + // Estimated: `6450` + // Minimum execution time: 15_971_000 picoseconds. + Weight::from_parts(16_730_000, 6450) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::DeletionQueue` (r:1 w:1025) + /// Proof: `Contracts::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) + /// Storage: `Contracts::DeletionQueueCounter` (r:0 w:1) + /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// The range of component `k` is `[0, 1024]`. + fn v11_migration_step(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + k * (1 ±0)` + // Estimated: `3635 + k * (1 ±0)` + // Minimum execution time: 3_149_000 picoseconds. + Weight::from_parts(3_264_000, 3635) + // Standard Error: 559 + .saturating_add(Weight::from_parts(1_111_209, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(k.into())) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:0 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn v12_migration_step(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `325 + c * (1 ±0)` + // Estimated: `6263 + c * (1 ±0)` + // Minimum execution time: 15_072_000 picoseconds. + Weight::from_parts(15_721_891, 6263) + // Standard Error: 2 + .saturating_add(Weight::from_parts(428, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn v13_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `440` + // Estimated: `6380` + // Minimum execution time: 12_047_000 picoseconds. + Weight::from_parts(12_500_000, 6380) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:2 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + fn v14_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `352` + // Estimated: `6292` + // Minimum execution time: 47_488_000 picoseconds. + Weight::from_parts(48_482_000, 6292) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn v15_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `594` + // Estimated: `6534` + // Minimum execution time: 52_801_000 picoseconds. + Weight::from_parts(54_230_000, 6534) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn v16_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `409` + // Estimated: `6349` + // Minimum execution time: 11_618_000 picoseconds. + Weight::from_parts(12_068_000, 6349) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + fn migration_noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `1627` + // Minimum execution time: 2_131_000 picoseconds. + Weight::from_parts(2_255_000, 1627) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) + fn migrate() -> Weight { + // Proof Size summary in bytes: + // Measured: `166` + // Estimated: `3631` + // Minimum execution time: 10_773_000 picoseconds. + Weight::from_parts(11_118_000, 3631) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + fn on_runtime_upgrade_noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 4_371_000 picoseconds. + Weight::from_parts(4_624_000, 3607) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + fn on_runtime_upgrade_in_progress() -> Weight { + // Proof Size summary in bytes: + // Measured: `167` + // Estimated: `3632` + // Minimum execution time: 5_612_000 picoseconds. + Weight::from_parts(5_838_000, 3632) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + fn on_runtime_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 5_487_000 picoseconds. + Weight::from_parts(5_693_000, 3607) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn call_with_code_per_byte(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `800 + c * (1 ±0)` + // Estimated: `4266 + c * (1 ±0)` + // Minimum execution time: 247_545_000 picoseconds. + Weight::from_parts(268_016_699, 4266) + // Standard Error: 4 + .saturating_add(Weight::from_parts(700, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:2 w:2) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::Nonce` (r:1 w:1) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + /// The range of component `i` is `[0, 1048576]`. + /// The range of component `s` is `[0, 1048576]`. + fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `323` + // Estimated: `6262` + // Minimum execution time: 4_396_772_000 picoseconds. + Weight::from_parts(235_107_907, 6262) + // Standard Error: 185 + .saturating_add(Weight::from_parts(53_843, 0).saturating_mul(c.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(2_143, 0).saturating_mul(i.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(2_210, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Contracts::Nonce` (r:1 w:1) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// The range of component `i` is `[0, 1048576]`. + /// The range of component `s` is `[0, 1048576]`. + fn instantiate(i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `560` + // Estimated: `4017` + // Minimum execution time: 2_240_868_000 picoseconds. + Weight::from_parts(2_273_668_000, 4017) + // Standard Error: 32 + .saturating_add(Weight::from_parts(934, 0).saturating_mul(i.into())) + // Standard Error: 32 + .saturating_add(Weight::from_parts(920, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn call() -> Weight { + // Proof Size summary in bytes: + // Measured: `826` + // Estimated: `4291` + // Minimum execution time: 165_067_000 picoseconds. + Weight::from_parts(168_582_000, 4291) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn upload_code_determinism_enforced(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 229_454_000 picoseconds. + Weight::from_parts(251_495_551, 3607) + // Standard Error: 71 + .saturating_add(Weight::from_parts(51_428, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn upload_code_determinism_relaxed(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 240_390_000 picoseconds. + Weight::from_parts(273_854_266, 3607) + // Standard Error: 243 + .saturating_add(Weight::from_parts(51_836, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + fn remove_code() -> Weight { + // Proof Size summary in bytes: + // Measured: `315` + // Estimated: `3780` + // Minimum execution time: 39_374_000 picoseconds. + Weight::from_parts(40_247_000, 3780) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:2 w:2) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + fn set_code() -> Weight { + // Proof Size summary in bytes: + // Measured: `552` + // Estimated: `6492` + // Minimum execution time: 24_473_000 picoseconds. + Weight::from_parts(25_890_000, 6492) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// The range of component `r` is `[0, 1600]`. + fn noop_host_fn(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_528_000 picoseconds. + Weight::from_parts(9_301_010, 0) + // Standard Error: 98 + .saturating_add(Weight::from_parts(53_173, 0).saturating_mul(r.into())) + } + fn seal_caller() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 643_000 picoseconds. + Weight::from_parts(678_000, 0) + } + /// Storage: `Contracts::ContractInfoOf` (r:1 w:0) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn seal_is_contract() -> Weight { + // Proof Size summary in bytes: + // Measured: `354` + // Estimated: `3819` + // Minimum execution time: 6_107_000 picoseconds. + Weight::from_parts(6_235_000, 3819) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Contracts::ContractInfoOf` (r:1 w:0) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn seal_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `3912` + // Minimum execution time: 7_316_000 picoseconds. + Weight::from_parts(7_653_000, 3912) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + fn seal_own_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 721_000 picoseconds. + Weight::from_parts(764_000, 0) + } + fn seal_caller_is_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 369_000 picoseconds. + Weight::from_parts(417_000, 0) + } + fn seal_caller_is_root() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 318_000 picoseconds. + Weight::from_parts(349_000, 0) + } + fn seal_address() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 590_000 picoseconds. + Weight::from_parts(628_000, 0) + } + fn seal_gas_left() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(730_000, 0) + } + fn seal_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `140` + // Estimated: `0` + // Minimum execution time: 4_361_000 picoseconds. + Weight::from_parts(4_577_000, 0) + } + fn seal_value_transferred() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 560_000 picoseconds. + Weight::from_parts(603_000, 0) + } + fn seal_minimum_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 561_000 picoseconds. + Weight::from_parts(610_000, 0) + } + fn seal_block_number() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 557_000 picoseconds. + Weight::from_parts(583_000, 0) + } + fn seal_now() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 550_000 picoseconds. + Weight::from_parts(602_000, 0) + } + /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) + /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `Measured`) + fn seal_weight_to_fee() -> Weight { + // Proof Size summary in bytes: + // Measured: `67` + // Estimated: `1552` + // Minimum execution time: 4_065_000 picoseconds. + Weight::from_parts(4_291_000, 1552) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// The range of component `n` is `[0, 1048572]`. + fn seal_input(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 487_000 picoseconds. + Weight::from_parts(517_000, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(301, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048572]`. + fn seal_return(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 318_000 picoseconds. + Weight::from_parts(372_000, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(411, 0).saturating_mul(n.into())) + } + /// Storage: `Contracts::DeletionQueueCounter` (r:1 w:1) + /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:33 w:33) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::DeletionQueue` (r:0 w:1) + /// Proof: `Contracts::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) + /// The range of component `n` is `[0, 32]`. + fn seal_terminate(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `319 + n * (78 ±0)` + // Estimated: `3784 + n * (2553 ±0)` + // Minimum execution time: 13_251_000 picoseconds. + Weight::from_parts(15_257_892, 3784) + // Standard Error: 7_089 + .saturating_add(Weight::from_parts(3_443_907, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2553).saturating_mul(n.into())) + } + /// Storage: `RandomnessCollectiveFlip::RandomMaterial` (r:1 w:0) + /// Proof: `RandomnessCollectiveFlip::RandomMaterial` (`max_values`: Some(1), `max_size`: Some(2594), added: 3089, mode: `Measured`) + fn seal_random() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1561` + // Minimum execution time: 3_434_000 picoseconds. + Weight::from_parts(3_605_000, 1561) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `System::EventTopics` (r:4 w:4) + /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `t` is `[0, 4]`. + /// The range of component `n` is `[0, 16384]`. + fn seal_deposit_event(t: u32, n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `990 + t * (2475 ±0)` + // Minimum execution time: 3_668_000 picoseconds. + Weight::from_parts(3_999_591, 990) + // Standard Error: 5_767 + .saturating_add(Weight::from_parts(2_011_090, 0).saturating_mul(t.into())) + // Standard Error: 1 + .saturating_add(Weight::from_parts(12, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) + .saturating_add(Weight::from_parts(0, 2475).saturating_mul(t.into())) + } + /// The range of component `i` is `[0, 1048576]`. + fn seal_debug_message(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 443_000 picoseconds. + Weight::from_parts(472_000, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(1_207, 0).saturating_mul(i.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn get_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `16618` + // Estimated: `16618` + // Minimum execution time: 13_752_000 picoseconds. + Weight::from_parts(14_356_000, 16618) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn get_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `26628` + // Estimated: `26628` + // Minimum execution time: 43_444_000 picoseconds. + Weight::from_parts(45_087_000, 26628) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `16618` + // Estimated: `16618` + // Minimum execution time: 15_616_000 picoseconds. + Weight::from_parts(16_010_000, 16618) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `26628` + // Estimated: `26628` + // Minimum execution time: 47_020_000 picoseconds. + Weight::from_parts(50_152_000, 26628) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + /// The range of component `o` is `[0, 16384]`. + fn seal_set_storage(n: u32, o: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `250 + o * (1 ±0)` + // Estimated: `249 + o * (1 ±0)` + // Minimum execution time: 8_824_000 picoseconds. + Weight::from_parts(8_915_233, 249) + // Standard Error: 1 + .saturating_add(Weight::from_parts(255, 0).saturating_mul(n.into())) + // Standard Error: 1 + .saturating_add(Weight::from_parts(39, 0).saturating_mul(o.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_clear_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 7_133_000 picoseconds. + Weight::from_parts(7_912_778, 248) + // Standard Error: 1 + .saturating_add(Weight::from_parts(88, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_get_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 6_746_000 picoseconds. + Weight::from_parts(7_647_236, 248) + // Standard Error: 2 + .saturating_add(Weight::from_parts(603, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_contains_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 6_247_000 picoseconds. + Weight::from_parts(6_952_661, 248) + // Standard Error: 1 + .saturating_add(Weight::from_parts(77, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_take_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 7_428_000 picoseconds. + Weight::from_parts(8_384_015, 248) + // Standard Error: 2 + .saturating_add(Weight::from_parts(625, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + fn set_transient_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_478_000 picoseconds. + Weight::from_parts(1_533_000, 0) + } + fn set_transient_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_485_000 picoseconds. + Weight::from_parts(2_728_000, 0) + } + fn get_transient_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_195_000 picoseconds. + Weight::from_parts(3_811_000, 0) + } + fn get_transient_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_902_000 picoseconds. + Weight::from_parts(4_118_000, 0) + } + fn rollback_transient_storage() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_571_000 picoseconds. + Weight::from_parts(1_662_000, 0) + } + /// The range of component `n` is `[0, 16384]`. + /// The range of component `o` is `[0, 16384]`. + fn seal_set_transient_storage(n: u32, o: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_250_000 picoseconds. + Weight::from_parts(2_465_568, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(201, 0).saturating_mul(n.into())) + // Standard Error: 0 + .saturating_add(Weight::from_parts(223, 0).saturating_mul(o.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_clear_transient_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_012_000 picoseconds. + Weight::from_parts(2_288_004, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(239, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_get_transient_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_906_000 picoseconds. + Weight::from_parts(2_121_040, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(225, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_contains_transient_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_736_000 picoseconds. + Weight::from_parts(1_954_728, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(111, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_take_transient_storage(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_872_000 picoseconds. + Weight::from_parts(8_125_644, 0) + } + fn seal_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `140` + // Estimated: `0` + // Minimum execution time: 8_489_000 picoseconds. + Weight::from_parts(8_791_000, 0) + } + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// The range of component `t` is `[0, 1]`. + /// The range of component `i` is `[0, 1048576]`. + fn seal_call(t: u32, i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `620 + t * (280 ±0)` + // Estimated: `4085 + t * (2182 ±0)` + // Minimum execution time: 122_759_000 picoseconds. + Weight::from_parts(120_016_020, 4085) + // Standard Error: 173_118 + .saturating_add(Weight::from_parts(42_848_338, 0).saturating_mul(t.into())) + // Standard Error: 0 + .saturating_add(Weight::from_parts(6, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) + .saturating_add(Weight::from_parts(0, 2182).saturating_mul(t.into())) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + fn seal_delegate_call() -> Weight { + // Proof Size summary in bytes: + // Measured: `430` + // Estimated: `3895` + // Minimum execution time: 111_566_000 picoseconds. + Weight::from_parts(115_083_000, 3895) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Contracts::Nonce` (r:1 w:0) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// The range of component `i` is `[0, 983040]`. + /// The range of component `s` is `[0, 983040]`. + fn seal_instantiate(i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `676` + // Estimated: `4132` + // Minimum execution time: 1_871_402_000 picoseconds. + Weight::from_parts(1_890_038_000, 4132) + // Standard Error: 24 + .saturating_add(Weight::from_parts(581, 0).saturating_mul(i.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(915, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_sha2_256(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 966_000 picoseconds. + Weight::from_parts(9_599_151, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_336, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_keccak_256(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_416_000 picoseconds. + Weight::from_parts(10_964_255, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(3_593, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_blake2_256(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 821_000 picoseconds. + Weight::from_parts(6_579_283, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_466, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_blake2_128(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 773_000 picoseconds. + Weight::from_parts(10_990_209, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_457, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 125697]`. + fn seal_sr25519_verify(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 43_195_000 picoseconds. + Weight::from_parts(41_864_855, 0) + // Standard Error: 9 + .saturating_add(Weight::from_parts(5_154, 0).saturating_mul(n.into())) + } + fn seal_ecdsa_recover() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 47_747_000 picoseconds. + Weight::from_parts(49_219_000, 0) + } + fn seal_ecdsa_to_eth_address() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 12_854_000 picoseconds. + Weight::from_parts(12_962_000, 0) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + fn seal_set_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `430` + // Estimated: `3895` + // Minimum execution time: 17_868_000 picoseconds. + Weight::from_parts(18_486_000, 3895) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + fn lock_delegate_dependency() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `3820` + // Minimum execution time: 8_393_000 picoseconds. + Weight::from_parts(8_640_000, 3820) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `MaxEncodedLen`) + fn unlock_delegate_dependency() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `3558` + // Minimum execution time: 7_489_000 picoseconds. + Weight::from_parts(7_815_000, 3558) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + fn seal_reentrance_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 299_000 picoseconds. + Weight::from_parts(339_000, 0) + } + fn seal_account_reentrance_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 324_000 picoseconds. + Weight::from_parts(380_000, 0) + } + /// Storage: `Contracts::Nonce` (r:1 w:0) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + fn seal_instantiation_nonce() -> Weight { + // Proof Size summary in bytes: + // Measured: `219` + // Estimated: `1704` + // Minimum execution time: 2_768_000 picoseconds. + Weight::from_parts(3_025_000, 1704) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// The range of component `r` is `[0, 5000]`. + fn instr_i64_load_store(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 766_000 picoseconds. + Weight::from_parts(722_169, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(7_191, 0).saturating_mul(r.into())) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Contracts::DeletionQueueCounter` (r:1 w:0) + /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + fn on_process_deletion_queue_batch() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `1627` + // Minimum execution time: 1_915_000 picoseconds. + Weight::from_parts(1_986_000, 1627) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `k` is `[0, 1024]`. + fn on_initialize_per_trie_key(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `452 + k * (69 ±0)` + // Estimated: `442 + k * (70 ±0)` + // Minimum execution time: 11_103_000 picoseconds. + Weight::from_parts(11_326_000, 442) + // Standard Error: 2_291 + .saturating_add(Weight::from_parts(1_196_329, 0).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 70).saturating_mul(k.into())) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) + /// The range of component `c` is `[0, 125952]`. + fn v9_migration_step(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `211 + c * (1 ±0)` + // Estimated: `6149 + c * (1 ±0)` + // Minimum execution time: 7_783_000 picoseconds. + Weight::from_parts(4_462_075, 6149) + // Standard Error: 5 + .saturating_add(Weight::from_parts(1_634, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn v10_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `510` + // Estimated: `6450` + // Minimum execution time: 15_971_000 picoseconds. + Weight::from_parts(16_730_000, 6450) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::DeletionQueue` (r:1 w:1025) + /// Proof: `Contracts::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) + /// Storage: `Contracts::DeletionQueueCounter` (r:0 w:1) + /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// The range of component `k` is `[0, 1024]`. + fn v11_migration_step(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + k * (1 ±0)` + // Estimated: `3635 + k * (1 ±0)` + // Minimum execution time: 3_149_000 picoseconds. + Weight::from_parts(3_264_000, 3635) + // Standard Error: 559 + .saturating_add(Weight::from_parts(1_111_209, 0).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(k.into())) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:0 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn v12_migration_step(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `325 + c * (1 ±0)` + // Estimated: `6263 + c * (1 ±0)` + // Minimum execution time: 15_072_000 picoseconds. + Weight::from_parts(15_721_891, 6263) + // Standard Error: 2 + .saturating_add(Weight::from_parts(428, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn v13_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `440` + // Estimated: `6380` + // Minimum execution time: 12_047_000 picoseconds. + Weight::from_parts(12_500_000, 6380) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:2 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + fn v14_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `352` + // Estimated: `6292` + // Minimum execution time: 47_488_000 picoseconds. + Weight::from_parts(48_482_000, 6292) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn v15_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `594` + // Estimated: `6534` + // Minimum execution time: 52_801_000 picoseconds. + Weight::from_parts(54_230_000, 6534) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn v16_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `409` + // Estimated: `6349` + // Minimum execution time: 11_618_000 picoseconds. + Weight::from_parts(12_068_000, 6349) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + fn migration_noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `1627` + // Minimum execution time: 2_131_000 picoseconds. + Weight::from_parts(2_255_000, 1627) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) + fn migrate() -> Weight { + // Proof Size summary in bytes: + // Measured: `166` + // Estimated: `3631` + // Minimum execution time: 10_773_000 picoseconds. + Weight::from_parts(11_118_000, 3631) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + fn on_runtime_upgrade_noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 4_371_000 picoseconds. + Weight::from_parts(4_624_000, 3607) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + fn on_runtime_upgrade_in_progress() -> Weight { + // Proof Size summary in bytes: + // Measured: `167` + // Estimated: `3632` + // Minimum execution time: 5_612_000 picoseconds. + Weight::from_parts(5_838_000, 3632) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + fn on_runtime_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 5_487_000 picoseconds. + Weight::from_parts(5_693_000, 3607) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn call_with_code_per_byte(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `800 + c * (1 ±0)` + // Estimated: `4266 + c * (1 ±0)` + // Minimum execution time: 247_545_000 picoseconds. + Weight::from_parts(268_016_699, 4266) + // Standard Error: 4 + .saturating_add(Weight::from_parts(700, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:2 w:2) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::Nonce` (r:1 w:1) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + /// The range of component `i` is `[0, 1048576]`. + /// The range of component `s` is `[0, 1048576]`. + fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `323` + // Estimated: `6262` + // Minimum execution time: 4_396_772_000 picoseconds. + Weight::from_parts(235_107_907, 6262) + // Standard Error: 185 + .saturating_add(Weight::from_parts(53_843, 0).saturating_mul(c.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(2_143, 0).saturating_mul(i.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(2_210, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Contracts::Nonce` (r:1 w:1) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// The range of component `i` is `[0, 1048576]`. + /// The range of component `s` is `[0, 1048576]`. + fn instantiate(i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `560` + // Estimated: `4017` + // Minimum execution time: 2_240_868_000 picoseconds. + Weight::from_parts(2_273_668_000, 4017) + // Standard Error: 32 + .saturating_add(Weight::from_parts(934, 0).saturating_mul(i.into())) + // Standard Error: 32 + .saturating_add(Weight::from_parts(920, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn call() -> Weight { + // Proof Size summary in bytes: + // Measured: `826` + // Estimated: `4291` + // Minimum execution time: 165_067_000 picoseconds. + Weight::from_parts(168_582_000, 4291) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn upload_code_determinism_enforced(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 229_454_000 picoseconds. + Weight::from_parts(251_495_551, 3607) + // Standard Error: 71 + .saturating_add(Weight::from_parts(51_428, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn upload_code_determinism_relaxed(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 240_390_000 picoseconds. + Weight::from_parts(273_854_266, 3607) + // Standard Error: 243 + .saturating_add(Weight::from_parts(51_836, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + fn remove_code() -> Weight { + // Proof Size summary in bytes: + // Measured: `315` + // Estimated: `3780` + // Minimum execution time: 39_374_000 picoseconds. + Weight::from_parts(40_247_000, 3780) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:2 w:2) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + fn set_code() -> Weight { + // Proof Size summary in bytes: + // Measured: `552` + // Estimated: `6492` + // Minimum execution time: 24_473_000 picoseconds. + Weight::from_parts(25_890_000, 6492) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// The range of component `r` is `[0, 1600]`. + fn noop_host_fn(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_528_000 picoseconds. + Weight::from_parts(9_301_010, 0) + // Standard Error: 98 + .saturating_add(Weight::from_parts(53_173, 0).saturating_mul(r.into())) + } + fn seal_caller() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 643_000 picoseconds. + Weight::from_parts(678_000, 0) + } + /// Storage: `Contracts::ContractInfoOf` (r:1 w:0) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn seal_is_contract() -> Weight { + // Proof Size summary in bytes: + // Measured: `354` + // Estimated: `3819` + // Minimum execution time: 6_107_000 picoseconds. + Weight::from_parts(6_235_000, 3819) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Contracts::ContractInfoOf` (r:1 w:0) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn seal_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `3912` + // Minimum execution time: 7_316_000 picoseconds. + Weight::from_parts(7_653_000, 3912) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + fn seal_own_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 721_000 picoseconds. + Weight::from_parts(764_000, 0) + } + fn seal_caller_is_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 369_000 picoseconds. + Weight::from_parts(417_000, 0) + } + fn seal_caller_is_root() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 318_000 picoseconds. + Weight::from_parts(349_000, 0) + } + fn seal_address() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 590_000 picoseconds. + Weight::from_parts(628_000, 0) + } + fn seal_gas_left() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(730_000, 0) + } + fn seal_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `140` + // Estimated: `0` + // Minimum execution time: 4_361_000 picoseconds. + Weight::from_parts(4_577_000, 0) + } + fn seal_value_transferred() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 560_000 picoseconds. + Weight::from_parts(603_000, 0) + } + fn seal_minimum_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 561_000 picoseconds. + Weight::from_parts(610_000, 0) + } + fn seal_block_number() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 557_000 picoseconds. + Weight::from_parts(583_000, 0) + } + fn seal_now() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 550_000 picoseconds. + Weight::from_parts(602_000, 0) + } + /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) + /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `Measured`) + fn seal_weight_to_fee() -> Weight { + // Proof Size summary in bytes: + // Measured: `67` + // Estimated: `1552` + // Minimum execution time: 4_065_000 picoseconds. + Weight::from_parts(4_291_000, 1552) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// The range of component `n` is `[0, 1048572]`. + fn seal_input(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 487_000 picoseconds. + Weight::from_parts(517_000, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(301, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048572]`. + fn seal_return(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 318_000 picoseconds. + Weight::from_parts(372_000, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(411, 0).saturating_mul(n.into())) + } + /// Storage: `Contracts::DeletionQueueCounter` (r:1 w:1) + /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:33 w:33) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::DeletionQueue` (r:0 w:1) + /// Proof: `Contracts::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) + /// The range of component `n` is `[0, 32]`. + fn seal_terminate(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `319 + n * (78 ±0)` + // Estimated: `3784 + n * (2553 ±0)` + // Minimum execution time: 13_251_000 picoseconds. + Weight::from_parts(15_257_892, 3784) + // Standard Error: 7_089 + .saturating_add(Weight::from_parts(3_443_907, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2553).saturating_mul(n.into())) + } + /// Storage: `RandomnessCollectiveFlip::RandomMaterial` (r:1 w:0) + /// Proof: `RandomnessCollectiveFlip::RandomMaterial` (`max_values`: Some(1), `max_size`: Some(2594), added: 3089, mode: `Measured`) + fn seal_random() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1561` + // Minimum execution time: 3_434_000 picoseconds. + Weight::from_parts(3_605_000, 1561) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `System::EventTopics` (r:4 w:4) + /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `t` is `[0, 4]`. + /// The range of component `n` is `[0, 16384]`. + fn seal_deposit_event(t: u32, n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `990 + t * (2475 ±0)` + // Minimum execution time: 3_668_000 picoseconds. + Weight::from_parts(3_999_591, 990) + // Standard Error: 5_767 + .saturating_add(Weight::from_parts(2_011_090, 0).saturating_mul(t.into())) + // Standard Error: 1 + .saturating_add(Weight::from_parts(12, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(t.into()))) + .saturating_add(Weight::from_parts(0, 2475).saturating_mul(t.into())) + } + /// The range of component `i` is `[0, 1048576]`. + fn seal_debug_message(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 443_000 picoseconds. + Weight::from_parts(472_000, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(1_207, 0).saturating_mul(i.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn get_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `16618` + // Estimated: `16618` + // Minimum execution time: 13_752_000 picoseconds. + Weight::from_parts(14_356_000, 16618) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn get_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `26628` + // Estimated: `26628` + // Minimum execution time: 43_444_000 picoseconds. + Weight::from_parts(45_087_000, 26628) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `16618` + // Estimated: `16618` + // Minimum execution time: 15_616_000 picoseconds. + Weight::from_parts(16_010_000, 16618) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `26628` + // Estimated: `26628` + // Minimum execution time: 47_020_000 picoseconds. + Weight::from_parts(50_152_000, 26628) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + /// The range of component `o` is `[0, 16384]`. + fn seal_set_storage(n: u32, o: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `250 + o * (1 ±0)` + // Estimated: `249 + o * (1 ±0)` + // Minimum execution time: 8_824_000 picoseconds. + Weight::from_parts(8_915_233, 249) + // Standard Error: 1 + .saturating_add(Weight::from_parts(255, 0).saturating_mul(n.into())) + // Standard Error: 1 + .saturating_add(Weight::from_parts(39, 0).saturating_mul(o.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_clear_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 7_133_000 picoseconds. + Weight::from_parts(7_912_778, 248) + // Standard Error: 1 + .saturating_add(Weight::from_parts(88, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_get_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 6_746_000 picoseconds. + Weight::from_parts(7_647_236, 248) + // Standard Error: 2 + .saturating_add(Weight::from_parts(603, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_contains_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 6_247_000 picoseconds. + Weight::from_parts(6_952_661, 248) + // Standard Error: 1 + .saturating_add(Weight::from_parts(77, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_take_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 7_428_000 picoseconds. + Weight::from_parts(8_384_015, 248) + // Standard Error: 2 + .saturating_add(Weight::from_parts(625, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + fn set_transient_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_478_000 picoseconds. + Weight::from_parts(1_533_000, 0) + } + fn set_transient_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_485_000 picoseconds. + Weight::from_parts(2_728_000, 0) + } + fn get_transient_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_195_000 picoseconds. + Weight::from_parts(3_811_000, 0) + } + fn get_transient_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_902_000 picoseconds. + Weight::from_parts(4_118_000, 0) + } + fn rollback_transient_storage() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_571_000 picoseconds. + Weight::from_parts(1_662_000, 0) + } + /// The range of component `n` is `[0, 16384]`. + /// The range of component `o` is `[0, 16384]`. + fn seal_set_transient_storage(n: u32, o: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_250_000 picoseconds. + Weight::from_parts(2_465_568, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(201, 0).saturating_mul(n.into())) + // Standard Error: 0 + .saturating_add(Weight::from_parts(223, 0).saturating_mul(o.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_clear_transient_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_012_000 picoseconds. + Weight::from_parts(2_288_004, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(239, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_get_transient_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_906_000 picoseconds. + Weight::from_parts(2_121_040, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(225, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_contains_transient_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_736_000 picoseconds. + Weight::from_parts(1_954_728, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(111, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_take_transient_storage(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_872_000 picoseconds. + Weight::from_parts(8_125_644, 0) + } + fn seal_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `140` + // Estimated: `0` + // Minimum execution time: 8_489_000 picoseconds. + Weight::from_parts(8_791_000, 0) + } + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// The range of component `t` is `[0, 1]`. + /// The range of component `i` is `[0, 1048576]`. + fn seal_call(t: u32, i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `620 + t * (280 ±0)` + // Estimated: `4085 + t * (2182 ±0)` + // Minimum execution time: 122_759_000 picoseconds. + Weight::from_parts(120_016_020, 4085) + // Standard Error: 173_118 + .saturating_add(Weight::from_parts(42_848_338, 0).saturating_mul(t.into())) + // Standard Error: 0 + .saturating_add(Weight::from_parts(6, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(t.into()))) + .saturating_add(Weight::from_parts(0, 2182).saturating_mul(t.into())) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + fn seal_delegate_call() -> Weight { + // Proof Size summary in bytes: + // Measured: `430` + // Estimated: `3895` + // Minimum execution time: 111_566_000 picoseconds. + Weight::from_parts(115_083_000, 3895) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Contracts::Nonce` (r:1 w:0) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// The range of component `i` is `[0, 983040]`. + /// The range of component `s` is `[0, 983040]`. + fn seal_instantiate(i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `676` + // Estimated: `4132` + // Minimum execution time: 1_871_402_000 picoseconds. + Weight::from_parts(1_890_038_000, 4132) + // Standard Error: 24 + .saturating_add(Weight::from_parts(581, 0).saturating_mul(i.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(915, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_sha2_256(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 966_000 picoseconds. + Weight::from_parts(9_599_151, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_336, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_keccak_256(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_416_000 picoseconds. + Weight::from_parts(10_964_255, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(3_593, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_blake2_256(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 821_000 picoseconds. + Weight::from_parts(6_579_283, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_466, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_blake2_128(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 773_000 picoseconds. + Weight::from_parts(10_990_209, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_457, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 125697]`. + fn seal_sr25519_verify(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 43_195_000 picoseconds. + Weight::from_parts(41_864_855, 0) + // Standard Error: 9 + .saturating_add(Weight::from_parts(5_154, 0).saturating_mul(n.into())) + } + fn seal_ecdsa_recover() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 47_747_000 picoseconds. + Weight::from_parts(49_219_000, 0) + } + fn seal_ecdsa_to_eth_address() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 12_854_000 picoseconds. + Weight::from_parts(12_962_000, 0) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + fn seal_set_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `430` + // Estimated: `3895` + // Minimum execution time: 17_868_000 picoseconds. + Weight::from_parts(18_486_000, 3895) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + fn lock_delegate_dependency() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `3820` + // Minimum execution time: 8_393_000 picoseconds. + Weight::from_parts(8_640_000, 3820) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `MaxEncodedLen`) + fn unlock_delegate_dependency() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `3558` + // Minimum execution time: 7_489_000 picoseconds. + Weight::from_parts(7_815_000, 3558) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + fn seal_reentrance_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 299_000 picoseconds. + Weight::from_parts(339_000, 0) + } + fn seal_account_reentrance_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 324_000 picoseconds. + Weight::from_parts(380_000, 0) + } + /// Storage: `Contracts::Nonce` (r:1 w:0) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + fn seal_instantiation_nonce() -> Weight { + // Proof Size summary in bytes: + // Measured: `219` + // Estimated: `1704` + // Minimum execution time: 2_768_000 picoseconds. + Weight::from_parts(3_025_000, 1704) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// The range of component `r` is `[0, 5000]`. + fn instr_i64_load_store(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 766_000 picoseconds. + Weight::from_parts(722_169, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(7_191, 0).saturating_mul(r.into())) + } +} diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml new file mode 100644 index 000000000000..862bf36f07cd --- /dev/null +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "pallet-revive-uapi" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "Exposes all the host functions that a contract can import." + +[lints] +workspace = true + +[dependencies] +paste = { workspace = true } +bitflags = { workspace = true } +scale-info = { features = ["derive"], optional = true, workspace = true } +codec = { features = [ + "derive", + "max-encoded-len", +], optional = true, workspace = true } + +[target.'cfg(target_arch = "riscv32")'.dependencies] +polkavm-derive = { version = "0.10.0" } + +[package.metadata.docs.rs] +default-target = ["wasm32-unknown-unknown"] + +[features] +default = ["scale"] +scale = ["dep:codec", "scale-info"] diff --git a/substrate/frame/revive/uapi/src/flags.rs b/substrate/frame/revive/uapi/src/flags.rs new file mode 100644 index 000000000000..17b91ce2b3be --- /dev/null +++ b/substrate/frame/revive/uapi/src/flags.rs @@ -0,0 +1,89 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use bitflags::bitflags; + +bitflags! { + /// Flags used by a contract to customize exit behaviour. + #[cfg_attr(feature = "scale", derive(codec::Encode, codec::Decode, scale_info::TypeInfo))] + pub struct ReturnFlags: u32 { + /// If this bit is set all changes made by the contract execution are rolled back. + const REVERT = 0x0000_0001; + } +} + +bitflags! { + /// Flags used to change the behaviour of `seal_call` and `seal_delegate_call`. + pub struct CallFlags: u32 { + /// Forward the input of current function to the callee. + /// + /// Supplied input pointers are ignored when set. + /// + /// # Note + /// + /// A forwarding call will consume the current contracts input. Any attempt to + /// access the input after this call returns will lead to [`Error::InputForwarded`]. + /// It does not matter if this is due to calling `seal_input` or trying another + /// forwarding call. Consider using [`Self::CLONE_INPUT`] in order to preserve + /// the input. + const FORWARD_INPUT = 0b0000_0001; + /// Identical to [`Self::FORWARD_INPUT`] but without consuming the input. + /// + /// This adds some additional weight costs to the call. + /// + /// # Note + /// + /// This implies [`Self::FORWARD_INPUT`] and takes precedence when both are set. + const CLONE_INPUT = 0b0000_0010; + /// Do not return from the call but rather return the result of the callee to the + /// callers caller. + /// + /// # Note + /// + /// This makes the current contract completely transparent to its caller by replacing + /// this contracts potential output by the callee ones. Any code after `seal_call` + /// can be safely considered unreachable. + const TAIL_CALL = 0b0000_0100; + /// Allow the callee to reenter into the current contract. + /// + /// Without this flag any reentrancy into the current contract that originates from + /// the callee (or any of its callees) is denied. This includes the first callee: + /// You cannot call into yourself with this flag set. + /// + /// # Note + /// + /// For `seal_delegate_call` should be always unset, otherwise + /// [`Error::InvalidCallFlags`] is returned. + const ALLOW_REENTRY = 0b0000_1000; + /// Indicates that the callee is restricted from modifying the state during call execution, + /// equivalent to Ethereum's STATICCALL. + /// + /// # Note + /// + /// For `seal_delegate_call` should be always unset, otherwise + /// [`Error::InvalidCallFlags`] is returned. + const READ_ONLY = 0b0001_0000; + } +} + +bitflags! { + /// Flags used by a contract to customize storage behaviour. + pub struct StorageFlags: u32 { + /// Access the transient storage instead of the persistent one. + const TRANSIENT = 0x0000_0001; + } +} diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs new file mode 100644 index 000000000000..6eb662363f7c --- /dev/null +++ b/substrate/frame/revive/uapi/src/host.rs @@ -0,0 +1,643 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::{CallFlags, Result, ReturnFlags, StorageFlags}; +use paste::paste; + +#[cfg(target_arch = "riscv32")] +mod riscv32; + +macro_rules! hash_fn { + ( $name:ident, $bytes:literal ) => { + paste! { + #[doc = "Computes the " $name " " $bytes "-bit hash on the given input buffer."] + #[doc = "\n# Notes\n"] + #[doc = "- The `input` and `output` buffer may overlap."] + #[doc = "- The output buffer is expected to hold at least " $bytes " bits."] + #[doc = "- It is the callers responsibility to provide an output buffer that is large enough to hold the expected amount of bytes returned by the hash function."] + #[doc = "\n# Parameters\n"] + #[doc = "- `input`: The input data buffer."] + #[doc = "- `output`: The output buffer to write the hash result to."] + fn [](input: &[u8], output: &mut [u8; $bytes]); + } + }; +} + +/// Implements [`HostFn`] when compiled on supported architectures (RISC-V). +pub enum HostFnImpl {} + +/// Defines all the host apis available to contracts. +pub trait HostFn: private::Sealed { + /// Stores the address of the current contract into the supplied buffer. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the address. + fn address(output: &mut &mut [u8]); + + /// Lock a new delegate dependency to the contract. + /// + /// Traps if the maximum number of delegate_dependencies is reached or if + /// the delegate dependency already exists. + /// + /// # Parameters + /// + /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps + /// otherwise. + fn lock_delegate_dependency(code_hash: &[u8]); + + /// Stores the *free* balance of the current account into the supplied buffer. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the balance. + fn balance(output: &mut &mut [u8]); + + /// Stores the current block number of the current contract into the supplied buffer. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the block number. + fn block_number(output: &mut &mut [u8]); + + /// Call (possibly transferring some amount of funds) into the specified account. + /// + /// # Parameters + /// + /// - `flags`: See [`CallFlags`] for a documentation of the supported flags. + /// - `callee`: The address of the callee. Should be decodable as an `T::AccountId`. Traps + /// otherwise. + /// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution. + /// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution. + /// - `deposit`: The storage deposit limit for instantiation. Should be decodable as a + /// `Option`. Traps otherwise. Passing `None` means setting no specific limit for + /// the call, which implies storage usage up to the limit of the parent call. + /// - `value`: The value to transfer into the contract. Should be decodable as a `T::Balance`. + /// Traps otherwise. + /// - `input`: The input data buffer used to call the contract. + /// - `output`: A reference to the output data buffer to write the call output buffer. If `None` + /// is provided then the output buffer is not copied. + /// + /// # Errors + /// + /// An error means that the call wasn't successful output buffer is returned unless + /// stated otherwise. + /// + /// - [CalleeReverted][`crate::ReturnErrorCode::CalleeReverted]: Output buffer is returned. + /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] + /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] + /// - [NotCallable][`crate::ReturnErrorCode::NotCallable] + fn call( + flags: CallFlags, + callee: &[u8], + ref_time_limit: u64, + proof_size_limit: u64, + deposit: Option<&[u8]>, + value: &[u8], + input_data: &[u8], + output: Option<&mut &mut [u8]>, + ) -> Result; + + /// Call into the chain extension provided by the chain if any. + /// + /// Handling of the input values is up to the specific chain extension and so is the + /// return value. The extension can decide to use the inputs as primitive inputs or as + /// in/out arguments by interpreting them as pointers. Any caller of this function + /// must therefore coordinate with the chain that it targets. + /// + /// # Note + /// + /// If no chain extension exists the contract will trap with the `NoChainExtension` + /// module error. + /// + /// # Parameters + /// + /// - `func_id`: The function id of the chain extension. + /// - `input`: The input data buffer. + /// - `output`: A reference to the output data buffer to write the call output buffer. If `None` + /// is provided then the output buffer is not copied. + /// + /// # Return + /// + /// The chain extension returned value, if executed successfully. + fn call_chain_extension(func_id: u32, input: &[u8], output: Option<&mut &mut [u8]>) -> u32; + + /// Call some dispatchable of the runtime. + /// + /// # Parameters + /// + /// - `call`: The call data. + /// + /// # Return + /// + /// Returns `Error::Success` when the dispatchable was successfully executed and + /// returned `Ok`. When the dispatchable was executed but returned an error + /// `Error::CallRuntimeFailed` is returned. The full error is not + /// provided because it is not guaranteed to be stable. + /// + /// # Comparison with `ChainExtension` + /// + /// Just as a chain extension this API allows the runtime to extend the functionality + /// of contracts. While making use of this function is generally easier it cannot be + /// used in all cases. Consider writing a chain extension if you need to do perform + /// one of the following tasks: + /// + /// - Return data. + /// - Provide functionality **exclusively** to contracts. + /// - Provide custom weights. + /// - Avoid the need to keep the `Call` data structure stable. + fn call_runtime(call: &[u8]) -> Result; + + /// Stores the address of the caller into the supplied buffer. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// If this is a top-level call (i.e. initiated by an extrinsic) the origin address of the + /// extrinsic will be returned. Otherwise, if this call is initiated by another contract then + /// the address of the contract will be returned. + /// + /// If there is no address associated with the caller (e.g. because the caller is root) then + /// it traps with `BadOrigin`. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the caller address. + fn caller(output: &mut &mut [u8]); + + /// Checks whether the caller of the current contract is the origin of the whole call stack. + /// + /// Prefer this over [`is_contract()`][`Self::is_contract`] when checking whether your contract + /// is being called by a contract or a plain account. The reason is that it performs better + /// since it does not need to do any storage lookups. + /// + /// # Return + /// + /// A return value of `true` indicates that this contract is being called by a plain account + /// and `false` indicates that the caller is another contract. + fn caller_is_origin() -> bool; + + /// Checks whether the caller of the current contract is root. + /// + /// Note that only the origin of the call stack can be root. Hence this function returning + /// `true` implies that the contract is being called by the origin. + /// + /// A return value of `true` indicates that this contract is being called by a root origin, + /// and `false` indicates that the caller is a signed origin. + fn caller_is_root() -> u32; + + /// Clear the value at the given key in the contract storage. + /// + /// # Parameters + /// + /// - `key`: The storage key. + /// + /// # Return + /// + /// Returns the size of the pre-existing value at the specified key if any. + fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option; + + /// Retrieve the code hash for a specified contract address. + /// + /// # Parameters + /// + /// - `account_id`: The address of the contract.Should be decodable as an `T::AccountId`. Traps + /// otherwise. + /// - `output`: A reference to the output data buffer to write the code hash. + /// + /// + /// # Errors + /// + /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] + fn code_hash(account_id: &[u8], output: &mut [u8]) -> Result; + + /// Checks whether there is a value stored under the given key. + /// + /// The key length must not exceed the maximum defined by the contracts module parameter. + /// + /// # Parameters + /// - `key`: The storage key. + /// + /// # Return + /// + /// Returns the size of the pre-existing value at the specified key if any. + fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option; + + /// Emit a custom debug message. + /// + /// No newlines are added to the supplied message. + /// Specifying invalid UTF-8 just drops the message with no trap. + /// + /// This is a no-op if debug message recording is disabled which is always the case + /// when the code is executing on-chain. The message is interpreted as UTF-8 and + /// appended to the debug buffer which is then supplied to the calling RPC client. + /// + /// # Note + /// + /// Even though no action is taken when debug message recording is disabled there is still + /// a non trivial overhead (and weight cost) associated with calling this function. Contract + /// languages should remove calls to this function (either at runtime or compile time) when + /// not being executed as an RPC. For example, they could allow users to disable logging + /// through compile time flags (cargo features) for on-chain deployment. Additionally, the + /// return value of this function can be cached in order to prevent further calls at runtime. + fn debug_message(str: &[u8]) -> Result; + + /// Execute code in the context (storage, caller, value) of the current contract. + /// + /// Reentrancy protection is always disabled since the callee is allowed + /// to modify the callers storage. This makes going through a reentrancy attack + /// unnecessary for the callee when it wants to exploit the caller. + /// + /// # Parameters + /// + /// - `flags`: See [`CallFlags`] for a documentation of the supported flags. + /// - `code_hash`: The hash of the code to be executed. + /// - `input`: The input data buffer used to call the contract. + /// - `output`: A reference to the output data buffer to write the call output buffer. If `None` + /// is provided then the output buffer is not copied. + /// + /// # Errors + /// + /// An error means that the call wasn't successful and no output buffer is returned unless + /// stated otherwise. + /// + /// - [CalleeReverted][`crate::ReturnErrorCode::CalleeReverted]: Output buffer is returned. + /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] + /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] + fn delegate_call( + flags: CallFlags, + code_hash: &[u8], + input_data: &[u8], + output: Option<&mut &mut [u8]>, + ) -> Result; + + /// Deposit a contract event with the data buffer and optional list of topics. There is a limit + /// on the maximum number of topics specified by `event_topics`. + /// + /// There should not be any duplicates in `topics`. + /// + /// # Parameters + /// + /// - `topics`: The topics list encoded as `Vec`. It can't contain duplicates. + fn deposit_event(topics: &[u8], data: &[u8]); + + /// Recovers the ECDSA public key from the given message hash and signature. + /// + /// Writes the public key into the given output buffer. + /// Assumes the secp256k1 curve. + /// + /// # Parameters + /// + /// - `signature`: The signature bytes. + /// - `message_hash`: The message hash bytes. + /// - `output`: A reference to the output data buffer to write the public key. + /// + /// # Errors + /// + /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] + fn ecdsa_recover( + signature: &[u8; 65], + message_hash: &[u8; 32], + output: &mut [u8; 33], + ) -> Result; + + /// Calculates Ethereum address from the ECDSA compressed public key and stores + /// it into the supplied buffer. + /// + /// # Parameters + /// + /// - `pubkey`: The public key bytes. + /// - `output`: A reference to the output data buffer to write the address. + /// + /// # Errors + /// + /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] + fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result; + + /// Stores the amount of weight left into the supplied buffer. + /// The data is encoded as Weight. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the weight left. + fn weight_left(output: &mut &mut [u8]); + + /// Retrieve the value under the given key from storage. + /// + /// The key length must not exceed the maximum defined by the contracts module parameter. + /// + /// # Parameters + /// - `key`: The storage key. + /// - `output`: A reference to the output data buffer to write the storage entry. + /// + /// # Errors + /// + /// [KeyNotFound][`crate::ReturnErrorCode::KeyNotFound] + fn get_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result; + + hash_fn!(sha2_256, 32); + hash_fn!(keccak_256, 32); + hash_fn!(blake2_256, 32); + hash_fn!(blake2_128, 16); + + /// Stores the input passed by the caller into the supplied buffer. + /// + /// # Note + /// + /// This function traps if: + /// - the input is larger than the available space. + /// - the input was previously forwarded by a [`call()`][`Self::call()`]. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the input data. + fn input(output: &mut &mut [u8]); + + /// Instantiate a contract with the specified code hash. + /// + /// This function creates an account and executes the constructor defined in the code specified + /// by the code hash. + /// + /// # Parameters + /// + /// - `code_hash`: The hash of the code to be instantiated. + /// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution. + /// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution. + /// - `deposit`: The storage deposit limit for instantiation. Should be decodable as a + /// `Option`. Traps otherwise. Passing `None` means setting no specific limit for + /// the call, which implies storage usage up to the limit of the parent call. + /// - `value`: The value to transfer into the contract. Should be decodable as a `T::Balance`. + /// Traps otherwise. + /// - `input`: The input data buffer. + /// - `address`: A reference to the address buffer to write the address of the contract. If + /// `None` is provided then the output buffer is not copied. + /// - `output`: A reference to the return value buffer to write the constructor output buffer. + /// If `None` is provided then the output buffer is not copied. + /// - `salt`: The salt bytes to use for this instantiation. + /// + /// # Errors + /// + /// Please consult the [ReturnErrorCode][`crate::ReturnErrorCode`] enum declaration for more + /// information on those errors. Here we only note things specific to this function. + /// + /// An error means that the account wasn't created and no address or output buffer + /// is returned unless stated otherwise. + /// + /// - [CalleeReverted][`crate::ReturnErrorCode::CalleeReverted]: Output buffer is returned. + /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] + /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] + /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] + fn instantiate( + code_hash: &[u8], + ref_time_limit: u64, + proof_size_limit: u64, + deposit: Option<&[u8]>, + value: &[u8], + input: &[u8], + address: Option<&mut &mut [u8]>, + output: Option<&mut &mut [u8]>, + salt: &[u8], + ) -> Result; + + /// Checks whether a specified address belongs to a contract. + /// + /// # Parameters + /// + /// - `account_id`: The address to check. Should be decodable as an `T::AccountId`. Traps + /// otherwise. + /// + /// # Return + /// + /// Returns `true` if the address belongs to a contract. + fn is_contract(account_id: &[u8]) -> bool; + + /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. + /// The data is encoded as `T::Balance`. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the minimum balance. + fn minimum_balance(output: &mut &mut [u8]); + + /// Retrieve the code hash of the currently executing contract. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the code hash. + fn own_code_hash(output: &mut [u8]); + + /// Load the latest block timestamp into the supplied buffer + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the timestamp. + fn now(output: &mut &mut [u8]); + + /// Removes the delegate dependency from the contract. + /// + /// Traps if the delegate dependency does not exist. + /// + /// # Parameters + /// + /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps + /// otherwise. + fn unlock_delegate_dependency(code_hash: &[u8]); + + /// Cease contract execution and save a data buffer as a result of the execution. + /// + /// This function never returns as it stops execution of the caller. + /// This is the only way to return a data buffer to the caller. Returning from + /// execution without calling this function is equivalent to calling: + /// ```nocompile + /// return_value(ReturnFlags::empty(), &[]) + /// ``` + /// + /// Using an unnamed non empty `ReturnFlags` triggers a trap. + /// + /// # Parameters + /// + /// - `flags`: Flag used to signal special return conditions to the supervisor. See + /// [`ReturnFlags`] for a documentation of the supported flags. + /// - `return_value`: The return value buffer. + fn return_value(flags: ReturnFlags, return_value: &[u8]) -> !; + + /// Replace the contract code at the specified address with new code. + /// + /// # Note + /// + /// There are a couple of important considerations which must be taken into account when + /// using this API: + /// + /// 1. The storage at the code address will remain untouched. This means that contract + /// developers must ensure that the storage layout of the new code is compatible with that of + /// the old code. + /// + /// 2. Contracts using this API can't be assumed as having deterministic addresses. Said another + /// way, when using this API you lose the guarantee that an address always identifies a specific + /// code hash. + /// + /// 3. If a contract calls into itself after changing its code the new call would use + /// the new code. However, if the original caller panics after returning from the sub call it + /// would revert the changes made by [`set_code_hash()`][`Self::set_code_hash`] and the next + /// caller would use the old code. + /// + /// # Parameters + /// + /// - `code_hash`: The hash of the new code. Should be decodable as an `T::Hash`. Traps + /// otherwise. + /// + /// # Errors + /// + /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] + fn set_code_hash(code_hash: &[u8]) -> Result; + + /// Set the value at the given key in the contract storage. + /// + /// The key and value lengths must not exceed the maximums defined by the contracts module + /// parameters. + /// + /// # Parameters + /// + /// - `key`: The storage key. + /// - `encoded_value`: The storage value. + /// + /// # Return + /// + /// Returns the size of the pre-existing value at the specified key if any. + fn set_storage(flags: StorageFlags, key: &[u8], value: &[u8]) -> Option; + + /// Verify a sr25519 signature + /// + /// # Parameters + /// + /// - `signature`: The signature bytes. + /// - `message`: The message bytes. + /// + /// # Errors + /// + /// - [Sr25519VerifyFailed][`crate::ReturnErrorCode::Sr25519VerifyFailed] + fn sr25519_verify(signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> Result; + + /// Retrieve and remove the value under the given key from storage. + /// + /// # Parameters + /// - `key`: The storage key. + /// - `output`: A reference to the output data buffer to write the storage entry. + /// + /// # Errors + /// + /// [KeyNotFound][`crate::ReturnErrorCode::KeyNotFound] + fn take_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result; + + /// Transfer some amount of funds into the specified account. + /// + /// # Parameters + /// + /// - `account_id`: The address of the account to transfer funds to. Should be decodable as an + /// `T::AccountId`. Traps otherwise. + /// - `value`: The value to transfer. Should be decodable as a `T::Balance`. Traps otherwise. + /// + /// # Errors + /// + /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] + fn transfer(account_id: &[u8], value: &[u8]) -> Result; + + /// Remove the calling account and transfer remaining **free** balance. + /// + /// This function never returns. Either the termination was successful and the + /// execution of the destroyed contract is halted. Or it failed during the termination + /// which is considered fatal and results in a trap + rollback. + /// + /// # Parameters + /// + /// - `beneficiary`: The address of the beneficiary account, Should be decodable as an + /// `T::AccountId`. + /// + /// # Traps + /// + /// - The contract is live i.e is already on the call stack. + /// - Failed to send the balance to the beneficiary. + /// - The deletion queue is full. + fn terminate(beneficiary: &[u8]) -> !; + + /// Stores the value transferred along with this call/instantiate into the supplied buffer. + /// The data is encoded as `T::Balance`. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the transferred value. + fn value_transferred(output: &mut &mut [u8]); + + /// Stores the price for the specified amount of gas into the supplied buffer. + /// The data is encoded as `T::Balance`. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `ref_time_limit`: The *ref_time* Weight limit to query the price for. + /// - `proof_size_limit`: The *proof_size* Weight limit to query the price for. + /// - `output`: A reference to the output data buffer to write the price. + fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut &mut [u8]); + + /// Execute an XCM program locally, using the contract's address as the origin. + /// This is equivalent to dispatching `pallet_xcm::execute` through call_runtime, except that + /// the function is called directly instead of being dispatched. + /// + /// # Parameters + /// + /// - `msg`: The message, should be decodable as a [VersionedXcm](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedXcm.html), + /// traps otherwise. + /// - `output`: A reference to the output data buffer to write the [Outcome](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/v3/enum.Outcome.html) + /// + /// # Return + /// + /// Returns `Error::Success` when the XCM execution attempt is successful. When the XCM + /// execution fails, `ReturnCode::XcmExecutionFailed` is returned + fn xcm_execute(msg: &[u8]) -> Result; + + /// Send an XCM program from the contract to the specified destination. + /// This is equivalent to dispatching `pallet_xcm::send` through `call_runtime`, except that + /// the function is called directly instead of being dispatched. + /// + /// # Parameters + /// + /// - `dest`: The XCM destination, should be decodable as [VersionedLocation](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedLocation.html), + /// traps otherwise. + /// - `msg`: The message, should be decodable as a [VersionedXcm](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedXcm.html), + /// traps otherwise. + /// + /// # Return + /// + /// Returns `ReturnCode::Success` when the message was successfully sent. When the XCM + /// execution fails, `ReturnErrorCode::XcmSendFailed` is returned. + fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result; +} + +mod private { + pub trait Sealed {} + impl Sealed for super::HostFnImpl {} +} diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs new file mode 100644 index 000000000000..0b7130015f13 --- /dev/null +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -0,0 +1,561 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(unused_variables)] + +use crate::{ + host::{CallFlags, HostFn, HostFnImpl, Result, StorageFlags}, + ReturnFlags, +}; + +mod sys { + use crate::ReturnCode; + + #[polkavm_derive::polkavm_define_abi] + mod abi {} + + impl abi::FromHost for ReturnCode { + type Regs = (u32,); + + fn from_host((a0,): Self::Regs) -> Self { + ReturnCode(a0) + } + } + + #[polkavm_derive::polkavm_import(abi = self::abi)] + extern "C" { + pub fn set_storage( + flags: u32, + key_ptr: *const u8, + key_len: u32, + value_ptr: *const u8, + value_len: u32, + ) -> ReturnCode; + pub fn clear_storage(flags: u32, key_ptr: *const u8, key_len: u32) -> ReturnCode; + pub fn get_storage( + flags: u32, + key_ptr: *const u8, + key_len: u32, + out_ptr: *mut u8, + out_len_ptr: *mut u32, + ) -> ReturnCode; + pub fn contains_storage(flags: u32, key_ptr: *const u8, key_len: u32) -> ReturnCode; + pub fn take_storage( + flags: u32, + key_ptr: *const u8, + key_len: u32, + out_ptr: *mut u8, + out_len_ptr: *mut u32, + ) -> ReturnCode; + pub fn transfer(account_ptr: *const u8, value_ptr: *const u8) -> ReturnCode; + pub fn call(ptr: *const u8) -> ReturnCode; + pub fn delegate_call( + flags: u32, + code_hash_ptr: *const u8, + input_data_ptr: *const u8, + input_data_len: u32, + out_ptr: *mut u8, + out_len_ptr: *mut u32, + ) -> ReturnCode; + pub fn instantiate(ptr: *const u8) -> ReturnCode; + pub fn terminate(beneficiary_ptr: *const u8); + pub fn input(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn seal_return(flags: u32, data_ptr: *const u8, data_len: u32); + pub fn caller(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn is_contract(account_ptr: *const u8) -> ReturnCode; + pub fn code_hash( + account_ptr: *const u8, + out_ptr: *mut u8, + out_len_ptr: *mut u32, + ) -> ReturnCode; + pub fn own_code_hash(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn caller_is_origin() -> ReturnCode; + pub fn caller_is_root() -> ReturnCode; + pub fn address(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn weight_to_fee( + ref_time: u64, + proof_size: u64, + out_ptr: *mut u8, + out_len_ptr: *mut u32, + ); + pub fn weight_left(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn balance(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn value_transferred(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn now(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn minimum_balance(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn deposit_event( + topics_ptr: *const u8, + topics_len: u32, + data_ptr: *const u8, + data_len: u32, + ); + pub fn block_number(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn hash_sha2_256(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); + pub fn hash_keccak_256(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); + pub fn hash_blake2_256(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); + pub fn hash_blake2_128(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); + pub fn call_chain_extension( + id: u32, + input_ptr: *const u8, + input_len: u32, + out_ptr: *mut u8, + out_len_ptr: *mut u32, + ) -> ReturnCode; + pub fn debug_message(str_ptr: *const u8, str_len: u32) -> ReturnCode; + pub fn call_runtime(call_ptr: *const u8, call_len: u32) -> ReturnCode; + pub fn ecdsa_recover( + signature_ptr: *const u8, + message_hash_ptr: *const u8, + out_ptr: *mut u8, + ) -> ReturnCode; + pub fn sr25519_verify( + signature_ptr: *const u8, + pub_key_ptr: *const u8, + message_len: u32, + message_ptr: *const u8, + ) -> ReturnCode; + pub fn set_code_hash(code_hash_ptr: *const u8) -> ReturnCode; + pub fn ecdsa_to_eth_address(key_ptr: *const u8, out_ptr: *mut u8) -> ReturnCode; + pub fn instantiation_nonce() -> u64; + pub fn lock_delegate_dependency(code_hash_ptr: *const u8); + pub fn unlock_delegate_dependency(code_hash_ptr: *const u8); + pub fn xcm_execute(msg_ptr: *const u8, msg_len: u32) -> ReturnCode; + pub fn xcm_send( + dest_ptr: *const u8, + msg_ptr: *const u8, + msg_len: u32, + out_ptr: *mut u8, + ) -> ReturnCode; + } +} + +macro_rules! impl_wrapper_for { + ( $( $name:ident, )* ) => { + $( + fn $name(output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + unsafe { + sys::$name( + output.as_mut_ptr(), + &mut output_len, + ) + } + extract_from_slice(output, output_len as usize) + } + )* + } +} + +macro_rules! impl_hash_fn { + ( $name:ident, $bytes_result:literal ) => { + paste::item! { + fn [](input: &[u8], output: &mut [u8; $bytes_result]) { + unsafe { + sys::[]( + input.as_ptr(), + input.len() as u32, + output.as_mut_ptr(), + ) + } + } + } + }; +} + +#[inline(always)] +fn extract_from_slice(output: &mut &mut [u8], new_len: usize) { + debug_assert!(new_len <= output.len()); + let tmp = core::mem::take(output); + *output = &mut tmp[..new_len]; +} + +#[inline(always)] +fn ptr_len_or_sentinel(data: &mut Option<&mut &mut [u8]>) -> (*mut u8, u32) { + match data { + Some(ref mut data) => (data.as_mut_ptr(), data.len() as _), + None => (crate::SENTINEL as _, 0), + } +} + +#[inline(always)] +fn ptr_or_sentinel(data: &Option<&[u8]>) -> *const u8 { + match data { + Some(ref data) => data.as_ptr(), + None => crate::SENTINEL as _, + } +} + +impl HostFn for HostFnImpl { + fn instantiate( + code_hash: &[u8], + ref_time_limit: u64, + proof_size_limit: u64, + deposit_limit: Option<&[u8]>, + value: &[u8], + input: &[u8], + mut address: Option<&mut &mut [u8]>, + mut output: Option<&mut &mut [u8]>, + salt: &[u8], + ) -> Result { + let (address_ptr, mut address_len) = ptr_len_or_sentinel(&mut address); + let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); + let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + #[repr(packed)] + #[allow(dead_code)] + struct Args { + code_hash: *const u8, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_limit: *const u8, + value: *const u8, + input: *const u8, + input_len: usize, + address: *const u8, + address_len: *mut u32, + output: *mut u8, + output_len: *mut u32, + salt: *const u8, + salt_len: usize, + } + let args = Args { + code_hash: code_hash.as_ptr(), + ref_time_limit, + proof_size_limit, + deposit_limit: deposit_limit_ptr, + value: value.as_ptr(), + input: input.as_ptr(), + input_len: input.len(), + address: address_ptr, + address_len: &mut address_len as *mut _, + output: output_ptr, + output_len: &mut output_len as *mut _, + salt: salt.as_ptr(), + salt_len: salt.len(), + }; + + let ret_code = { unsafe { sys::instantiate(&args as *const Args as *const _) } }; + + if let Some(ref mut address) = address { + extract_from_slice(address, address_len as usize); + } + + if let Some(ref mut output) = output { + extract_from_slice(output, output_len as usize); + } + + ret_code.into() + } + + fn call( + flags: CallFlags, + callee: &[u8], + ref_time_limit: u64, + proof_size_limit: u64, + deposit_limit: Option<&[u8]>, + value: &[u8], + input: &[u8], + mut output: Option<&mut &mut [u8]>, + ) -> Result { + let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); + let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + #[repr(packed)] + #[allow(dead_code)] + struct Args { + flags: u32, + callee: *const u8, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_limit: *const u8, + value: *const u8, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: *mut u32, + } + let args = Args { + flags: flags.bits(), + callee: callee.as_ptr(), + ref_time_limit, + proof_size_limit, + deposit_limit: deposit_limit_ptr, + value: value.as_ptr(), + input: input.as_ptr(), + input_len: input.len(), + output: output_ptr, + output_len: &mut output_len as *mut _, + }; + + let ret_code = { unsafe { sys::call(&args as *const Args as *const _) } }; + + if let Some(ref mut output) = output { + extract_from_slice(output, output_len as usize); + } + + ret_code.into() + } + + fn caller_is_root() -> u32 { + unsafe { sys::caller_is_root() }.into_u32() + } + + fn delegate_call( + flags: CallFlags, + code_hash: &[u8], + input: &[u8], + mut output: Option<&mut &mut [u8]>, + ) -> Result { + let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); + let ret_code = { + unsafe { + sys::delegate_call( + flags.bits(), + code_hash.as_ptr(), + input.as_ptr(), + input.len() as u32, + output_ptr, + &mut output_len, + ) + } + }; + + if let Some(ref mut output) = output { + extract_from_slice(output, output_len as usize); + } + + ret_code.into() + } + + fn transfer(account_id: &[u8], value: &[u8]) -> Result { + let ret_code = unsafe { sys::transfer(account_id.as_ptr(), value.as_ptr()) }; + ret_code.into() + } + + fn deposit_event(topics: &[u8], data: &[u8]) { + unsafe { + sys::deposit_event( + topics.as_ptr(), + topics.len() as u32, + data.as_ptr(), + data.len() as u32, + ) + } + } + + fn set_storage(flags: StorageFlags, key: &[u8], encoded_value: &[u8]) -> Option { + let ret_code = unsafe { + sys::set_storage( + flags.bits(), + key.as_ptr(), + key.len() as u32, + encoded_value.as_ptr(), + encoded_value.len() as u32, + ) + }; + ret_code.into() + } + + fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option { + let ret_code = unsafe { sys::clear_storage(flags.bits(), key.as_ptr(), key.len() as u32) }; + ret_code.into() + } + + fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option { + let ret_code = + unsafe { sys::contains_storage(flags.bits(), key.as_ptr(), key.len() as u32) }; + ret_code.into() + } + + fn get_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result { + let mut output_len = output.len() as u32; + let ret_code = { + unsafe { + sys::get_storage( + flags.bits(), + key.as_ptr(), + key.len() as u32, + output.as_mut_ptr(), + &mut output_len, + ) + } + }; + extract_from_slice(output, output_len as usize); + ret_code.into() + } + + fn take_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result { + let mut output_len = output.len() as u32; + let ret_code = { + unsafe { + sys::take_storage( + flags.bits(), + key.as_ptr(), + key.len() as u32, + output.as_mut_ptr(), + &mut output_len, + ) + } + }; + extract_from_slice(output, output_len as usize); + ret_code.into() + } + + fn debug_message(str: &[u8]) -> Result { + let ret_code = unsafe { sys::debug_message(str.as_ptr(), str.len() as u32) }; + ret_code.into() + } + + fn terminate(beneficiary: &[u8]) -> ! { + unsafe { sys::terminate(beneficiary.as_ptr()) } + panic!("terminate does not return"); + } + + fn call_chain_extension(func_id: u32, input: &[u8], mut output: Option<&mut &mut [u8]>) -> u32 { + let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); + let ret_code = { + unsafe { + sys::call_chain_extension( + func_id, + input.as_ptr(), + input.len() as u32, + output_ptr, + &mut output_len, + ) + } + }; + + if let Some(ref mut output) = output { + extract_from_slice(output, output_len as usize); + } + ret_code.into_u32() + } + + fn input(output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + { + unsafe { sys::input(output.as_mut_ptr(), &mut output_len) }; + } + extract_from_slice(output, output_len as usize); + } + + fn return_value(flags: ReturnFlags, return_value: &[u8]) -> ! { + unsafe { sys::seal_return(flags.bits(), return_value.as_ptr(), return_value.len() as u32) } + panic!("seal_return does not return"); + } + + fn call_runtime(call: &[u8]) -> Result { + let ret_code = unsafe { sys::call_runtime(call.as_ptr(), call.len() as u32) }; + ret_code.into() + } + + impl_wrapper_for! { + caller, block_number, address, balance, + value_transferred,now, minimum_balance, + weight_left, + } + + fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + { + unsafe { + sys::weight_to_fee( + ref_time_limit, + proof_size_limit, + output.as_mut_ptr(), + &mut output_len, + ) + }; + } + extract_from_slice(output, output_len as usize); + } + + impl_hash_fn!(sha2_256, 32); + impl_hash_fn!(keccak_256, 32); + impl_hash_fn!(blake2_256, 32); + impl_hash_fn!(blake2_128, 16); + + fn ecdsa_recover( + signature: &[u8; 65], + message_hash: &[u8; 32], + output: &mut [u8; 33], + ) -> Result { + let ret_code = unsafe { + sys::ecdsa_recover(signature.as_ptr(), message_hash.as_ptr(), output.as_mut_ptr()) + }; + ret_code.into() + } + + fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result { + let ret_code = unsafe { sys::ecdsa_to_eth_address(pubkey.as_ptr(), output.as_mut_ptr()) }; + ret_code.into() + } + + fn sr25519_verify(signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> Result { + let ret_code = unsafe { + sys::sr25519_verify( + signature.as_ptr(), + pub_key.as_ptr(), + message.len() as u32, + message.as_ptr(), + ) + }; + ret_code.into() + } + + fn is_contract(account_id: &[u8]) -> bool { + let ret_val = unsafe { sys::is_contract(account_id.as_ptr()) }; + ret_val.into_bool() + } + + fn caller_is_origin() -> bool { + let ret_val = unsafe { sys::caller_is_origin() }; + ret_val.into_bool() + } + + fn set_code_hash(code_hash: &[u8]) -> Result { + let ret_val = unsafe { sys::set_code_hash(code_hash.as_ptr()) }; + ret_val.into() + } + + fn code_hash(account_id: &[u8], output: &mut [u8]) -> Result { + let mut output_len = output.len() as u32; + let ret_val = + unsafe { sys::code_hash(account_id.as_ptr(), output.as_mut_ptr(), &mut output_len) }; + ret_val.into() + } + + fn own_code_hash(output: &mut [u8]) { + let mut output_len = output.len() as u32; + unsafe { sys::own_code_hash(output.as_mut_ptr(), &mut output_len) } + } + + fn lock_delegate_dependency(code_hash: &[u8]) { + unsafe { sys::lock_delegate_dependency(code_hash.as_ptr()) } + } + + fn unlock_delegate_dependency(code_hash: &[u8]) { + unsafe { sys::unlock_delegate_dependency(code_hash.as_ptr()) } + } + + fn xcm_execute(msg: &[u8]) -> Result { + let ret_code = unsafe { sys::xcm_execute(msg.as_ptr(), msg.len() as _) }; + ret_code.into() + } + + fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result { + let ret_code = unsafe { + sys::xcm_send(dest.as_ptr(), msg.as_ptr(), msg.len() as _, output.as_mut_ptr()) + }; + ret_code.into() + } +} diff --git a/substrate/frame/revive/uapi/src/lib.rs b/substrate/frame/revive/uapi/src/lib.rs new file mode 100644 index 000000000000..e660ce36ef75 --- /dev/null +++ b/substrate/frame/revive/uapi/src/lib.rs @@ -0,0 +1,131 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! External C API to communicate with substrate contracts runtime module. +//! +//! Refer to substrate FRAME contract module for more documentation. + +#![no_std] + +mod flags; +pub use flags::*; +mod host; + +pub use host::{HostFn, HostFnImpl}; + +macro_rules! define_error_codes { + ( + $( + $( #[$attr:meta] )* + $name:ident = $discr:literal, + )* + ) => { + /// Every error that can be returned to a contract when it calls any of the host functions. + #[derive(Debug, PartialEq, Eq)] + #[repr(u32)] + pub enum ReturnErrorCode { + /// API call successful. + Success = 0, + $( + $( #[$attr] )* + $name = $discr, + )* + /// Returns if an unknown error was received from the host module. + Unknown, + } + + impl From for Result { + fn from(return_code: ReturnCode) -> Self { + match return_code.0 { + 0 => Ok(()), + $( + $discr => Err(ReturnErrorCode::$name), + )* + _ => Err(ReturnErrorCode::Unknown), + } + } + } + }; +} + +impl From for u32 { + fn from(code: ReturnErrorCode) -> u32 { + code as u32 + } +} + +define_error_codes! { + /// The called function trapped and has its state changes reverted. + /// In this case no output buffer is returned. + /// Can only be returned from `call` and `instantiate`. + CalleeTrapped = 1, + /// The called function ran to completion but decided to revert its state. + /// An output buffer is returned when one was supplied. + /// Can only be returned from `call` and `instantiate`. + CalleeReverted = 2, + /// The passed key does not exist in storage. + KeyNotFound = 3, + /// Transfer failed for other not further specified reason. Most probably + /// reserved or locked balance of the sender that was preventing the transfer. + TransferFailed = 4, + /// No code could be found at the supplied code hash. + CodeNotFound = 5, + /// The account that was called is no contract. + NotCallable = 6, + /// The call to `debug_message` had no effect because debug message + /// recording was disabled. + LoggingDisabled = 7, + /// The call dispatched by `call_runtime` was executed but returned an error. + CallRuntimeFailed = 8, + /// ECDSA public key recovery failed. Most probably wrong recovery id or signature. + EcdsaRecoveryFailed = 9, + /// sr25519 signature verification failed. + Sr25519VerifyFailed = 10, + /// The `xcm_execute` call failed. + XcmExecutionFailed = 11, + /// The `xcm_send` call failed. + XcmSendFailed = 12, +} + +/// The raw return code returned by the host side. +#[repr(transparent)] +pub struct ReturnCode(u32); + +/// Used as a sentinel value when reading and writing contract memory. +/// +/// We use this value to signal `None` to a contract when only a primitive is +/// allowed and we don't want to go through encoding a full Rust type. +/// Using `u32::Max` is a safe sentinel because contracts are never +/// allowed to use such a large amount of resources. So this value doesn't +/// make sense for a memory location or length. +const SENTINEL: u32 = u32::MAX; + +impl From for Option { + fn from(code: ReturnCode) -> Self { + (code.0 < SENTINEL).then_some(code.0) + } +} + +impl ReturnCode { + /// Returns the raw underlying `u32` representation. + pub fn into_u32(self) -> u32 { + self.0 + } + /// Returns the underlying `u32` converted into `bool`. + pub fn into_bool(self) -> bool { + self.0.ne(&0) + } +} + +type Result = core::result::Result<(), ReturnErrorCode>; diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index 235d75a4b752..e5fb15cdd07c 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -177,8 +177,15 @@ pub mod runtime { pub use frame_executive::*; /// Macro to amalgamate the runtime into `struct Runtime`. + /// + /// Consider using the new version of this [`frame_construct_runtime`]. pub use frame_support::construct_runtime; + /// Macro to amalgamate the runtime into `struct Runtime`. + /// + /// This is the newer version of [`construct_runtime`]. + pub use frame_support::runtime as frame_construct_runtime; + /// Macro to easily derive the `Config` trait of various pallet for `Runtime`. pub use frame_support::derive_impl; @@ -186,12 +193,18 @@ pub mod runtime { // TODO: using linking in the Get in the line above triggers an ICE :/ pub use frame_support::{ord_parameter_types, parameter_types}; + /// For building genesis config. + pub use frame_support::genesis_builder_helper::{build_state, get_preset}; + /// Const types that can easily be used in conjuncture with `Get`. pub use frame_support::traits::{ ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, ConstU16, ConstU32, ConstU64, ConstU8, }; + /// Used for simple fee calculation. + pub use frame_support::weights::{self, FixedFee, NoFee}; + /// Primary types used to parameterize `EnsureOrigin` and `EnsureRootWithArg`. pub use frame_system::{ EnsureNever, EnsureNone, EnsureRoot, EnsureRootWithSuccess, EnsureSigned, @@ -201,11 +214,16 @@ pub mod runtime { /// Types to define your runtime version. pub use sp_version::{create_runtime_str, runtime_version, RuntimeVersion}; + #[cfg(feature = "std")] + pub use sp_version::NativeVersion; + /// Macro to implement runtime APIs. pub use sp_api::impl_runtime_apis; - #[cfg(feature = "std")] - pub use sp_version::NativeVersion; + // Types often used in the runtime APIs. + pub use sp_core::OpaqueMetadata; + pub use sp_inherents::{CheckInherentsResult, InherentData}; + pub use sp_runtime::{ApplyExtrinsicResult, ExtrinsicInclusionMode}; } /// Types and traits for runtimes that implement runtime APIs. @@ -223,11 +241,6 @@ pub mod runtime { // moved to file similarly. #[allow(ambiguous_glob_reexports)] pub mod apis { - // Types often used in the runtime APIs. - pub use sp_core::OpaqueMetadata; - pub use sp_inherents::{CheckInherentsResult, InherentData}; - pub use sp_runtime::{ApplyExtrinsicResult, ExtrinsicInclusionMode}; - pub use frame_system_rpc_runtime_api::*; pub use sp_api::{self, *}; pub use sp_block_builder::*; diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index a423656c394f..f635ed32a124 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -92,6 +92,8 @@ pub use hooks::{ pub mod schedule; mod storage; +#[cfg(feature = "experimental")] +pub use storage::MaybeConsideration; pub use storage::{ Consideration, Footprint, Incrementable, Instance, LinearStoragePrice, PartialStorageInfoTrait, StorageInfo, StorageInfoTrait, StorageInstance, TrackedStorageKey, WhitelistedStorageKeys, diff --git a/substrate/frame/support/src/traits/storage.rs b/substrate/frame/support/src/traits/storage.rs index eb63ea59f6cb..2b8e43707389 100644 --- a/substrate/frame/support/src/traits/storage.rs +++ b/substrate/frame/support/src/traits/storage.rs @@ -257,6 +257,24 @@ impl Consideration for () { fn ensure_successful(_: &A, _: F) {} } +#[cfg(feature = "experimental")] +/// An extension of the [`Consideration`] trait that allows for the management of tickets that may +/// represent no cost. While the [`MaybeConsideration`] still requires proper handling, it +/// introduces the ability to determine if a ticket represents no cost and can be safely forgotten +/// without any side effects. +pub trait MaybeConsideration: Consideration { + /// Returns `true` if this [`Consideration`] represents a no-cost ticket and can be forgotten + /// without any side effects. + fn is_none(&self) -> bool; +} + +#[cfg(feature = "experimental")] +impl MaybeConsideration for () { + fn is_none(&self) -> bool { + true + } +} + macro_rules! impl_incrementable { ($($type:ty),+) => { $( diff --git a/substrate/frame/support/src/traits/tokens/fungible/mod.rs b/substrate/frame/support/src/traits/tokens/fungible/mod.rs index 9b7c86971bb8..c67755e133bf 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/mod.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/mod.rs @@ -186,6 +186,8 @@ use sp_core::Get; use sp_runtime::{traits::Convert, DispatchError}; pub use union_of::{NativeFromLeft, NativeOrWithId, UnionOf}; +#[cfg(feature = "experimental")] +use crate::traits::MaybeConsideration; use crate::{ ensure, traits::{Consideration, Footprint}, @@ -241,6 +243,20 @@ impl< let _ = F::mint_into(who, F::minimum_balance().saturating_add(D::convert(fp))); } } +#[cfg(feature = "experimental")] +impl< + A: 'static + Eq, + #[cfg(not(feature = "runtime-benchmarks"))] F: 'static + MutateFreeze, + #[cfg(feature = "runtime-benchmarks")] F: 'static + MutateFreeze + Mutate, + R: 'static + Get, + D: 'static + Convert, + Fp: 'static, + > MaybeConsideration for FreezeConsideration +{ + fn is_none(&self) -> bool { + self.0.is_zero() + } +} /// Consideration method using a `fungible` balance frozen as the cost exacted for the footprint. #[derive( @@ -295,6 +311,20 @@ impl< let _ = F::mint_into(who, F::minimum_balance().saturating_add(D::convert(fp))); } } +#[cfg(feature = "experimental")] +impl< + A: 'static + Eq, + #[cfg(not(feature = "runtime-benchmarks"))] F: 'static + MutateHold, + #[cfg(feature = "runtime-benchmarks")] F: 'static + MutateHold + Mutate, + R: 'static + Get, + D: 'static + Convert, + Fp: 'static, + > MaybeConsideration for HoldConsideration +{ + fn is_none(&self) -> bool { + self.0.is_zero() + } +} /// Basic consideration method using a `fungible` balance frozen as the cost exacted for the /// footprint. diff --git a/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs b/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs index 326f3530e26e..81cdd40d1bcf 100644 --- a/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs +++ b/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs @@ -19,8 +19,6 @@ use frame_support::derive_impl; mod common; -use common::outer_enums::{pallet, pallet2}; - pub type Header = sp_runtime::generic::Header; pub type Block = sp_runtime::generic::Block; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; @@ -75,8 +73,10 @@ frame_support::construct_runtime!( } ); +#[cfg(feature = "experimental")] #[test] fn module_error_outer_enum_expand_explicit() { + use common::outer_enums::{pallet, pallet2}; // The Runtime has *all* parts explicitly defined. // Check that all error types are propagated @@ -90,9 +90,7 @@ fn module_error_outer_enum_expand_explicit() { frame_system::Error::NonZeroRefCount => (), frame_system::Error::CallFiltered => (), frame_system::Error::MultiBlockMigrationsOngoing => (), - #[cfg(feature = "experimental")] frame_system::Error::InvalidTask => (), - #[cfg(feature = "experimental")] frame_system::Error::FailedTask => (), frame_system::Error::NothingAuthorized => (), frame_system::Error::Unauthorized => (), diff --git a/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs b/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs index 4149c4880cca..d2e759640c73 100644 --- a/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs +++ b/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs @@ -19,8 +19,6 @@ use frame_support::derive_impl; mod common; -use common::outer_enums::{pallet, pallet2}; - pub type Header = sp_runtime::generic::Header; pub type Block = sp_runtime::generic::Block; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; @@ -75,8 +73,10 @@ frame_support::construct_runtime!( } ); +#[cfg(feature = "experimental")] #[test] fn module_error_outer_enum_expand_implicit() { + use common::outer_enums::{pallet, pallet2}; // The Runtime has *all* parts implicitly defined. // Check that all error types are propagated @@ -90,9 +90,7 @@ fn module_error_outer_enum_expand_implicit() { frame_system::Error::NonZeroRefCount => (), frame_system::Error::CallFiltered => (), frame_system::Error::MultiBlockMigrationsOngoing => (), - #[cfg(feature = "experimental")] frame_system::Error::InvalidTask => (), - #[cfg(feature = "experimental")] frame_system::Error::FailedTask => (), frame_system::Error::NothingAuthorized => (), frame_system::Error::Unauthorized => (), diff --git a/substrate/frame/treasury/src/benchmarking.rs b/substrate/frame/treasury/src/benchmarking.rs index 63978c94e682..e63febb5798b 100644 --- a/substrate/frame/treasury/src/benchmarking.rs +++ b/substrate/frame/treasury/src/benchmarking.rs @@ -78,7 +78,7 @@ fn create_approved_proposals, I: 'static>(n: u32) -> Result<(), &'s let (_, value, lookup) = setup_proposal::(i); Treasury::::spend_local(origin.clone(), value, lookup)?; } - ensure!(>::get().len() == n as usize, "Not all approved"); + ensure!(Approvals::::get().len() == n as usize, "Not all approved"); Ok(()) } @@ -130,7 +130,7 @@ mod benchmarks { T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let (_, value, beneficiary_lookup) = setup_proposal::(SEED); Treasury::::spend_local(origin, value, beneficiary_lookup)?; - let proposal_id = Treasury::::proposal_count() - 1; + let proposal_id = ProposalCount::::get() - 1; let reject_origin = T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; diff --git a/substrate/frame/treasury/src/lib.rs b/substrate/frame/treasury/src/lib.rs index 3954489a2d15..edb39f230642 100644 --- a/substrate/frame/treasury/src/lib.rs +++ b/substrate/frame/treasury/src/lib.rs @@ -100,7 +100,7 @@ use frame_support::{ ReservableCurrency, WithdrawReasons, }, weights::Weight, - PalletId, + BoundedVec, PalletId, }; pub use pallet::*; @@ -278,12 +278,10 @@ pub mod pallet { /// Number of proposals that have been made. #[pallet::storage] - #[pallet::getter(fn proposal_count)] - pub(crate) type ProposalCount = StorageValue<_, ProposalIndex, ValueQuery>; + pub type ProposalCount = StorageValue<_, ProposalIndex, ValueQuery>; /// Proposals that have been made. #[pallet::storage] - #[pallet::getter(fn proposals)] pub type Proposals, I: 'static = ()> = StorageMap< _, Twox64Concat, @@ -299,7 +297,6 @@ pub mod pallet { /// Proposal indices that have been approved but not yet awarded. #[pallet::storage] - #[pallet::getter(fn approvals)] pub type Approvals, I: 'static = ()> = StorageValue<_, BoundedVec, ValueQuery>; @@ -335,7 +332,7 @@ pub mod pallet { impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) { // Create Treasury account - let account_id = >::account_id(); + let account_id = Pallet::::account_id(); let min = T::Currency::minimum_balance(); if T::Currency::free_balance(&account_id) < min { let _ = T::Currency::make_free_balance_be(&account_id, min); @@ -501,7 +498,7 @@ pub mod pallet { .unwrap_or(Ok(()))?; let beneficiary = T::Lookup::lookup(beneficiary)?; - let proposal_index = Self::proposal_count(); + let proposal_index = ProposalCount::::get(); Approvals::::try_append(proposal_index) .map_err(|_| Error::::TooManyApprovals)?; let proposal = Proposal { @@ -794,6 +791,21 @@ impl, I: 'static> Pallet { T::PalletId::get().into_account_truncating() } + /// Public function to proposal_count storage. + pub fn proposal_count() -> ProposalIndex { + ProposalCount::::get() + } + + /// Public function to proposals storage. + pub fn proposals(index: ProposalIndex) -> Option>> { + Proposals::::get(index) + } + + /// Public function to approvals storage. + pub fn approvals() -> BoundedVec { + Approvals::::get() + } + /// Spend some money! returns number of approvals before spend. pub fn spend_funds() -> Weight { let mut total_weight = Weight::zero(); @@ -803,15 +815,15 @@ impl, I: 'static> Pallet { let account_id = Self::account_id(); let mut missed_any = false; - let mut imbalance = >::zero(); + let mut imbalance = PositiveImbalanceOf::::zero(); let proposals_len = Approvals::::mutate(|v| { let proposals_approvals_len = v.len() as u32; v.retain(|&index| { // Should always be true, but shouldn't panic if false or we're screwed. - if let Some(p) = Self::proposals(index) { + if let Some(p) = Proposals::::get(index) { if p.value <= budget_remaining { budget_remaining -= p.value; - >::remove(index); + Proposals::::remove(index); // return their deposit. let err_amount = T::Currency::unreserve(&p.proposer, p.bond); @@ -982,6 +994,6 @@ where { type Type = ::AccountId; fn get() -> Self::Type { - >::account_id() + crate::Pallet::::account_id() } } diff --git a/substrate/frame/treasury/src/tests.rs b/substrate/frame/treasury/src/tests.rs index f38a06f1fdf4..a895ea8151af 100644 --- a/substrate/frame/treasury/src/tests.rs +++ b/substrate/frame/treasury/src/tests.rs @@ -219,7 +219,7 @@ fn get_payment_id(i: SpendIndex) -> Option { fn genesis_config_works() { ExtBuilder::default().build().execute_with(|| { assert_eq!(Treasury::pot(), 0); - assert_eq!(Treasury::proposal_count(), 0); + assert_eq!(ProposalCount::::get(), 0); }); } @@ -437,9 +437,9 @@ fn remove_already_removed_approval_fails() { assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 100, 3)); - assert_eq!(Treasury::approvals(), vec![0]); + assert_eq!(Approvals::::get(), vec![0]); assert_ok!(Treasury::remove_approval(RuntimeOrigin::root(), 0)); - assert_eq!(Treasury::approvals(), vec![]); + assert_eq!(Approvals::::get(), vec![]); assert_noop!( Treasury::remove_approval(RuntimeOrigin::root(), 0), diff --git a/substrate/primitives/merkle-mountain-range/src/lib.rs b/substrate/primitives/merkle-mountain-range/src/lib.rs index 3740047e0278..061e5dbb6c7d 100644 --- a/substrate/primitives/merkle-mountain-range/src/lib.rs +++ b/substrate/primitives/merkle-mountain-range/src/lib.rs @@ -144,7 +144,7 @@ impl FullLeaf for OpaqueLeaf { /// /// It is different from [`OpaqueLeaf`], because it does implement `Codec` /// and the encoding has to match raw `Vec` encoding. -#[derive(codec::Encode, codec::Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] +#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)] pub struct EncodableOpaqueLeaf(pub Vec); impl EncodableOpaqueLeaf { diff --git a/substrate/primitives/wasm-interface/Cargo.toml b/substrate/primitives/wasm-interface/Cargo.toml index 96ea8f4235d5..9d0310fd22e8 100644 --- a/substrate/primitives/wasm-interface/Cargo.toml +++ b/substrate/primitives/wasm-interface/Cargo.toml @@ -25,5 +25,9 @@ anyhow = { optional = true, workspace = true } [features] default = ["std"] -std = ["codec/std", "log/std"] +std = [ + "anyhow?/std", + "codec/std", + "log/std", +] wasmtime = ["anyhow", "dep:wasmtime"] diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs index cfbbab4df7ca..471919815206 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -243,7 +243,7 @@ impl PalletCmd { let state = &state_without_tracking; let runtime = self.runtime_blob(&state_without_tracking)?; let runtime_code = runtime.code()?; - let alloc_strategy = Self::alloc_strategy(runtime_code.heap_pages); + let alloc_strategy = self.alloc_strategy(runtime_code.heap_pages); let executor = WasmExecutor::<( sp_io::SubstrateHostFunctions, @@ -753,9 +753,9 @@ impl PalletCmd { } /// Allocation strategy for pallet benchmarking. - fn alloc_strategy(heap_pages: Option) -> HeapAllocStrategy { - heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |p| HeapAllocStrategy::Static { - extra_pages: p as _, + fn alloc_strategy(&self, runtime_heap_pages: Option) -> HeapAllocStrategy { + self.heap_pages.or(runtime_heap_pages).map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |p| { + HeapAllocStrategy::Static { extra_pages: p as _ } }) } diff --git a/substrate/utils/frame/omni-bencher/Cargo.toml b/substrate/utils/frame/omni-bencher/Cargo.toml index 89ef2a48e01d..e2ffca8b4714 100644 --- a/substrate/utils/frame/omni-bencher/Cargo.toml +++ b/substrate/utils/frame/omni-bencher/Cargo.toml @@ -6,6 +6,7 @@ authors.workspace = true edition.workspace = true repository.workspace = true license.workspace = true +readme = "README.md" [lints] workspace = true @@ -17,5 +18,5 @@ frame-benchmarking-cli = { workspace = true } sc-cli = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-statement-store = { workspace = true, default-features = true } -sp-tracing = { workspace = true } +tracing-subscriber = { workspace = true } log = { workspace = true } diff --git a/substrate/utils/frame/omni-bencher/README.md b/substrate/utils/frame/omni-bencher/README.md new file mode 100644 index 000000000000..29bfaeb6450b --- /dev/null +++ b/substrate/utils/frame/omni-bencher/README.md @@ -0,0 +1,60 @@ +# Polkadot Omni Benchmarking CLI + +The Polkadot Omni benchmarker allows to benchmark the extrinsics of any Polkadot runtime. It is +meant to replace the current manual integration of the `benchmark pallet` into every parachain node. +This reduces duplicate code and makes maintenance for builders easier. The CLI is currently only +able to benchmark extrinsics. In the future it is planned to extend this to some other areas. + +General FRAME runtimes could also be used with this benchmarker, as long as they don't utilize any +host functions that are not part of the Polkadot host specification. + +## Installation + +Directly via crates.io: + +```sh +cargo install frame-omni-bencher --profile=production +``` + +from GitHub: + +```sh +cargo install --git https://github.com/paritytech/polkadot-sdk frame-omni-bencher --profile=production +``` + +or locally from the sources: + +```sh +cargo install --path substrate/utils/frame/omni-bencher --profile=production +``` + +Check the installed version and print the docs: + +```sh +frame-omni-bencher --help +``` + +## Usage + +First we need to ensure that there is a runtime available. As example we will build the Westend +runtime: + +```sh +cargo build -p westend-runtime --profile production --features runtime-benchmarks +``` + +Now as an example, we benchmark the `balances` pallet: + +```sh +frame-omni-bencher v1 benchmark pallet \ +--runtime target/release/wbuild/westend-runtime/westend-runtime.compact.compressed.wasm \ +--pallet "pallet_balances" --extrinsic "" +``` + +The `--steps`, `--repeat`, `--heap-pages` and `--wasm-execution` arguments have sane defaults and do +not need be passed explicitly anymore. + +## Backwards Compatibility + +The exposed pallet sub-command is identical as the node-integrated CLI. The only difference is that +it needs to be prefixed with a `v1` to ensure drop-in compatibility. diff --git a/substrate/utils/frame/omni-bencher/src/command.rs b/substrate/utils/frame/omni-bencher/src/command.rs index f0159f4307d6..19177ed549b7 100644 --- a/substrate/utils/frame/omni-bencher/src/command.rs +++ b/substrate/utils/frame/omni-bencher/src/command.rs @@ -36,13 +36,19 @@ use sp_runtime::traits::BlakeTwo256; /// Directly via crates.io: /// /// ```sh -/// cargo install --locked frame-omni-bencher +/// cargo install frame-omni-bencher --profile=production /// ``` /// -/// or when the sources are locally checked out: +/// from GitHub: /// /// ```sh -/// cargo install --locked --path substrate/utils/frame/omni-bencher --profile=production +/// cargo install --git https://github.com/paritytech/polkadot-sdk frame-omni-bencher --profile=production +/// ``` +/// +/// or locally from the sources: +/// +/// ```sh +/// cargo install --path substrate/utils/frame/omni-bencher --profile=production /// ``` /// /// Check the installed version and print the docs: @@ -60,7 +66,7 @@ use sp_runtime::traits::BlakeTwo256; /// cargo build -p westend-runtime --profile production --features runtime-benchmarks /// ``` /// -/// Now as example we benchmark `pallet_balances`: +/// Now as an example, we benchmark the `balances` pallet: /// /// ```sh /// frame-omni-bencher v1 benchmark pallet \ diff --git a/substrate/utils/frame/omni-bencher/src/main.rs b/substrate/utils/frame/omni-bencher/src/main.rs index a8893b5a79af..ef3450add8e4 100644 --- a/substrate/utils/frame/omni-bencher/src/main.rs +++ b/substrate/utils/frame/omni-bencher/src/main.rs @@ -19,10 +19,22 @@ mod command; use clap::Parser; use sc_cli::Result; +use tracing_subscriber::EnvFilter; fn main() -> Result<()> { - sp_tracing::try_init_simple(); + setup_logger(); + log::warn!("The FRAME omni-bencher is not yet battle tested - double check the results.",); command::Command::parse().run() } + +/// Setup logging with `info` as default level. Can be set via `RUST_LOG` env. +fn setup_logger() { + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); + + tracing_subscriber::fmt() + .with_env_filter(env_filter) + .with_writer(std::io::stderr) + .init(); +} diff --git a/substrate/utils/frame/remote-externalities/src/lib.rs b/substrate/utils/frame/remote-externalities/src/lib.rs index 40864085349b..955e79008c8c 100644 --- a/substrate/utils/frame/remote-externalities/src/lib.rs +++ b/substrate/utils/frame/remote-externalities/src/lib.rs @@ -55,7 +55,7 @@ type ChildKeyValues = Vec<(ChildInfo, Vec)>; type SnapshotVersion = Compact; const LOG_TARGET: &str = "remote-ext"; -const DEFAULT_HTTP_ENDPOINT: &str = "https://polkadot-try-runtime-node.parity-chains.parity.io:443"; +const DEFAULT_HTTP_ENDPOINT: &str = "https://try-runtime.polkadot.io:443"; const SNAPSHOT_VERSION: SnapshotVersion = Compact(4); /// The snapshot that we store on disk. diff --git a/substrate/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs b/substrate/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs index c0333bb7dac0..c455d8d39b7d 100644 --- a/substrate/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs +++ b/substrate/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs @@ -21,8 +21,9 @@ use jsonrpsee::{ core::RpcResult, proc_macros::rpc, types::error::{ErrorCode, ErrorObject, ErrorObjectOwned}, + Extensions, }; -use sc_rpc_api::DenyUnsafe; +use sc_rpc_api::check_if_safe; use serde::{Deserialize, Serialize}; use sp_runtime::traits::Block as BlockT; use std::sync::Arc; @@ -134,7 +135,7 @@ pub trait StateMigrationApi { /// This call is performed locally without submitting any transactions. Thus executing this /// won't change any state. Nonetheless it is a VERY costly call that should be /// only exposed to trusted peers. - #[method(name = "state_trieMigrationStatus")] + #[method(name = "state_trieMigrationStatus", with_extensions)] fn call(&self, at: Option) -> RpcResult; } @@ -142,14 +143,13 @@ pub trait StateMigrationApi { pub struct StateMigration { client: Arc, backend: Arc, - deny_unsafe: DenyUnsafe, _marker: std::marker::PhantomData<(B, BA)>, } impl StateMigration { /// Create new state migration rpc for the given reference to the client. - pub fn new(client: Arc, backend: Arc, deny_unsafe: DenyUnsafe) -> Self { - StateMigration { client, backend, deny_unsafe, _marker: Default::default() } + pub fn new(client: Arc, backend: Arc) -> Self { + StateMigration { client, backend, _marker: Default::default() } } } @@ -159,8 +159,12 @@ where C: Send + Sync + 'static + sc_client_api::HeaderBackend, BA: 'static + sc_client_api::backend::Backend, { - fn call(&self, at: Option<::Hash>) -> RpcResult { - self.deny_unsafe.check_if_safe()?; + fn call( + &self, + ext: &Extensions, + at: Option<::Hash>, + ) -> RpcResult { + check_if_safe(ext)?; let hash = at.unwrap_or_else(|| self.client.info().best_hash); let state = self.backend.state_at(hash).map_err(error_into_rpc_err)?; diff --git a/substrate/utils/frame/rpc/system/src/lib.rs b/substrate/utils/frame/rpc/system/src/lib.rs index 8cb7b785bc7c..9fcaa53a35d8 100644 --- a/substrate/utils/frame/rpc/system/src/lib.rs +++ b/substrate/utils/frame/rpc/system/src/lib.rs @@ -24,9 +24,9 @@ use jsonrpsee::{ core::{async_trait, RpcResult}, proc_macros::rpc, types::error::ErrorObject, + Extensions, }; -use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; use sp_api::ApiExt; use sp_block_builder::BlockBuilder; @@ -49,7 +49,7 @@ pub trait SystemApi { async fn nonce(&self, account: AccountId) -> RpcResult; /// Dry run an extrinsic at a given block. Return SCALE encoded ApplyExtrinsicResult. - #[method(name = "system_dryRun", aliases = ["system_dryRunAt"])] + #[method(name = "system_dryRun", aliases = ["system_dryRunAt"], with_extensions)] async fn dry_run(&self, extrinsic: Bytes, at: Option) -> RpcResult; } @@ -74,14 +74,13 @@ impl From for i32 { pub struct System { client: Arc, pool: Arc

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

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

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

, - /// Whether to deny unsafe calls - pub deny_unsafe: DenyUnsafe, } #[docify::export] @@ -57,11 +56,11 @@ where C::Api: substrate_frame_rpc_system::AccountNonceApi, P: TransactionPool + 'static, { - use substrate_frame_rpc_system::{System, SystemApiServer}; + use polkadot_sdk::substrate_frame_rpc_system::{System, SystemApiServer}; let mut module = RpcModule::new(()); - let FullDeps { client, pool, deny_unsafe } = deps; + let FullDeps { client, pool } = deps; - module.merge(System::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; + module.merge(System::new(client.clone(), pool.clone()).into_rpc())?; Ok(module) } diff --git a/templates/minimal/node/src/service.rs b/templates/minimal/node/src/service.rs index decb9d6c636e..ce3afe1e6b76 100644 --- a/templates/minimal/node/src/service.rs +++ b/templates/minimal/node/src/service.rs @@ -17,12 +17,15 @@ use futures::FutureExt; use minimal_template_runtime::{interface::OpaqueBlock as Block, RuntimeApi}; -use sc_client_api::backend::Backend; -use sc_executor::WasmExecutor; -use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; -use sc_telemetry::{Telemetry, TelemetryWorker}; -use sc_transaction_pool_api::OffchainTransactionPoolFactory; -use sp_runtime::traits::Block as BlockT; +use polkadot_sdk::{ + sc_client_api::backend::Backend, + sc_executor::WasmExecutor, + sc_service::{error::Error as ServiceError, Configuration, TaskManager}, + sc_telemetry::{Telemetry, TelemetryWorker}, + sc_transaction_pool_api::OffchainTransactionPoolFactory, + sp_runtime::traits::Block as BlockT, + *, +}; use std::sync::Arc; use crate::cli::Consensus; @@ -168,9 +171,8 @@ pub fn new_full::Ha let client = client.clone(); let pool = transaction_pool.clone(); - Box::new(move |deny_unsafe, _| { - let deps = - crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe }; + Box::new(move |_| { + let deps = crate::rpc::FullDeps { client: client.clone(), pool: pool.clone() }; crate::rpc::create_full(deps).map_err(Into::into) }) }; diff --git a/templates/minimal/pallets/template/Cargo.toml b/templates/minimal/pallets/template/Cargo.toml index 9d231fe7d7d4..9a02d4daeaac 100644 --- a/templates/minimal/pallets/template/Cargo.toml +++ b/templates/minimal/pallets/template/Cargo.toml @@ -13,18 +13,14 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { features = [ - "derive", -], workspace = true } -scale-info = { features = [ - "derive", -], workspace = true } -frame = { features = [ +codec = { features = ["derive"], workspace = true } +scale-info = { features = ["derive"], workspace = true } +polkadot-sdk = { workspace = true, default-features = false, features = [ "experimental", "runtime", -], workspace = true } +] } [features] default = ["std"] -std = ["codec/std", "frame/std", "scale-info/std"] +std = ["codec/std", "polkadot-sdk/std", "scale-info/std"] diff --git a/templates/minimal/pallets/template/src/lib.rs b/templates/minimal/pallets/template/src/lib.rs index 92b90ad4412b..b8a8614932a6 100644 --- a/templates/minimal/pallets/template/src/lib.rs +++ b/templates/minimal/pallets/template/src/lib.rs @@ -5,7 +5,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame::prelude::*; +use polkadot_sdk::polkadot_sdk_frame as frame; // Re-export all pallet parts, this is needed to properly import the pallet into the runtime. pub use pallet::*; @@ -15,7 +15,7 @@ pub mod pallet { use super::*; #[pallet::config] - pub trait Config: frame_system::Config {} + pub trait Config: polkadot_sdk::frame_system::Config {} #[pallet::pallet] pub struct Pallet(_); diff --git a/templates/minimal/runtime/Cargo.toml b/templates/minimal/runtime/Cargo.toml index 5d3cf8492e52..49ddf3987e96 100644 --- a/templates/minimal/runtime/Cargo.toml +++ b/templates/minimal/runtime/Cargo.toml @@ -12,47 +12,29 @@ publish = false [dependencies] codec = { workspace = true } scale-info = { workspace = true } - -# this is a frame-based runtime, thus importing `frame` with runtime feature enabled. -frame = { features = [ +polkadot-sdk = { workspace = true, features = [ "experimental", + "pallet-balances", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", "runtime", -], workspace = true } - -# pallets that we want to use -pallet-balances = { workspace = true } -pallet-sudo = { workspace = true } -pallet-timestamp = { workspace = true } -pallet-transaction-payment = { workspace = true } -pallet-transaction-payment-rpc-runtime-api = { workspace = true } - -# genesis builder that allows us to interact with runtime genesis config -sp-genesis-builder = { workspace = true } -sp-runtime = { features = ["serde"], workspace = true } +] } # local pallet templates pallet-minimal-template = { workspace = true } [build-dependencies] -substrate-wasm-builder = { optional = true, workspace = true, default-features = true } +polkadot-sdk = { optional = true, workspace = true, features = [ + "substrate-wasm-builder", +] } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - - "frame/std", - - "pallet-balances/std", - "pallet-sudo/std", - "pallet-timestamp/std", - "pallet-transaction-payment-rpc-runtime-api/std", - "pallet-transaction-payment/std", - "pallet-minimal-template/std", - - "sp-genesis-builder/std", - "sp-runtime/std", - "substrate-wasm-builder", + "polkadot-sdk/std", + "scale-info/std", ] diff --git a/templates/minimal/runtime/build.rs b/templates/minimal/runtime/build.rs index e6f92757e225..2cb2966b5d82 100644 --- a/templates/minimal/runtime/build.rs +++ b/templates/minimal/runtime/build.rs @@ -18,6 +18,6 @@ fn main() { #[cfg(feature = "std")] { - substrate_wasm_builder::WasmBuilder::build_using_defaults(); + polkadot_sdk::substrate_wasm_builder::WasmBuilder::build_using_defaults(); } } diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index 08ad537ecdd1..474d9ddfb9e8 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -26,20 +26,14 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); extern crate alloc; use alloc::{vec, vec::Vec}; -use frame::{ - deps::frame_support::{ - genesis_builder_helper::{build_state, get_preset}, - runtime, - weights::{FixedFee, NoFee}, - }, - prelude::*, - runtime::{ - apis::{ - self, impl_runtime_apis, ApplyExtrinsicResult, CheckInherentsResult, - ExtrinsicInclusionMode, OpaqueMetadata, - }, +use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; +use polkadot_sdk::{ + polkadot_sdk_frame::{ + self as frame, prelude::*, + runtime::{apis, prelude::*}, }, + *, }; /// The runtime version. @@ -83,7 +77,7 @@ type SignedExtra = ( ); // Composes the runtime by adding all the used pallets and deriving necessary types. -#[runtime] +#[frame_construct_runtime] mod runtime { /// The main runtime type. #[runtime::runtime] @@ -171,8 +165,6 @@ type Header = HeaderFor; type RuntimeExecutive = Executive, Runtime, AllPalletsWithSystem>; -use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; - impl_runtime_apis! { impl apis::Core for Runtime { fn version() -> RuntimeVersion { @@ -296,7 +288,7 @@ impl_runtime_apis! { // https://github.com/paritytech/substrate/issues/10579#issuecomment-1600537558 pub mod interface { use super::Runtime; - use frame::deps::frame_system; + use polkadot_sdk::{polkadot_sdk_frame as frame, *}; pub type Block = super::Block; pub use frame::runtime::types_common::OpaqueBlock; diff --git a/templates/parachain/README.md b/templates/parachain/README.md index b7fa2bed0ed9..b912d8e005c7 100644 --- a/templates/parachain/README.md +++ b/templates/parachain/README.md @@ -59,15 +59,22 @@ docker build . -t polkadot-sdk-parachain-template You can grab a [released binary](https://github.com/paritytech/zombienet/releases/latest) or use an [npm version](https://www.npmjs.com/package/@zombienet/cli). This template produces a parachain node. +You can install it in your environment by running: + +```sh +cargo install --path node +``` + You still need a relaychain node - you can download the `polkadot` (and the accompanying `polkadot-prepare-worker` and `polkadot-execute-worker`) binaries from [Polkadot SDK releases](https://github.com/paritytech/polkadot-sdk/releases/latest). -Make sure to bring the parachain node - as well as `polkadot`, `polkadot-prepare-worker`, `polkadot-execute-worker`, -and `zombienet` - into `PATH` like so: +In addition to the installed parachain node, make sure to bring +`zombienet`, `polkadot`, `polkadot-prepare-worker`, and `polkadot-execute-worker` +into `PATH`, for example: ```sh -export PATH="./target/release/:$PATH" +export PATH=":$PATH" ``` This way, we can conveniently use them in the following steps. diff --git a/templates/parachain/node/src/command.rs b/templates/parachain/node/src/command.rs index eba7fdcdae71..f346ae4386a6 100644 --- a/templates/parachain/node/src/command.rs +++ b/templates/parachain/node/src/command.rs @@ -1,5 +1,3 @@ -use std::net::SocketAddr; - use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions; use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; @@ -7,7 +5,7 @@ use log::info; use parachain_template_runtime::Block; use sc_cli::{ ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, - NetworkParams, Result, SharedParams, SubstrateCli, + NetworkParams, Result, RpcEndpoint, SharedParams, SubstrateCli, }; use sc_service::config::{BasePath, PrometheusConfig}; @@ -297,7 +295,7 @@ impl CliConfiguration for RelayChainCli { .or_else(|| self.base_path.clone().map(Into::into))) } - fn rpc_addr(&self, default_listen_port: u16) -> Result> { + fn rpc_addr(&self, default_listen_port: u16) -> Result>> { self.base.base.rpc_addr(default_listen_port) } diff --git a/templates/parachain/node/src/rpc.rs b/templates/parachain/node/src/rpc.rs index bb52b974f0ce..4937469e11e2 100644 --- a/templates/parachain/node/src/rpc.rs +++ b/templates/parachain/node/src/rpc.rs @@ -9,7 +9,6 @@ use std::sync::Arc; use parachain_template_runtime::{opaque::Block, AccountId, Balance, Nonce}; -pub use sc_rpc::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; @@ -24,8 +23,6 @@ pub struct FullDeps { pub client: Arc, /// Transaction pool instance. pub pool: Arc

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

, - /// Whether to deny unsafe calls - pub deny_unsafe: DenyUnsafe, } /// Instantiate all full RPC extensions. @@ -43,9 +39,9 @@ where use substrate_frame_rpc_system::{System, SystemApiServer}; let mut module = RpcModule::new(()); - let FullDeps { client, pool, deny_unsafe } = deps; + let FullDeps { client, pool } = deps; - module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; + module.merge(System::new(client.clone(), pool).into_rpc())?; module.merge(TransactionPayment::new(client).into_rpc())?; // Extend this RPC with a custom API by using the following syntax. diff --git a/templates/solochain/node/src/service.rs b/templates/solochain/node/src/service.rs index 7eef9766b107..2b43ecfa1ce2 100644 --- a/templates/solochain/node/src/service.rs +++ b/templates/solochain/node/src/service.rs @@ -212,9 +212,8 @@ pub fn new_full< let client = client.clone(); let pool = transaction_pool.clone(); - Box::new(move |deny_unsafe, _| { - let deps = - crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe }; + Box::new(move |_| { + let deps = crate::rpc::FullDeps { client: client.clone(), pool: pool.clone() }; crate::rpc::create_full(deps).map_err(Into::into) }) }; diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 2bfed05cd178..6d380a4bcbb7 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -120,6 +120,9 @@ std = [ "pallet-recovery?/std", "pallet-referenda?/std", "pallet-remark?/std", + "pallet-revive-fixtures?/std", + "pallet-revive-mock-network?/std", + "pallet-revive?/std", "pallet-root-offences?/std", "pallet-root-testing?/std", "pallet-safe-mode?/std", @@ -304,6 +307,8 @@ runtime-benchmarks = [ "pallet-recovery?/runtime-benchmarks", "pallet-referenda?/runtime-benchmarks", "pallet-remark?/runtime-benchmarks", + "pallet-revive-mock-network?/runtime-benchmarks", + "pallet-revive?/runtime-benchmarks", "pallet-root-offences?/runtime-benchmarks", "pallet-safe-mode?/runtime-benchmarks", "pallet-salary?/runtime-benchmarks", @@ -433,6 +438,7 @@ try-runtime = [ "pallet-recovery?/try-runtime", "pallet-referenda?/try-runtime", "pallet-remark?/try-runtime", + "pallet-revive?/try-runtime", "pallet-root-offences?/try-runtime", "pallet-root-testing?/try-runtime", "pallet-safe-mode?/try-runtime", @@ -486,6 +492,7 @@ serde = [ "pallet-parameters?/serde", "pallet-referenda?/serde", "pallet-remark?/serde", + "pallet-revive?/serde", "pallet-state-trie-migration?/serde", "pallet-tips?/serde", "pallet-transaction-payment?/serde", @@ -529,37 +536,8 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-fixtures", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ - "assets-common", - "binary-merkle-tree", - "bp-header-chain", - "bp-messages", - "bp-parachains", - "bp-polkadot", - "bp-polkadot-core", - "bp-relayers", - "bp-runtime", - "bp-test-utils", - "bp-xcm-bridge-hub", - "bp-xcm-bridge-hub-router", - "bridge-hub-common", - "bridge-runtime-common", - "cumulus-pallet-aura-ext", - "cumulus-pallet-dmp-queue", - "cumulus-pallet-parachain-system", - "cumulus-pallet-parachain-system-proc-macro", - "cumulus-pallet-session-benchmarking", - "cumulus-pallet-solo-to-para", - "cumulus-pallet-xcm", - "cumulus-pallet-xcmp-queue", - "cumulus-ping", - "cumulus-primitives-aura", - "cumulus-primitives-core", - "cumulus-primitives-parachain-inherent", - "cumulus-primitives-proof-size-hostfunction", - "cumulus-primitives-storage-weight-reclaim", - "cumulus-primitives-timestamp", - "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", @@ -573,134 +551,8 @@ runtime = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", - "pallet-alliance", - "pallet-asset-conversion", - "pallet-asset-conversion-ops", - "pallet-asset-conversion-tx-payment", - "pallet-asset-rate", - "pallet-asset-tx-payment", - "pallet-assets", - "pallet-assets-freezer", - "pallet-atomic-swap", - "pallet-aura", - "pallet-authority-discovery", - "pallet-authorship", - "pallet-babe", - "pallet-bags-list", - "pallet-balances", - "pallet-beefy", - "pallet-beefy-mmr", - "pallet-bounties", - "pallet-bridge-grandpa", - "pallet-bridge-messages", - "pallet-bridge-parachains", - "pallet-bridge-relayers", - "pallet-broker", - "pallet-child-bounties", - "pallet-collator-selection", - "pallet-collective", - "pallet-collective-content", - "pallet-contracts", - "pallet-contracts-proc-macro", - "pallet-contracts-uapi", - "pallet-conviction-voting", - "pallet-core-fellowship", - "pallet-delegated-staking", - "pallet-democracy", - "pallet-dev-mode", - "pallet-election-provider-multi-phase", - "pallet-election-provider-support-benchmarking", - "pallet-elections-phragmen", - "pallet-fast-unstake", - "pallet-glutton", - "pallet-grandpa", - "pallet-identity", - "pallet-im-online", - "pallet-indices", - "pallet-insecure-randomness-collective-flip", - "pallet-lottery", - "pallet-membership", - "pallet-message-queue", - "pallet-migrations", - "pallet-mixnet", - "pallet-mmr", - "pallet-multisig", - "pallet-nft-fractionalization", - "pallet-nfts", - "pallet-nfts-runtime-api", - "pallet-nis", - "pallet-node-authorization", - "pallet-nomination-pools", - "pallet-nomination-pools-benchmarking", - "pallet-nomination-pools-runtime-api", - "pallet-offences", - "pallet-offences-benchmarking", - "pallet-paged-list", - "pallet-parameters", - "pallet-preimage", - "pallet-proxy", - "pallet-ranked-collective", - "pallet-recovery", - "pallet-referenda", - "pallet-remark", - "pallet-root-offences", - "pallet-root-testing", - "pallet-safe-mode", - "pallet-salary", - "pallet-scheduler", - "pallet-scored-pool", - "pallet-session", - "pallet-session-benchmarking", - "pallet-skip-feeless-payment", - "pallet-society", - "pallet-staking", - "pallet-staking-reward-curve", - "pallet-staking-reward-fn", - "pallet-staking-runtime-api", - "pallet-state-trie-migration", - "pallet-statement", - "pallet-sudo", - "pallet-timestamp", - "pallet-tips", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "pallet-transaction-storage", - "pallet-treasury", - "pallet-tx-pause", - "pallet-uniques", - "pallet-utility", - "pallet-vesting", - "pallet-whitelist", - "pallet-xcm", - "pallet-xcm-benchmarks", - "pallet-xcm-bridge-hub", - "pallet-xcm-bridge-hub-router", - "parachains-common", - "polkadot-core-primitives", - "polkadot-parachain-primitives", - "polkadot-primitives", - "polkadot-runtime-common", - "polkadot-runtime-metrics", - "polkadot-runtime-parachains", "polkadot-sdk-frame", "polkadot-sdk-frame?/runtime", - "sc-chain-spec-derive", - "sc-tracing-proc-macro", - "slot-range-helper", - "snowbridge-beacon-primitives", - "snowbridge-core", - "snowbridge-ethereum", - "snowbridge-outbound-queue-merkle-tree", - "snowbridge-outbound-queue-runtime-api", - "snowbridge-pallet-ethereum-client", - "snowbridge-pallet-ethereum-client-fixtures", - "snowbridge-pallet-inbound-queue", - "snowbridge-pallet-inbound-queue-fixtures", - "snowbridge-pallet-outbound-queue", - "snowbridge-pallet-system", - "snowbridge-router-primitives", - "snowbridge-runtime-common", - "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", @@ -747,21 +599,16 @@ runtime = [ "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", - "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", - "substrate-bip39", - "testnet-parachains-constants", - "tracing-gum-proc-macro", - "xcm-procedural", - "xcm-runtime-apis", ] -node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-parachain-lib", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] +node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-parachain-lib", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] tuples-96 = [ "frame-support-procedural?/tuples-96", "frame-support?/tuples-96", ] +riscv = [ + "pallet-revive-fixtures?/riscv", + "pallet-revive?/riscv", +] [package.edition] workspace = true @@ -1334,6 +1181,26 @@ path = "../substrate/frame/remark" default-features = false optional = true +[dependencies.pallet-revive] +path = "../substrate/frame/revive" +default-features = false +optional = true + +[dependencies.pallet-revive-fixtures] +path = "../substrate/frame/revive/fixtures" +default-features = false +optional = true + +[dependencies.pallet-revive-proc-macro] +path = "../substrate/frame/revive/proc-macro" +default-features = false +optional = true + +[dependencies.pallet-revive-uapi] +path = "../substrate/frame/revive/uapi" +default-features = false +optional = true + [dependencies.pallet-root-offences] path = "../substrate/frame/root-offences" default-features = false @@ -2024,6 +1891,11 @@ path = "../substrate/frame/contracts/mock-network" default-features = false optional = true +[dependencies.pallet-revive-mock-network] +path = "../substrate/frame/revive/mock-network" +default-features = false +optional = true + [dependencies.pallet-transaction-payment-rpc] path = "../substrate/frame/transaction-payment/rpc" default-features = false @@ -2615,5 +2487,5 @@ default-features = false optional = true [package.metadata.docs.rs] -features = ["node", "runtime"] +features = ["node", "runtime-full"] targets = ["x86_64-unknown-linux-gnu"] diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 5820fa803547..b7b9c15fe588 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -576,6 +576,26 @@ pub use pallet_referenda; #[cfg(feature = "pallet-remark")] pub use pallet_remark; +/// FRAME pallet for PolkaVM contracts. +#[cfg(feature = "pallet-revive")] +pub use pallet_revive; + +/// Fixtures for testing and benchmarking. +#[cfg(feature = "pallet-revive-fixtures")] +pub use pallet_revive_fixtures; + +/// A mock network for testing pallet-revive. +#[cfg(feature = "pallet-revive-mock-network")] +pub use pallet_revive_mock_network; + +/// Procedural macros used in pallet_revive. +#[cfg(feature = "pallet-revive-proc-macro")] +pub use pallet_revive_proc_macro; + +/// Exposes all the host functions that a contract can import. +#[cfg(feature = "pallet-revive-uapi")] +pub use pallet_revive_uapi; + /// FRAME root offences pallet. #[cfg(feature = "pallet-root-offences")] pub use pallet_root_offences;