From dd9924fad0cd9314f21eff621208dc2ec60e0cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 31 Oct 2024 13:18:04 +0100 Subject: [PATCH 1/6] Remove `riscv` feature flag (#6305) Since /~https://github.com/paritytech/polkadot-sdk/pull/6266 we no longer require a custom toolchain to build the `pallet-revive-fixtures`. Hence we no longer have to guard the build behind a feature flag. --------- Co-authored-by: GitHub Action --- .config/zepter.yaml | 2 +- .github/scripts/cmd/test_cmd.py | 26 +- .github/workflows/runtimes-matrix.json | 2 +- .../workflows/tests-linux-stable-coverage.yml | 4 +- .github/workflows/tests-linux-stable.yml | 4 +- prdoc/pr_6305.prdoc | 17 + scripts/generate-umbrella.py | 2 - substrate/bin/node/cli/Cargo.toml | 1 - substrate/bin/node/runtime/Cargo.toml | 1 - substrate/frame/revive/Cargo.toml | 4 - substrate/frame/revive/fixtures/Cargo.toml | 4 - substrate/frame/revive/fixtures/build.rs | 348 +- substrate/frame/revive/fixtures/src/lib.rs | 7 - .../frame/revive/mock-network/Cargo.toml | 1 - .../frame/revive/mock-network/src/lib.rs | 2 +- substrate/frame/revive/rpc/Cargo.toml | 11 +- substrate/frame/revive/rpc/Dockerfile | 1 + substrate/frame/revive/rpc/examples/README.md | 3 +- substrate/frame/revive/rpc/src/tests.rs | 2 - .../frame/revive/src/benchmarking/mod.rs | 4 +- .../frame/revive/src/benchmarking_dummy.rs | 37 - substrate/frame/revive/src/evm/runtime.rs | 1 - substrate/frame/revive/src/exec.rs | 6 +- substrate/frame/revive/src/lib.rs | 1 - substrate/frame/revive/src/storage.rs | 1 - substrate/frame/revive/src/tests.rs | 7080 ++++++++--------- .../frame/revive/src/tests/test_debug.rs | 248 +- substrate/frame/revive/src/wasm/mod.rs | 2 +- umbrella/Cargo.toml | 6 - 29 files changed, 3841 insertions(+), 3987 deletions(-) create mode 100644 prdoc/pr_6305.prdoc delete mode 100644 substrate/frame/revive/src/benchmarking_dummy.rs diff --git a/.config/zepter.yaml b/.config/zepter.yaml index 7a67ba2695cf..24441e90b1a0 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,riscv,runtime,with-tracing,tuples-96,with-tracing', '-p=polkadot-sdk' ] + - [ $check.0, '--features=serde,experimental,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/scripts/cmd/test_cmd.py b/.github/scripts/cmd/test_cmd.py index faad3f261b9a..7b29fbfe90d8 100644 --- a/.github/scripts/cmd/test_cmd.py +++ b/.github/scripts/cmd/test_cmd.py @@ -13,7 +13,7 @@ "path": "substrate/frame", "header": "substrate/HEADER-APACHE2", "template": "substrate/.maintain/frame-weight-template.hbs", - "bench_features": "runtime-benchmarks,riscv", + "bench_features": "runtime-benchmarks", "bench_flags": "--flag1 --flag2" }, { @@ -67,7 +67,7 @@ def setUp(self): self.patcher6 = patch('importlib.util.spec_from_file_location', return_value=MagicMock()) self.patcher7 = patch('importlib.util.module_from_spec', return_value=MagicMock()) self.patcher8 = patch('cmd.generate_prdoc.main', return_value=0) - + self.mock_open = self.patcher1.start() self.mock_json_load = self.patcher2.start() self.mock_parse_args = self.patcher3.start() @@ -101,7 +101,7 @@ def test_bench_command_normal_execution_all_runtimes(self): clean=False, image=None ), []) - + self.mock_popen.return_value.read.side_effect = [ "pallet_balances\npallet_staking\npallet_something\n", # Output for dev runtime "pallet_balances\npallet_staking\npallet_something\n", # Output for westend runtime @@ -109,7 +109,7 @@ def test_bench_command_normal_execution_all_runtimes(self): "pallet_balances\npallet_staking\npallet_something\n", # Output for asset-hub-westend runtime "./substrate/frame/balances/Cargo.toml\n", # Mock manifest path for dev -> pallet_balances ] - + with patch('sys.exit') as mock_exit: import cmd cmd.main() @@ -117,11 +117,11 @@ def test_bench_command_normal_execution_all_runtimes(self): expected_calls = [ # Build calls - call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks,riscv"), + call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks"), call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), call("forklift cargo build -p rococo-runtime --profile release --features=runtime-benchmarks"), call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), - + call(get_mock_bench_output( runtime='kitchensink', pallets='pallet_balances', @@ -162,7 +162,7 @@ def test_bench_command_normal_execution(self): self.mock_popen.return_value.read.side_effect = [ "pallet_balances\npallet_staking\npallet_something\n", # Output for westend runtime ] - + with patch('sys.exit') as mock_exit: import cmd cmd.main() @@ -171,7 +171,7 @@ def test_bench_command_normal_execution(self): expected_calls = [ # Build calls call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), - + # Westend runtime calls call(get_mock_bench_output( runtime='westend', @@ -205,7 +205,7 @@ def test_bench_command_normal_execution_xcm(self): self.mock_popen.return_value.read.side_effect = [ "pallet_balances\npallet_staking\npallet_something\npallet_xcm_benchmarks::generic\n", # Output for westend runtime ] - + with patch('sys.exit') as mock_exit: import cmd cmd.main() @@ -214,7 +214,7 @@ def test_bench_command_normal_execution_xcm(self): expected_calls = [ # Build calls call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), - + # Westend runtime calls call(get_mock_bench_output( runtime='westend', @@ -241,7 +241,7 @@ def test_bench_command_two_runtimes_two_pallets(self): "pallet_staking\npallet_balances\n", # Output for westend runtime "pallet_staking\npallet_balances\n", # Output for rococo runtime ] - + with patch('sys.exit') as mock_exit: import cmd cmd.main() @@ -309,7 +309,7 @@ def test_bench_command_one_dev_runtime(self): expected_calls = [ # Build calls - call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks,riscv"), + call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output( runtime='kitchensink', @@ -429,4 +429,4 @@ def test_prdoc_command(self, mock_system, mock_parse_args): self.mock_generate_prdoc_main.assert_called_with(mock_parse_args.return_value[0]) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json index e4e3a2dbe6d1..f991db55b86d 100644 --- a/.github/workflows/runtimes-matrix.json +++ b/.github/workflows/runtimes-matrix.json @@ -5,7 +5,7 @@ "path": "substrate/frame", "header": "substrate/HEADER-APACHE2", "template": "substrate/.maintain/frame-weight-template.hbs", - "bench_features": "runtime-benchmarks,riscv", + "bench_features": "runtime-benchmarks", "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic,pallet_nomination_pools,pallet_remark,pallet_transaction_storage", "uri": null, "is_relay": false diff --git a/.github/workflows/tests-linux-stable-coverage.yml b/.github/workflows/tests-linux-stable-coverage.yml index 90d7bc34a926..c5af6bcae77f 100644 --- a/.github/workflows/tests-linux-stable-coverage.yml +++ b/.github/workflows/tests-linux-stable-coverage.yml @@ -56,7 +56,7 @@ jobs: --no-report --release --workspace --locked --no-fail-fast - --features try-runtime,ci-only-tests,experimental,riscv + --features try-runtime,ci-only-tests,experimental --filter-expr " !test(/.*benchmark.*/) - test(/recovers_from_only_chunks_if_pov_large::case_1/) @@ -120,4 +120,4 @@ jobs: - uses: actions/checkout@v4 - uses: actions-ecosystem/action-remove-labels@v1 with: - labels: GHA-coverage \ No newline at end of file + labels: GHA-coverage diff --git a/.github/workflows/tests-linux-stable.yml b/.github/workflows/tests-linux-stable.yml index dd292d55e201..24b96219738a 100644 --- a/.github/workflows/tests-linux-stable.yml +++ b/.github/workflows/tests-linux-stable.yml @@ -91,7 +91,7 @@ jobs: --release \ --no-fail-fast \ --cargo-quiet \ - --features try-runtime,experimental,riscv,ci-only-tests \ + --features try-runtime,experimental,ci-only-tests \ --partition count:${{ matrix.partition }} # run runtime-api tests with `enable-staging-api` feature on the 1st node - name: runtime-api tests @@ -129,7 +129,7 @@ jobs: --release \ --no-fail-fast \ --cargo-quiet \ - --features experimental,riscv,ci-only-tests \ + --features experimental,ci-only-tests \ --filter-expr " !test(/all_security_features_work/) - test(/nonexistent_cache_dir/)" \ --partition count:${{ matrix.partition }} \ diff --git a/prdoc/pr_6305.prdoc b/prdoc/pr_6305.prdoc new file mode 100644 index 000000000000..bfc6f06b19ec --- /dev/null +++ b/prdoc/pr_6305.prdoc @@ -0,0 +1,17 @@ +title: Remove `riscv` feature flag +doc: +- audience: Runtime Dev + description: Since /~https://github.com/paritytech/polkadot-sdk/pull/6266 we no longer + require a custom toolchain to build the `pallet-revive-fixtures`. Hence we no + longer have to guard the build behind a feature flag. +crates: +- name: pallet-revive + bump: major +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive-mock-network + bump: major +- name: pallet-revive-eth-rpc + bump: major +- name: polkadot-sdk + bump: major diff --git a/scripts/generate-umbrella.py b/scripts/generate-umbrella.py index e1ef6de86f9c..8326909c3449 100644 --- a/scripts/generate-umbrella.py +++ b/scripts/generate-umbrella.py @@ -111,7 +111,6 @@ def main(path, version): "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 = { @@ -207,4 +206,3 @@ def parse_args(): if __name__ == "__main__": args = parse_args() main(args.sdk, args.version) - diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 933406670e5c..c179579c1885 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -183,7 +183,6 @@ 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/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 7acf4294c51b..3ad6315561d0 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -74,4 +74,3 @@ experimental = [ "pallet-example-tasks/experimental", ] metadata-hash = ["substrate-wasm-builder/metadata-hash"] -riscv = ["polkadot-sdk/riscv"] diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index c6e733477f38..67bc1809cad7 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -79,10 +79,6 @@ xcm-builder = { 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", diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index 1e6c950addfd..7a5452853d65 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -26,9 +26,5 @@ anyhow = { workspace = true, default-features = 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", "frame-system", "log/std", "sp-core", "sp-io", "sp-runtime"] diff --git a/substrate/frame/revive/fixtures/build.rs b/substrate/frame/revive/fixtures/build.rs index 38d63621677d..bbd986d9d44c 100644 --- a/substrate/frame/revive/fixtures/build.rs +++ b/substrate/frame/revive/fixtures/build.rs @@ -18,218 +18,200 @@ //! Compile text fixtures to PolkaVM binaries. use anyhow::Result; -fn main() -> Result<()> { - build::run() +use anyhow::{bail, Context}; +use std::{ + cfg, env, fs, + path::{Path, PathBuf}, + process::Command, +}; + +const OVERRIDE_RUSTUP_TOOLCHAIN_ENV_VAR: &str = "PALLET_REVIVE_FIXTURES_RUSTUP_TOOLCHAIN"; +const OVERRIDE_STRIP_ENV_VAR: &str = "PALLET_REVIVE_FIXTURES_STRIP"; +const OVERRIDE_OPTIMIZE_ENV_VAR: &str = "PALLET_REVIVE_FIXTURES_OPTIMIZE"; + +/// A contract entry. +struct Entry { + /// The path to the contract source file. + path: PathBuf, } -#[cfg(feature = "riscv")] -mod build { - use super::Result; - use anyhow::{bail, Context}; - use std::{ - cfg, env, fs, - path::{Path, PathBuf}, - process::Command, - }; - - const OVERRIDE_RUSTUP_TOOLCHAIN_ENV_VAR: &str = "PALLET_REVIVE_FIXTURES_RUSTUP_TOOLCHAIN"; - const OVERRIDE_STRIP_ENV_VAR: &str = "PALLET_REVIVE_FIXTURES_STRIP"; - const OVERRIDE_OPTIMIZE_ENV_VAR: &str = "PALLET_REVIVE_FIXTURES_OPTIMIZE"; +impl Entry { + /// Create a new contract entry from the given path. + fn new(path: PathBuf) -> Self { + Self { path } + } - /// A contract entry. - struct Entry { - /// The path to the contract source file. - path: PathBuf, + /// Return the path to the contract source file. + fn path(&self) -> &str { + self.path.to_str().expect("path is valid unicode; qed") } - impl Entry { - /// Create a new contract entry from the given path. - fn new(path: PathBuf) -> Self { - Self { path } - } + /// 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 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 polkavm file. + fn out_filename(&self) -> String { + format!("{}.polkavm", self.name()) + } +} - /// 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") - } +/// Collect all contract entries from the given source directory. +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 + } - /// Return the name of the polkavm file. - fn out_filename(&self) -> String { - format!("{}.polkavm", self.name()) - } - } + Some(Entry::new(path)) + }) + .collect::>() +} - /// Collect all contract entries from the given source directory. - 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)) +/// 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::>() - } + .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) +} - 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(target: &Path, current_dir: &Path) -> Result<()> { + let encoded_rustflags = ["-Dwarnings"].join("\x1f"); + + let mut build_command = Command::new(env::var("CARGO")?); + build_command + .current_dir(current_dir) + .env_clear() + .env("PATH", env::var("PATH").unwrap_or_default()) + .env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags) + .env("RUSTC_BOOTSTRAP", "1") + .env("RUSTUP_HOME", env::var("RUSTUP_HOME").unwrap_or_default()) + .args([ + "build", + "--release", + "-Zbuild-std=core", + "-Zbuild-std-features=panic_immediate_abort", + ]) + .arg("--target") + .arg(target); + + if let Ok(toolchain) = env::var(OVERRIDE_RUSTUP_TOOLCHAIN_ENV_VAR) { + build_command.env("RUSTUP_TOOLCHAIN", &toolchain); } - fn invoke_build(target: &Path, current_dir: &Path) -> Result<()> { - let encoded_rustflags = ["-Dwarnings"].join("\x1f"); - - let mut build_command = Command::new(env::var("CARGO")?); - build_command - .current_dir(current_dir) - .env_clear() - .env("PATH", env::var("PATH").unwrap_or_default()) - .env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags) - .env("RUSTC_BOOTSTRAP", "1") - .env("RUSTUP_HOME", env::var("RUSTUP_HOME").unwrap_or_default()) - .args([ - "build", - "--release", - "-Zbuild-std=core", - "-Zbuild-std-features=panic_immediate_abort", - ]) - .arg("--target") - .arg(target); - - if let Ok(toolchain) = env::var(OVERRIDE_RUSTUP_TOOLCHAIN_ENV_VAR) { - build_command.env("RUSTUP_TOOLCHAIN", &toolchain); - } + let build_res = build_command.output().expect("failed to execute process"); - let build_res = build_command.output().expect("failed to execute process"); + if build_res.status.success() { + return Ok(()) + } - if build_res.status.success() { - return Ok(()) - } + let stderr = String::from_utf8_lossy(&build_res.stderr); + eprintln!("{}", stderr); - let stderr = String::from_utf8_lossy(&build_res.stderr); - eprintln!("{}", stderr); + bail!("Failed to build contracts"); +} - bail!("Failed to build contracts"); - } +/// Post-process the compiled code. +fn post_process(input_path: &Path, output_path: &Path) -> Result<()> { + let strip = std::env::var(OVERRIDE_STRIP_ENV_VAR).map_or(false, |value| value == "1"); + let optimize = std::env::var(OVERRIDE_OPTIMIZE_ENV_VAR).map_or(true, |value| value == "1"); + + let mut config = polkavm_linker::Config::default(); + config.set_strip(strip); + config.set_optimize(optimize); + 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) +} - /// Post-process the compiled code. - fn post_process(input_path: &Path, output_path: &Path) -> Result<()> { - let strip = std::env::var(OVERRIDE_STRIP_ENV_VAR).map_or(false, |value| value == "1"); - let optimize = std::env::var(OVERRIDE_OPTIMIZE_ENV_VAR).map_or(true, |value| value == "1"); - - let mut config = polkavm_linker::Config::default(); - config.set_strip(strip); - config.set_optimize(optimize); - 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/riscv32emac-unknown-none-polkavm/release") + .join(entry.name()), + &out_dir.join(entry.out_filename()), + )?; } - /// 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/riscv32emac-unknown-none-polkavm/release") - .join(entry.name()), - &out_dir.join(entry.out_filename()), - )?; - } + Ok(()) +} - Ok(()) +pub fn main() -> Result<()> { + let fixtures_dir: PathBuf = env::var("CARGO_MANIFEST_DIR")?.into(); + let contracts_dir = fixtures_dir.join("contracts"); + let out_dir: PathBuf = env::var("OUT_DIR")?.into(); + let target = fixtures_dir.join("riscv32emac-unknown-none-polkavm.json"); + + println!("cargo::rerun-if-env-changed={OVERRIDE_RUSTUP_TOOLCHAIN_ENV_VAR}"); + println!("cargo::rerun-if-env-changed={OVERRIDE_STRIP_ENV_VAR}"); + println!("cargo::rerun-if-env-changed={OVERRIDE_OPTIMIZE_ENV_VAR}"); + + // the fixtures have a dependency on the uapi crate + println!("cargo::rerun-if-changed={}", fixtures_dir.display()); + let uapi_dir = fixtures_dir.parent().expect("parent dir exits; qed").join("uapi"); + if uapi_dir.exists() { + println!("cargo::rerun-if-changed={}", uapi_dir.display()); } - pub fn run() -> Result<()> { - let fixtures_dir: PathBuf = env::var("CARGO_MANIFEST_DIR")?.into(); - let contracts_dir = fixtures_dir.join("contracts"); - let out_dir: PathBuf = env::var("OUT_DIR")?.into(); - let target = fixtures_dir.join("riscv32emac-unknown-none-polkavm.json"); - - println!("cargo::rerun-if-env-changed={OVERRIDE_RUSTUP_TOOLCHAIN_ENV_VAR}"); - println!("cargo::rerun-if-env-changed={OVERRIDE_STRIP_ENV_VAR}"); - println!("cargo::rerun-if-env-changed={OVERRIDE_OPTIMIZE_ENV_VAR}"); - - // the fixtures have a dependency on the uapi crate - println!("cargo::rerun-if-changed={}", fixtures_dir.display()); - let uapi_dir = fixtures_dir.parent().expect("parent dir exits; qed").join("uapi"); - if uapi_dir.exists() { - println!("cargo::rerun-if-changed={}", uapi_dir.display()); - } - - let entries = collect_entries(&contracts_dir); - if entries.is_empty() { - return Ok(()) - } + let entries = collect_entries(&contracts_dir); + if entries.is_empty() { + return Ok(()) + } - let tmp_dir = tempfile::tempdir()?; - let tmp_dir_path = tmp_dir.path(); + let tmp_dir = tempfile::tempdir()?; + let tmp_dir_path = tmp_dir.path(); - create_cargo_toml(&fixtures_dir, entries.iter(), tmp_dir.path())?; - invoke_build(&target, tmp_dir_path)?; + create_cargo_toml(&fixtures_dir, entries.iter(), tmp_dir.path())?; + invoke_build(&target, tmp_dir_path)?; - write_output(tmp_dir_path, &out_dir, entries)?; + write_output(tmp_dir_path, &out_dir, entries)?; - #[cfg(unix)] - if let Ok(symlink_dir) = env::var("CARGO_WORKSPACE_ROOT_DIR") { - let symlink_dir: PathBuf = symlink_dir.into(); - let symlink_dir: PathBuf = symlink_dir.join("target").join("pallet-revive-fixtures"); - if symlink_dir.is_symlink() { - fs::remove_file(&symlink_dir)? - } - std::os::unix::fs::symlink(&out_dir, &symlink_dir)?; + #[cfg(unix)] + if let Ok(symlink_dir) = env::var("CARGO_WORKSPACE_ROOT_DIR") { + let symlink_dir: PathBuf = symlink_dir.into(); + let symlink_dir: PathBuf = symlink_dir.join("target").join("pallet-revive-fixtures"); + if symlink_dir.is_symlink() { + fs::remove_file(&symlink_dir)? } - - Ok(()) + std::os::unix::fs::symlink(&out_dir, &symlink_dir)?; } -} - -#[cfg(not(feature = "riscv"))] -mod build { - use super::Result; - pub fn run() -> Result<()> { - Ok(()) - } + Ok(()) } diff --git a/substrate/frame/revive/fixtures/src/lib.rs b/substrate/frame/revive/fixtures/src/lib.rs index 5548dca66d07..cc84daec9b59 100644 --- a/substrate/frame/revive/fixtures/src/lib.rs +++ b/substrate/frame/revive/fixtures/src/lib.rs @@ -37,18 +37,11 @@ pub fn compile_module(fixture_name: &str) -> anyhow::Result<(Vec, sp_core::H 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"); diff --git a/substrate/frame/revive/mock-network/Cargo.toml b/substrate/frame/revive/mock-network/Cargo.toml index 12de634b0b4a..c5b18b3fa290 100644 --- a/substrate/frame/revive/mock-network/Cargo.toml +++ b/substrate/frame/revive/mock-network/Cargo.toml @@ -48,7 +48,6 @@ pallet-revive-fixtures = { workspace = true } [features] default = ["std"] -riscv = ["pallet-revive-fixtures/riscv"] std = [ "codec/std", "frame-support/std", diff --git a/substrate/frame/revive/mock-network/src/lib.rs b/substrate/frame/revive/mock-network/src/lib.rs index 848994653972..adfd0016b4dd 100644 --- a/substrate/frame/revive/mock-network/src/lib.rs +++ b/substrate/frame/revive/mock-network/src/lib.rs @@ -19,7 +19,7 @@ pub mod parachain; pub mod primitives; pub mod relay_chain; -#[cfg(all(test, feature = "riscv"))] +#[cfg(test)] mod tests; use crate::primitives::{AccountId, UNITS}; diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index ef7a7c1b28e4..e6d8c38c04f0 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -15,27 +15,27 @@ path = "src/main.rs" [[example]] name = "deploy" path = "examples/rust/deploy.rs" -required-features = ["example", "riscv"] +required-features = ["example"] [[example]] name = "transfer" path = "examples/rust/transfer.rs" -required-features = ["example", "riscv"] +required-features = ["example"] [[example]] name = "rpc-playground" path = "examples/rust/rpc-playground.rs" -required-features = ["example", "riscv"] +required-features = ["example"] [[example]] name = "extrinsic" path = "examples/rust/extrinsic.rs" -required-features = ["example", "riscv"] +required-features = ["example"] [[example]] name = "remark-extrinsic" path = "examples/rust/remark-extrinsic.rs" -required-features = ["example", "riscv"] +required-features = ["example"] [dependencies] clap = { workspace = true, features = ["derive"] } @@ -72,7 +72,6 @@ env_logger = { workspace = true } [features] example = ["hex", "hex-literal", "rlp", "secp256k1", "subxt-signer"] -riscv = ["pallet-revive/riscv"] [dev-dependencies] hex-literal = { workspace = true } diff --git a/substrate/frame/revive/rpc/Dockerfile b/substrate/frame/revive/rpc/Dockerfile index 981d5c19a158..fb867062a818 100644 --- a/substrate/frame/revive/rpc/Dockerfile +++ b/substrate/frame/revive/rpc/Dockerfile @@ -7,6 +7,7 @@ RUN apt-get update && \ WORKDIR /polkadot COPY . /polkadot +RUN rustup component add rust-src RUN cargo build --locked --profile production -p pallet-revive-eth-rpc --bin eth-rpc FROM docker.io/parity/base-bin:latest diff --git a/substrate/frame/revive/rpc/examples/README.md b/substrate/frame/revive/rpc/examples/README.md index 7c01dc0075ee..bf30426648ba 100644 --- a/substrate/frame/revive/rpc/examples/README.md +++ b/substrate/frame/revive/rpc/examples/README.md @@ -3,7 +3,7 @@ Build `pallet-revive-fixture`, as we need some compiled contracts to exercise the RPC server. ```bash -cargo build -p pallet-revive-fixtures --features riscv +cargo build -p pallet-revive-fixtures ``` ## Start the node @@ -96,4 +96,3 @@ See [this guide][import-account] for more info on how to import an account. [add-network]: https://support.metamask.io/networks-and-sidechains/managing-networks/how-to-add-a-custom-network-rpc/#adding-a-network-manually [import-account]: https://support.metamask.io/managing-my-wallet/accounts-and-addresses/how-to-import-an-account/ [reset-account]: https://support.metamask.io/managing-my-wallet/resetting-deleting-and-restoring/how-to-clear-your-account-activity-reset-account - diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index f745bea6a5f6..5d84e06e9e04 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -16,8 +16,6 @@ // limitations under the License. //! Test the eth-rpc cli with the kitchensink node. -// We require the `riscv` feature to get access to the compiled fixtures. -#![cfg(feature = "riscv")] use crate::{ cli::{self, CliCommand}, example::{send_transaction, wait_for_receipt}, diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index dd7e52327b66..3d1d7d2a224a 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -15,9 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Benchmarks for the contracts pallet +//! Benchmarks for the revive pallet -#![cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] +#![cfg(feature = "runtime-benchmarks")] mod call_builder; mod code; diff --git a/substrate/frame/revive/src/benchmarking_dummy.rs b/substrate/frame/revive/src/benchmarking_dummy.rs deleted file mode 100644 index 6bb467911272..000000000000 --- a/substrate/frame/revive/src/benchmarking_dummy.rs +++ /dev/null @@ -1,37 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -//! 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/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 6db3f43857ee..3acd67b32aab 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -396,7 +396,6 @@ pub trait EthExtra { } } -#[cfg(feature = "riscv")] #[cfg(test)] mod test { use super::*; diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 4b7198d570c1..8629a21c4fda 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -819,7 +819,7 @@ where .map(|_| (address, stack.first_frame.last_frame_output)) } - #[cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] + #[cfg(feature = "runtime-benchmarks")] pub fn bench_new_call( dest: H160, origin: Origin, @@ -1330,12 +1330,12 @@ where /// Certain APIs, e.g. `{set,get}_immutable_data` behave differently depending /// on the configured entry point. Thus, we allow setting the export manually. - #[cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] + #[cfg(feature = "runtime-benchmarks")] pub(crate) fn override_export(&mut self, export: ExportedFunction) { self.top_frame_mut().entry_point = export; } - #[cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] + #[cfg(feature = "runtime-benchmarks")] pub(crate) fn set_block_number(&mut self, block_number: BlockNumberFor) { self.block_number = block_number; } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index d50da45fc3a9..51e9a8fa3f90 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -23,7 +23,6 @@ extern crate alloc; mod address; mod benchmarking; -mod benchmarking_dummy; mod exec; mod gas; mod limits; diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs index db4db3e8eac3..b7156588d44c 100644 --- a/substrate/frame/revive/src/storage.rs +++ b/substrate/frame/revive/src/storage.rs @@ -505,7 +505,6 @@ impl DeletionQueueManager { } #[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() } diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 7ce2e3d9bf34..2d9cae16c441 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -15,8 +15,6 @@ // 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; @@ -64,6 +62,8 @@ use frame_system::{EventRecord, Phase}; use pallet_revive_fixtures::{bench::dummy_unique, compile_module}; use pallet_revive_uapi::ReturnErrorCode as RuntimeReturnCode; use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier}; +use pretty_assertions::{assert_eq, assert_ne}; +use sp_core::U256; use sp_io::hashing::blake2_256; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ @@ -624,1300 +624,1400 @@ impl Default for Origin { } } -/// 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}; - use sp_core::U256; +#[test] +fn calling_plain_account_is_balance_transfer() { + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000); + assert!(!>::contains_key(BOB_ADDR)); + assert_eq!(test_utils::get_balance(&BOB_FALLBACK), 0); + let result = builder::bare_call(BOB_ADDR).value(42).build_and_unwrap_result(); + assert_eq!(test_utils::get_balance(&BOB_FALLBACK), 42); + assert_eq!(result, Default::default()); + }); +} - #[test] - fn calling_plain_account_is_balance_transfer() { - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000); - assert!(!>::contains_key(BOB_ADDR)); - assert_eq!(test_utils::get_balance(&BOB_FALLBACK), 0); - let result = builder::bare_call(BOB_ADDR).value(42).build_and_unwrap_result(); - assert_eq!(test_utils::get_balance(&BOB_FALLBACK), 42); - assert_eq!(result, Default::default()); - }); - } +#[test] +fn instantiate_and_call_and_deposit_event() { + let (wasm, code_hash) = compile_module("event_and_return_on_deploy").unwrap(); - #[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; - 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 Contract { addr, account_id } = builder::bare_instantiate(Code::Existing(code_hash)) + .value(value) + .build_and_unwrap_contract(); + assert!(ContractInfoOf::::contains_key(&addr)); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: account_id.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: account_id.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: value, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::ContractEmitted { + contract: addr, + data: vec![1, 2, 3, 4], + topics: vec![H256::repeat_byte(42)], + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE_ADDR, + contract: addr + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE_ADDR, + to: addr, + amount: test_utils::contract_info_storage_deposit(&addr), + } + ), + topics: vec![], + }, + ] + ); + }); +} - // 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::(), - )); +#[test] +fn create1_address_from_extrinsic() { + let (wasm, code_hash) = compile_module("dummy").unwrap(); - // Drop previous events - initialize_block(2); + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - // Check at the end to get hash on error easily - let Contract { addr, account_id } = - builder::bare_instantiate(Code::Existing(code_hash)) - .value(value) - .build_and_unwrap_contract(); - assert!(ContractInfoOf::::contains_key(&addr)); + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + deposit_limit::(), + )); + + assert_eq!(System::account_nonce(&ALICE), 0); + System::inc_account_nonce(&ALICE); + for nonce in 1..3 { + let Contract { addr, .. } = builder::bare_instantiate(Code::Existing(code_hash)) + .salt(None) + .build_and_unwrap_contract(); + assert!(ContractInfoOf::::contains_key(&addr)); assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: account_id.clone() - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: account_id.clone(), - free_balance: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: value, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::ContractEmitted { - contract: addr, - data: vec![1, 2, 3, 4], - topics: vec![H256::repeat_byte(42)], - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, - ] + addr, + create1(&::AddressMapper::to_address(&ALICE), nonce - 1) ); - }); - } + } + assert_eq!(System::account_nonce(&ALICE), 3); - #[test] - fn create1_address_from_extrinsic() { - let (wasm, code_hash) = compile_module("dummy").unwrap(); + for nonce in 3..6 { + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm.clone())) + .salt(None) + .build_and_unwrap_contract(); + assert!(ContractInfoOf::::contains_key(&addr)); + assert_eq!( + addr, + create1(&::AddressMapper::to_address(&ALICE), nonce - 1) + ); + } + assert_eq!(System::account_nonce(&ALICE), 6); + }); +} - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); +#[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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_contract(); + + // Call contract with allowed storage value. + assert_ok!(builder::call(addr) + .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, + ); + }); +} - assert_ok!(Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - wasm.clone(), - deposit_limit::(), - )); +// 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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(100 * min_balance) + .build_and_unwrap_contract(); + + // 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, + ); + }); +} - assert_eq!(System::account_nonce(&ALICE), 0); - System::inc_account_nonce(&ALICE); +// 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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); + + 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) + .gas_limit(gas_limit) + .data(ExtensionInput { extension_id: 0, func_id: 2, extra: &u32::MAX.encode() }.into()) + .build() + .result; + assert_err!(result, >::OutOfGas); + }); +} - for nonce in 1..3 { - let Contract { addr, .. } = builder::bare_instantiate(Code::Existing(code_hash)) - .salt(None) - .build_and_unwrap_contract(); - assert!(ContractInfoOf::::contains_key(&addr)); - assert_eq!( - addr, - create1(&::AddressMapper::to_address(&ALICE), nonce - 1) - ); - } - assert_eq!(System::account_nonce(&ALICE), 3); +#[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 contract = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(contract.addr).data(0u32.encode()).build(); + assert_ok!(result.result); + let engine_consumed_noop = result.gas_consumed.ref_time(); + + let result = builder::bare_call(contract.addr).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(contract.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); + }); +} - for nonce in 3..6 { - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm.clone())) - .salt(None) - .build_and_unwrap_contract(); - assert!(ContractInfoOf::::contains_key(&addr)); - assert_eq!( - addr, - create1(&::AddressMapper::to_address(&ALICE), nonce - 1) - ); - } - assert_eq!(System::account_nonce(&ALICE), 6); - }); - } +/// 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(); - #[test] - fn deposit_event_max_value_limit() { - let (wasm, _code_hash) = compile_module("event_size").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(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); + // Instantiate the contract and store its trie id for later comparison. + let Contract { addr, .. } = + builder::bare_instantiate(Code::Existing(code_hash)).build_and_unwrap_contract(); + let trie_id = get_contract(&addr).trie_id; - // Call contract with allowed storage value. - assert_ok!(builder::call(addr) - .gas_limit(GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2)) // we are copying a huge buffer, - .data(limits::PAYLOAD_BYTES.encode()) - .build()); + // Try to instantiate it again without termination should yield an error. + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).build(), + >::DuplicateContract, + ); - // Call contract with too large a storage value. - assert_err_ignore_postinfo!( - builder::call(addr).data((limits::PAYLOAD_BYTES + 1).encode()).build(), - Error::::ValueTooLarge, - ); - }); - } + // Terminate the contract. + assert_ok!(builder::call(addr).build()); - // 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); + // Re-Instantiate after termination. + assert_ok!(builder::instantiate(code_hash).build()); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(100 * min_balance) - .build_and_unwrap_contract(); + // Trie ids shouldn't match or we might have a collision + assert_ne!(trie_id, get_contract(&addr).trie_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, - ); - }); - } +#[test] +fn storage_work() { + let (code, _code_hash) = compile_module("storage").unwrap(); - // 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); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .value(min_balance * 100) - .build_and_unwrap_contract(); + builder::bare_call(addr).build_and_unwrap_result(); + }); +} - let gas_limit = Weight::from_parts(u32::MAX as u64, GAS_LIMIT.proof_size()); +#[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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_contract(); + get_contract(&addr); + + // Call contract with allowed storage value. + assert_ok!(builder::call(addr) + .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, + ); + }); +} - // Use chain extension to charge more ref_time than it is available. - let result = builder::bare_call(addr) - .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 transient_storage_work() { + let (code, _code_hash) = compile_module("transient_storage").unwrap(); - #[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 contract = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - let result = builder::bare_call(contract.addr).data(0u32.encode()).build(); - assert_ok!(result.result); - let engine_consumed_noop = result.gas_consumed.ref_time(); - - let result = builder::bare_call(contract.addr).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(contract.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); - }); - } + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); - /// 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(); + builder::bare_call(addr).build_and_unwrap_result(); + }); +} - 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(); +#[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 Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + + // 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) + .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) + .data((4u32 * 1024u32, 200u32, &addr_callee).encode()) + .build(), + >::OutOfTransientStorage, + ); - // Instantiate the contract and store its trie id for later comparison. - let Contract { addr, .. } = - builder::bare_instantiate(Code::Existing(code_hash)).build_and_unwrap_contract(); - let trie_id = get_contract(&addr).trie_id; + // 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 + ); + }); +} - // Try to instantiate it again without termination should yield an error. - assert_err_ignore_postinfo!( - builder::instantiate(code_hash).build(), - >::DuplicateContract, - ); +#[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(); - // Terminate the contract. - assert_ok!(builder::call(addr).build()); + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let min_balance = Contracts::min_balance(); - // Re-Instantiate after termination. - assert_ok!(builder::instantiate(code_hash).build()); + // Create + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let Contract { addr: caller_addr, account_id: caller_account } = + builder::bare_instantiate(Code::Upload(caller_wasm)) + .value(100_000) + .build_and_unwrap_contract(); - // Trie ids shouldn't match or we might have a collision - assert_ne!(trie_id, get_contract(&addr).trie_id); - }); - } + let callee_addr = create2( + &caller_addr, + &callee_wasm, + &[0, 1, 34, 51, 68, 85, 102, 119], // hard coded in wasm + &[0u8; 32], + ); + let callee_account = ::AddressMapper::to_account_id(&callee_addr); - #[test] - fn storage_work() { - let (code, _code_hash) = compile_module("storage").unwrap(); + Contracts::upload_code(RuntimeOrigin::signed(ALICE), callee_wasm, deposit_limit::()) + .unwrap(); - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let min_balance = Contracts::min_balance(); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .value(min_balance * 100) - .build_and_unwrap_contract(); + // Drop previous events + initialize_block(2); - builder::bare_call(addr).build_and_unwrap_result(); - }); - } + // 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).data(callee_code_hash.as_ref().to_vec()).build()); - #[test] - fn storage_max_value_limit() { - let (wasm, _code_hash) = compile_module("storage_size").unwrap(); + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: callee_account.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: callee_account.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: callee_account.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: caller_account.clone(), + to: callee_account.clone(), + amount: 32768 // hardcoded in wasm + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: caller_addr, + contract: callee_addr, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: caller_account.clone(), + to: callee_account.clone(), + amount: 32768, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(caller_account.clone()), + contract: callee_addr, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: caller_addr, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE_ADDR, + to: callee_addr, + amount: test_utils::contract_info_storage_deposit(&callee_addr), + } + ), + topics: vec![], + }, + ] + ); + }); +} - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - get_contract(&addr); - - // Call contract with allowed storage value. - assert_ok!(builder::call(addr) - .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 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(); - #[test] - fn transient_storage_work() { - let (code, _code_hash) = compile_module("transient_storage").unwrap(); + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let min_balance = Contracts::min_balance(); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .value(min_balance * 100) + // Instantiate the 'caller' + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_wasm)) + .value(300_000) .build_and_unwrap_contract(); + // Only upload 'callee' code + assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), callee_wasm, 100_000,)); + + assert_ok!(builder::call(caller_addr) + .value(1337) + .data(callee_code_hash.as_ref().to_vec()) + .build()); + }); +} - builder::bare_call(addr).build_and_unwrap_result(); - }); - } +#[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); - #[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); + // Instantiate the BOB contract. + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(1_000) + .build_and_unwrap_contract(); - // Create both contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); - - // 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) - .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) - .data((4u32 * 1024u32, 200u32, &addr_callee).encode()) - .build(), - >::OutOfTransientStorage, - ); + // Check that the BOB contract has been instantiated. + get_contract(&addr); - // 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 - ); - }); - } + let account = ::AddressMapper::to_account_id(&addr); + let total_balance = ::Currency::total_balance(&account); - #[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(); + assert_eq!( + test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account), + test_utils::contract_info_storage_deposit(&addr) + ); - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let min_balance = Contracts::min_balance(); + // Some ot the total balance is held, so it can't be transferred. + assert_err!( + <::Currency as Mutate>::transfer( + &account, + &ALICE, + total_balance, + Preservation::Expendable, + ), + TokenError::FundsUnavailable, + ); - // Create - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr: caller_addr, account_id: caller_account } = - builder::bare_instantiate(Code::Upload(caller_wasm)) - .value(100_000) - .build_and_unwrap_contract(); + assert_eq!(::Currency::total_balance(&account), total_balance); + }); +} - let callee_addr = create2( - &caller_addr, - &callee_wasm, - &[0, 1, 34, 51, 68, 85, 102, 119], // hard coded in wasm - &[0u8; 32], - ); - let callee_account = ::AddressMapper::to_account_id(&callee_addr); +#[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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(value) + .build_and_unwrap_contract(); + let account = ::AddressMapper::to_account_id(&addr); + + // 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).build()); + + // Make sure the account wasn't remove by sending all free balance away. + assert_eq!( + ::Currency::total_balance(&account), + value + test_utils::contract_info_storage_deposit(&addr) + min_balance, + ); + }); +} - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - callee_wasm, - deposit_limit::(), - ) - .unwrap(); +#[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 contract = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); + let info_deposit = test_utils::contract_info_storage_deposit(&contract.addr); + + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&contract.addr).total_deposit(), info_deposit); + assert_eq!(get_contract(&contract.addr).extra_deposit(), 0); + assert_eq!( + ::Currency::total_balance(&contract.account_id), + info_deposit + min_balance + ); - // Drop previous events - initialize_block(2); + // Create 100 bytes of storage with a price of per byte and a single storage item of + // price 2 + assert_ok!(builder::call(contract.addr).data(100u32.to_le_bytes().to_vec()).build()); + assert_eq!(get_contract(&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(contract.addr).data(0u32.to_le_bytes().to_vec()).build()); + + // Make sure the account wasn't removed by the refund + assert_eq!( + ::Currency::total_balance(&contract.account_id), + get_contract(&contract.addr).total_deposit() + min_balance, + ); + assert_eq!(get_contract(&contract.addr).extra_deposit(), 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).data(callee_code_hash.as_ref().to_vec()).build()); +#[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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_contract(); + + // 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).data(vec![0]).build(), + Error::::ContractTrapped, + ); - assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: callee_account.clone() - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: callee_account.clone(), - free_balance: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: callee_account.clone(), - amount: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: caller_account.clone(), - to: callee_account.clone(), - amount: 32768 // hardcoded in wasm - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: caller_addr, - contract: callee_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: caller_account.clone(), - to: callee_account.clone(), - amount: 32768, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(caller_account.clone()), - contract: callee_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: caller_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: callee_addr, - amount: test_utils::contract_info_storage_deposit(&callee_addr), - } - ), - topics: vec![], - }, - ] - ); - }); - } + // Check that BOB is still there. + get_contract(&addr); + }); +} - #[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(); +#[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_FALLBACK, 1_000_000); + let min_balance = Contracts::min_balance(); - ExtBuilder::default().existential_deposit(500).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + // Instantiate the BOB contract. + let contract = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_contract(); - // Instantiate the 'caller' - let Contract { addr: caller_addr, .. } = - builder::bare_instantiate(Code::Upload(caller_wasm)) - .value(300_000) - .build_and_unwrap_contract(); - // Only upload 'callee' code - assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), callee_wasm, 100_000,)); + // Check that the BOB contract has been instantiated. + let _ = get_contract(&contract.addr); - assert_ok!(builder::call(caller_addr) - .value(1337) - .data(callee_code_hash.as_ref().to_vec()) - .build()); - }); - } + let info_deposit = test_utils::contract_info_storage_deposit(&contract.addr); - #[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); + // Drop all previous events + initialize_block(2); - // Instantiate the BOB contract. - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(1_000) - .build_and_unwrap_contract(); + // Call BOB without input data which triggers termination. + assert_matches!(builder::call(contract.addr).build(), Ok(_)); - // Check that the BOB contract has been instantiated. - get_contract(&addr); + // Check that code is still there but refcount dropped to zero. + assert_refcount!(&code_hash, 0); - let account = ::AddressMapper::to_account_id(&addr); - let total_balance = ::Currency::total_balance(&account); + // Check that account is gone + assert!(get_contract_checked(&contract.addr).is_none()); + assert_eq!(::Currency::total_balance(&contract.account_id), 0); - assert_eq!( - test_utils::get_balance_on_hold( - &HoldReason::StorageDepositReserve.into(), - &account - ), - test_utils::contract_info_storage_deposit(&addr) - ); + // Check that the beneficiary (django) got remaining balance. + assert_eq!( + ::Currency::free_balance(DJANGO_FALLBACK), + 1_000_000 + 100_000 + min_balance + ); - // Some ot the total balance is held, so it can't be transferred. - assert_err!( - <::Currency as Mutate>::transfer( - &account, - &ALICE, - total_balance, - Preservation::Expendable, - ), - TokenError::FundsUnavailable, - ); + // 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) + ); - assert_eq!(::Currency::total_balance(&account), total_balance); - }); - } + pretty_assertions::assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Terminated { + contract: contract.addr, + beneficiary: DJANGO_ADDR, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: contract.addr, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndReleased { + from: contract.addr, + to: ALICE_ADDR, + amount: info_deposit, + } + ), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::KilledAccount { + account: contract.account_id.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: contract.account_id.clone(), + to: DJANGO_FALLBACK, + amount: 100_000 + min_balance, + }), + topics: vec![], + }, + ], + ); + }); +} - #[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(); +// 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.clone(), + deposit_limit::(), + ) + .unwrap(); + + // This deploys the BOB contract, which in turn deploys the CHARLIE contract during + // construction. + let Contract { addr: addr_bob, .. } = builder::bare_instantiate(Code::Upload(caller_wasm)) + .value(200_000) + .data(callee_code_hash.as_ref().to_vec()) + .build_and_unwrap_contract(); + + // Check that the CHARLIE contract has been instantiated. + let salt = [47; 32]; // hard coded in fixture. + let addr_charlie = create2(&addr_bob, &callee_wasm, &[], &salt); + 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()); + }); +} - // Instantiate the BOB contract. - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(value) - .build_and_unwrap_contract(); - let account = ::AddressMapper::to_account_id(&addr); +#[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); - // Check that the BOB contract has been instantiated. - get_contract(&addr); + // 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, + ); + }); +} - // 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).build()); +#[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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_contract(); + // 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).data(params).build_and_unwrap_result(); + assert!(!result.did_revert()); + let expected = hash_fn(input.as_ref()); + assert_eq!(&result.data[..*expected_size], &*expected); + } + }) +} - // Make sure the account wasn't remove by sending all free balance away. - assert_eq!( - ::Currency::total_balance(&account), - value + test_utils::contract_info_storage_deposit(&addr) + min_balance, - ); - }); - } +#[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 contract = builder::bare_instantiate(Code::Upload(wasm)) + .value(min_balance * 100) + .build_and_unwrap_contract(); + + // Contract has only the minimal balance so any transfer will fail. + ::Currency::set_balance(&contract.account_id, min_balance); + let result = builder::bare_call(contract.addr).build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); + }); +} - #[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(); +#[test] +fn call_return_code() { + use test_utils::u256_bytes; + + 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 bob = builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); + + // Contract calls into Django which is no valid contract + // This will be a balance transfer into a new account + // with more than the contract has which will make the transfer fail + let result = builder::bare_call(bob.addr) + .data( + AsRef::<[u8]>::as_ref(&DJANGO_ADDR) + .iter() + .chain(&u256_bytes(min_balance * 200)) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); - // Instantiate the BOB contract. - let contract = - builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - let info_deposit = test_utils::contract_info_storage_deposit(&contract.addr); + // Sending less than the minimum balance will also make the transfer fail + let result = builder::bare_call(bob.addr) + .data( + AsRef::<[u8]>::as_ref(&DJANGO_ADDR) + .iter() + .chain(&u256_bytes(42)) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); + + // Sending at least the minimum balance should result in success but + // no code called. + assert_eq!(test_utils::get_balance(&DJANGO_FALLBACK), 0); + let result = builder::bare_call(bob.addr) + .data( + AsRef::<[u8]>::as_ref(&DJANGO_ADDR) + .iter() + .chain(&u256_bytes(55)) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::Success); + assert_eq!(test_utils::get_balance(&DJANGO_FALLBACK), 55); + + let django = builder::bare_instantiate(Code::Upload(callee_code)) + .origin(RuntimeOrigin::signed(CHARLIE)) + .value(min_balance * 100) + .build_and_unwrap_contract(); + + // Sending more than the contract has will make the transfer fail. + let result = builder::bare_call(bob.addr) + .data( + AsRef::<[u8]>::as_ref(&django.addr) + .iter() + .chain(&u256_bytes(min_balance * 300)) + .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(&bob.account_id, min_balance + 1000); + let result = builder::bare_call(bob.addr) + .data( + AsRef::<[u8]>::as_ref(&django.addr) + .iter() + .chain(&u256_bytes(5)) + .chain(&1u32.to_le_bytes()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeReverted); - // Check that the contract has been instantiated and has the minimum balance - assert_eq!(get_contract(&contract.addr).total_deposit(), info_deposit); - assert_eq!(get_contract(&contract.addr).extra_deposit(), 0); - assert_eq!( - ::Currency::total_balance(&contract.account_id), - info_deposit + min_balance - ); + // Contract has enough balance but callee traps because "2" is passed. + let result = builder::bare_call(bob.addr) + .data( + AsRef::<[u8]>::as_ref(&django.addr) + .iter() + .chain(&u256_bytes(5)) + .chain(&2u32.to_le_bytes()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); + }); +} - // Create 100 bytes of storage with a price of per byte and a single storage item of - // price 2 - assert_ok!(builder::call(contract.addr).data(100u32.to_le_bytes().to_vec()).build()); - assert_eq!(get_contract(&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(contract.addr).data(0u32.to_le_bytes().to_vec()).build()); - - // Make sure the account wasn't removed by the refund - assert_eq!( - ::Currency::total_balance(&contract.account_id), - get_contract(&contract.addr).total_deposit() + min_balance, - ); - assert_eq!(get_contract(&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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(100_000) - .build_and_unwrap_contract(); - - // 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).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_FALLBACK, 1_000_000); - let min_balance = Contracts::min_balance(); - - // Instantiate the BOB contract. - let contract = builder::bare_instantiate(Code::Upload(wasm)) - .value(100_000) - .build_and_unwrap_contract(); - - // Check that the BOB contract has been instantiated. - let _ = get_contract(&contract.addr); - - let info_deposit = test_utils::contract_info_storage_deposit(&contract.addr); - - // Drop all previous events - initialize_block(2); - - // Call BOB without input data which triggers termination. - assert_matches!(builder::call(contract.addr).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(&contract.addr).is_none()); - assert_eq!(::Currency::total_balance(&contract.account_id), 0); - - // Check that the beneficiary (django) got remaining balance. - assert_eq!( - ::Currency::free_balance(DJANGO_FALLBACK), - 1_000_000 + 100_000 + min_balance - ); +#[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 contract = builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); + + // Contract has only the minimal balance so any transfer will fail. + ::Currency::set_balance(&contract.account_id, min_balance); + let result = builder::bare_call(contract.addr) + .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(&contract.account_id, min_balance + 10_000); + let result = builder::bare_call(contract.addr).data(vec![0; 33]).build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CodeNotFound); + + // Contract has enough balance but callee reverts because "1" is passed. + let result = builder::bare_call(contract.addr) + .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(contract.addr) + .data(callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); + }); +} - // 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) - ); +#[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 contract = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); + TestExtension::disable(); + assert_err_ignore_postinfo!( + builder::call(contract.addr).data(vec![7u8; 8]).build(), + Error::::NoChainExtension, + ); + }); +} - pretty_assertions::assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Terminated { - contract: contract.addr, - beneficiary: DJANGO_ADDR, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract.addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndReleased { - from: contract.addr, - to: ALICE_ADDR, - amount: info_deposit, - } - ), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::System(frame_system::Event::KilledAccount { - account: contract.account_id.clone() - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: contract.account_id.clone(), - to: DJANGO_FALLBACK, - amount: 100_000 + min_balance, - }), - topics: vec![], - }, - ], - ); - }); - } +#[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 contract = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); + + // 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(contract.addr).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(contract.addr) + .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(contract.addr) + .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(contract.addr) + .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(contract.addr) + .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(contract.addr) + .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(contract.addr) + .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(contract.addr) + .data(ExtensionInput { extension_id: 2, func_id: 0, extra: &[] }.into()) + .build(), + Error::::NoChainExtension, + ); + }); +} - // 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(); +#[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 contract = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); + + // 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(), + ); - 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.clone(), - deposit_limit::(), - ) - .unwrap(); + assert_ok!(builder::bare_call(contract.addr).data(input.clone()).build().result); + }) +} - // This deploys the BOB contract, which in turn deploys the CHARLIE contract during - // construction. - let Contract { addr: addr_bob, .. } = - builder::bare_instantiate(Code::Upload(caller_wasm)) - .value(200_000) - .data(callee_code_hash.as_ref().to_vec()) - .build_and_unwrap_contract(); +#[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); - // Check that the CHARLIE contract has been instantiated. - let salt = [47; 32]; // hard coded in fixture. - let addr_charlie = create2(&addr_bob, &callee_wasm, &[], &salt); - get_contract(&addr_charlie); + let contract = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); - // Call BOB, which calls CHARLIE, forcing CHARLIE to self-destruct. - assert_ok!(builder::call(addr_bob).data(addr_charlie.encode()).build()); + let info = get_contract(&contract.addr); + let trie = &info.child_trie_info(); - // Check that CHARLIE has moved on to the great beyond (ie. died). - assert!(get_contract_checked(&addr_charlie).is_none()); - }); - } + // Put value into the contracts child trie + child::put(trie, &[99], &42); - #[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); + // Terminate the contract + assert_ok!(builder::call(contract.addr).build()); - // 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, - ); - }); - } + // Contract info should be gone + assert!(!>::contains_key(&contract.addr)); - #[test] - fn crypto_hashes() { - let (wasm, _code_hash) = compile_module("crypto_hashes").unwrap(); + // But value should be still there as the lazy removal did not run, yet. + assert_matches!(child::get(trie, &[99]), Some(42)); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + // Run the lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); - // Instantiate the CRYPTO_HASHES contract. - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(100_000) - .build_and_unwrap_contract(); - // 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).data(params).build_and_unwrap_result(); - assert!(!result.did_revert()); - let expected = hash_fn(input.as_ref()); - assert_eq!(&result.data[..*expected_size], &*expected); - } - }) - } + // Value should be gone now + assert_matches!(child::get::(trie, &[99]), None); + }); +} - #[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); +#[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![]; - let contract = builder::bare_instantiate(Code::Upload(wasm)) + for i in 0..3u8 { + let contract = builder::bare_instantiate(Code::Upload(code.clone())) .value(min_balance * 100) + .salt(Some([i; 32])) .build_and_unwrap_contract(); - // Contract has only the minimal balance so any transfer will fail. - ::Currency::set_balance(&contract.account_id, min_balance); - let result = builder::bare_call(contract.addr).build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::TransferFailed); - }); - } + let info = get_contract(&contract.addr); + let trie = &info.child_trie_info(); - #[test] - fn call_return_code() { - use test_utils::u256_bytes; + // Put value into the contracts child trie + child::put(trie, &[99], &42); - 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); + // 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(contract.addr).build()); - let bob = builder::bare_instantiate(Code::Upload(caller_code)) - .value(min_balance * 100) - .build_and_unwrap_contract(); + assert!(!>::contains_key(&contract.addr)); + assert_matches!(child::get(trie, &[99]), Some(42)); - // Contract calls into Django which is no valid contract - // This will be a balance transfer into a new account - // with more than the contract has which will make the transfer fail - let result = builder::bare_call(bob.addr) - .data( - AsRef::<[u8]>::as_ref(&DJANGO_ADDR) - .iter() - .chain(&u256_bytes(min_balance * 200)) - .cloned() - .collect(), - ) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::TransferFailed); + tries.push(trie.clone()) + } - // Sending less than the minimum balance will also make the transfer fail - let result = builder::bare_call(bob.addr) - .data( - AsRef::<[u8]>::as_ref(&DJANGO_ADDR) - .iter() - .chain(&u256_bytes(42)) - .cloned() - .collect(), - ) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::TransferFailed); + // Run single lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); - // Sending at least the minimum balance should result in success but - // no code called. - assert_eq!(test_utils::get_balance(&DJANGO_FALLBACK), 0); - let result = builder::bare_call(bob.addr) - .data( - AsRef::<[u8]>::as_ref(&DJANGO_ADDR) - .iter() - .chain(&u256_bytes(55)) - .cloned() - .collect(), - ) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::Success); - assert_eq!(test_utils::get_balance(&DJANGO_FALLBACK), 55); + // The single lazy removal should have removed all queued tries + for trie in tries.iter() { + assert_matches!(child::get::(trie, &[99]), None); + } + }); +} - let django = builder::bare_instantiate(Code::Upload(callee_code)) - .origin(RuntimeOrigin::signed(CHARLIE)) - .value(min_balance * 100) - .build_and_unwrap_contract(); +#[test] +fn lazy_removal_partial_remove_works() { + let (code, _hash) = compile_module("self_destruct").unwrap(); - // Sending more than the contract has will make the transfer fail. - let result = builder::bare_call(bob.addr) - .data( - AsRef::<[u8]>::as_ref(&django.addr) - .iter() - .chain(&u256_bytes(min_balance * 300)) - .chain(&0u32.to_le_bytes()) - .cloned() - .collect(), - ) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::TransferFailed); + // 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(); - // Contract has enough balance but callee reverts because "1" is passed. - ::Currency::set_balance(&bob.account_id, min_balance + 1000); - let result = builder::bare_call(bob.addr) - .data( - AsRef::<[u8]>::as_ref(&django.addr) - .iter() - .chain(&u256_bytes(5)) - .chain(&1u32.to_le_bytes()) - .cloned() - .collect(), - ) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::CalleeReverted); + let mut ext = ExtBuilder::default().existential_deposit(50).build(); - // Contract has enough balance but callee traps because "2" is passed. - let result = builder::bare_call(bob.addr) - .data( - AsRef::<[u8]>::as_ref(&django.addr) - .iter() - .chain(&u256_bytes(5)) - .chain(&2u32.to_le_bytes()) - .cloned() - .collect(), - ) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); - }); - } + let trie = ext.execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - #[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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); - let contract = builder::bare_instantiate(Code::Upload(caller_code)) - .value(min_balance * 100) - .build_and_unwrap_contract(); + let info = get_contract(&addr); - // Contract has only the minimal balance so any transfer will fail. - ::Currency::set_balance(&contract.account_id, min_balance); - let result = builder::bare_call(contract.addr) - .data(callee_hash.clone()) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::TransferFailed); + // 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()); - // Contract has enough balance but the passed code hash is invalid - ::Currency::set_balance(&contract.account_id, min_balance + 10_000); - let result = - builder::bare_call(contract.addr).data(vec![0; 33]).build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::CodeNotFound); + // Terminate the contract + assert_ok!(builder::call(addr).build()); - // Contract has enough balance but callee reverts because "1" is passed. - let result = builder::bare_call(contract.addr) - .data(callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect()) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::CalleeReverted); + // Contract info should be gone + assert!(!>::contains_key(&addr)); - // Contract has enough balance but callee traps because "2" is passed. - let result = builder::bare_call(contract.addr) - .data(callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect()) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); - }); - } + let trie = info.child_trie_info(); - #[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 contract = builder::bare_instantiate(Code::Upload(code)) - .value(min_balance * 100) - .build_and_unwrap_contract(); - TestExtension::disable(); - assert_err_ignore_postinfo!( - builder::call(contract.addr).data(vec![7u8; 8]).build(), - Error::::NoChainExtension, - ); - }); - } + // 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)); + } - #[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 contract = builder::bare_instantiate(Code::Upload(code)) - .value(min_balance * 100) - .build_and_unwrap_contract(); + trie.clone() + }); - // 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(contract.addr).data(input.clone()).build(); - assert_eq!(TestExtension::last_seen_buffer(), input); - assert_eq!(result.result.unwrap().data, input); + // 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(); - // 1 = treat inputs as integer primitives and store the supplied integers - builder::bare_call(contract.addr) - .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(contract.addr) - .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(contract.addr) - .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(contract.addr) - .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(contract.addr) - .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(contract.addr) - .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(contract.addr) - .data(ExtensionInput { extension_id: 2, func_id: 0, extra: &[] }.into()) - .build(), - Error::::NoChainExtension, - ); - }); - } + ext.execute_with(|| { + // Run the lazy removal + ContractInfo::::process_deletion_queue_batch(&mut meter); - #[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 contract = builder::bare_instantiate(Code::Upload(code)) - .value(min_balance * 100) - .build_and_unwrap_contract(); + // Weight should be exhausted because we could not even delete all keys + assert!(!meter.can_consume(weight_per_key)); - // 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(), - ); + let mut num_deleted = 0u32; + let mut num_remaining = 0u32; - assert_ok!(builder::bare_call(contract.addr).data(input.clone()).build().result); - }) - } + 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"), + } + } - #[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); + // 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); + }); +} - let contract = builder::bare_instantiate(Code::Upload(code)) - .value(min_balance * 100) - .build_and_unwrap_contract(); +#[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 info = get_contract(&contract.addr); - let trie = &info.child_trie_info(); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); - // Put value into the contracts child trie - child::put(trie, &[99], &42); + let info = get_contract(&addr); + let trie = &info.child_trie_info(); - // Terminate the contract - assert_ok!(builder::call(contract.addr).build()); + // Put value into the contracts child trie + child::put(trie, &[99], &42); - // Contract info should be gone - assert!(!>::contains_key(&contract.addr)); + // Terminate the contract + assert_ok!(builder::call(addr).build()); - // But value should be still there as the lazy removal did not run, yet. - assert_matches!(child::get(trie, &[99]), Some(42)); + // Contract info should be gone + assert!(!>::contains_key(&addr)); - // Run the lazy removal - Contracts::on_idle(System::block_number(), Weight::MAX); + // But value should be still there as the lazy removal did not run, yet. + assert_matches!(child::get(trie, &[99]), Some(42)); - // Value should be gone now - assert_matches!(child::get::(trie, &[99]), None); - }); - } + // 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(); - #[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 contract = builder::bare_instantiate(Code::Upload(code.clone())) - .value(min_balance * 100) - .salt(Some([i; 32])) - .build_and_unwrap_contract(); + // Run the lazy removal + Contracts::on_idle(System::block_number(), low_remaining_weight); - let info = get_contract(&contract.addr); - let trie = &info.child_trie_info(); + // Value should still be there, since remaining weight was too low for removal + assert_matches!(child::get::(trie, &[99]), Some(42)); - // Put value into the contracts child trie - child::put(trie, &[99], &42); + // Run the lazy removal while deletion_queue is not full + Contracts::on_initialize(System::block_number()); - // 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(contract.addr).build()); + // Value should still be there, since deletion_queue was not full + assert_matches!(child::get::(trie, &[99]), Some(42)); - assert!(!>::contains_key(&contract.addr)); - 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); - tries.push(trie.clone()) - } + // Value should be gone + assert_matches!(child::get::(trie, &[99]), None); + }); +} - // Run single lazy removal - Contracts::on_idle(System::block_number(), Weight::MAX); +#[test] +fn lazy_removal_does_not_use_all_weight() { + let (code, _hash) = compile_module("self_destruct").unwrap(); - // The single lazy removal should have removed all queued tries - for trie in tries.iter() { - assert_matches!(child::get::(trie, &[99]), None); - } - }); - } + let mut meter = WeightMeter::with_limit(Weight::from_parts(5_000_000_000, 100 * 1024)); + let mut ext = ExtBuilder::default().existential_deposit(50).build(); - #[test] - fn lazy_removal_partial_remove_works() { - let (code, _hash) = compile_module("self_destruct").unwrap(); + let (trie, vals, weight_per_key) = ext.execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - // 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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); + + let info = get_contract(&addr); let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(&meter); - let vals: Vec<_> = (0..max_keys + extra_keys) + 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(); - 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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .value(min_balance * 100) - .build_and_unwrap_contract(); - - 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()); + // 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).build()); + // Terminate the contract + assert_ok!(builder::call(addr).build()); - // Contract info should be gone - assert!(!>::contains_key(&addr)); + // Contract info should be gone + assert!(!>::contains_key(&addr)); - let trie = info.child_trie_info(); + 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)); - } + // 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() - }); + (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(); + // 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); + 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); - // Weight should be exhausted because we could not even delete all keys - assert!(!meter.can_consume(weight_per_key)); + // All the keys are removed + for val in vals { + assert_eq!(child::get::(&trie, &blake2_256(&val.0)), None); + } + }); +} - let mut num_deleted = 0u32; - let mut num_remaining = 0u32; +#[test] +fn deletion_queue_ring_buffer_overflow() { + let (code, _hash) = compile_module("self_destruct").unwrap(); + let mut ext = ExtBuilder::default().existential_deposit(50).build(); - 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"), - } - } + // setup the deletion queue with custom counters + ext.execute_with(|| { + let queue = DeletionQueueManager::from_test_values(u32::MAX - 1, u32::MAX - 1); + >::set(queue); + }); - // 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); - }); - } + // commit the changes to the storage + ext.commit_all().unwrap(); - #[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); + ext.execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let mut tries: Vec = vec![]; - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + // add 3 contracts to the deletion queue + for i in 0..3u8 { + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code.clone())) .value(min_balance * 100) + .salt(Some([i; 32])) .build_and_unwrap_contract(); let info = get_contract(&addr); @@ -1926,669 +2026,816 @@ mod run_tests { // Put value into the contracts child trie child::put(trie, &[99], &42); - // Terminate the contract + // 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).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); + tries.push(trie.clone()) + } - // Value should still be there, since remaining weight was too low for removal - assert_matches!(child::get::(trie, &[99]), Some(42)); + // Run single lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); - // Run the lazy removal while deletion_queue is not full - Contracts::on_initialize(System::block_number()); + // The single lazy removal should have removed all queued tries + for trie in tries.iter() { + assert_matches!(child::get::(trie, &[99]), None); + } - // Value should still be there, since deletion_queue was not full - assert_matches!(child::get::(trie, &[99]), Some(42)); + // 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 Contract { addr: addr0, .. } = builder::bare_instantiate(Code::Upload(wasm.clone())) + .value(min_balance * 100) + .salt(Some([0; 32])) + .build_and_unwrap_contract(); + let Contract { addr: addr1, .. } = builder::bare_instantiate(Code::Upload(wasm.clone())) + .value(min_balance * 100) + .salt(Some([1; 32])) + .build_and_unwrap_contract(); + assert_refcount!(code_hash, 2); + + // Sharing should also work with the usual instantiate call + let Contract { addr: addr2, .. } = builder::bare_instantiate(Code::Existing(code_hash)) + .value(min_balance * 100) + .salt(Some([2; 32])) + .build_and_unwrap_contract(); + 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)); + }); +} - // Run on_idle with max remaining weight, this should remove the value - Contracts::on_idle(System::block_number(), Weight::MAX); +#[test] +fn debug_message_works() { + let (wasm, _code_hash) = compile_module("debug_message_works").unwrap(); - // Value should be gone - assert_matches!(child::get::(trie, &[99]), None); - }); - } + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_contract(); + let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); - #[test] - fn lazy_removal_does_not_use_all_weight() { - let (code, _hash) = compile_module("self_destruct").unwrap(); + assert_matches!(result.result, Ok(_)); + assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!"); + }); +} - let mut meter = WeightMeter::with_limit(Weight::from_parts(5_000_000_000, 100 * 1024)); - let mut ext = ExtBuilder::default().existential_deposit(50).build(); +#[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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_contract(); + // the dispatchables always run without debugging + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr, + 0, + GAS_LIMIT, + deposit_limit::(), + vec![] + )); + }); +} - let (trie, vals, weight_per_key) = ext.execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); +#[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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_contract(); + let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); + assert_ok!(result.result); + assert!(result.debug_message.is_empty()); + }); +} - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) +#[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 Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(caller_code)) .value(min_balance * 100) .build_and_unwrap_contract(); - let info = get_contract(&addr); - let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(&meter); - assert!(max_keys > 0); + let Contract { addr: addr_dummy, .. } = builder::bare_instantiate(Code::Upload(dummy_code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); - // 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(); + let Contract { addr: addr_call_runtime, .. } = + builder::bare_instantiate(Code::Upload(call_runtime_code)) + .value(min_balance * 100) + .build_and_unwrap_contract(); - // 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()); + // 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), + ]; - // Terminate the contract - assert_ok!(builder::call(addr).build()); + // 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(); - // Contract info should be gone - assert!(!>::contains_key(&addr)); + // 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), + ]; - let trie = info.child_trie_info(); + 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).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() + }; - // 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)); + // Make the same call using the estimated gas. Should succeed. + let result = builder::bare_call(addr_caller) + .gas_limit(result_orig.gas_required) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .data(input.clone()) + .build(); + assert_ok!(&result.result); + + // Check that it fails with too little ref_time + let result = builder::bare_call(addr_caller) + .gas_limit(result_orig.gas_required.sub_ref_time(1)) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .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) + .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); } + } + }); +} - (trie, vals, weight_per_key) - }); +#[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); - // 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(); + let Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .salt(Some([0; 32])) + .build_and_unwrap_contract(); - 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); - } + // 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).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 deletion_queue_ring_buffer_overflow() { - let (code, _hash) = compile_module("self_destruct").unwrap(); - let mut ext = ExtBuilder::default().existential_deposit(50).build(); +#[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 Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .salt(Some([0; 32])) + .build_and_unwrap_contract(); - // setup the deletion queue with custom counters - ext.execute_with(|| { - let queue = DeletionQueueManager::from_test_values(u32::MAX - 1, u32::MAX - 1); - >::set(queue); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(callee_code)) + .value(min_balance * 100) + .salt(Some([1; 32])) + .build_and_unwrap_contract(); + + // 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![], }); - // commit the changes to the storage - ext.commit_all().unwrap(); + // Call runtime to re-enter back to contracts engine by + // calling dummy contract + let result = builder::bare_call(addr_caller).data(call.encode()).build_and_unwrap_result(); + // Call to runtime should fail because of the re-entrancy guard + assert_return_code!(result, RuntimeReturnCode::CallRuntimeFailed); + }); +} - 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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code.clone())) - .value(min_balance * 100) - .salt(Some([i; 32])) - .build_and_unwrap_contract(); +#[test] +fn ecdsa_recover() { + let (wasm, _code_hash) = compile_module("ecdsa_recover").unwrap(); - let info = get_contract(&addr); - let trie = &info.child_trie_info(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - // Put value into the contracts child trie - child::put(trie, &[99], &42); + // Instantiate the ecdsa_recover contract. + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_contract(); - // 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).build()); + #[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).data(params).build_and_unwrap_result(); + assert!(!result.did_revert()); + assert_eq!(result.data, EXPECTED_COMPRESSED_PUBLIC_KEY); + }) +} - assert!(!>::contains_key(&addr)); - assert_matches!(child::get(trie, &[99]), Some(42)); +#[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()); + }); +} - tries.push(trie.clone()) - } +#[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); - // Run single lazy removal - Contracts::on_idle(System::block_number(), Weight::MAX); + let result = builder::bare_instantiate(Code::Upload(wasm)).value(min_balance * 100).build(); - // The single lazy removal should have removed all queued tries - for trie in tries.iter() { - assert_matches!(child::get::(trie, &[99]), None); - } + let events = result.events; + assert!(!System::events().is_empty()); + assert!(events.is_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(); +#[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); - // Create two contracts with the same code and check that they do in fact share it. - let Contract { addr: addr0, .. } = - builder::bare_instantiate(Code::Upload(wasm.clone())) - .value(min_balance * 100) - .salt(Some([0; 32])) - .build_and_unwrap_contract(); - let Contract { addr: addr1, .. } = - builder::bare_instantiate(Code::Upload(wasm.clone())) - .value(min_balance * 100) - .salt(Some([1; 32])) - .build_and_unwrap_contract(); - assert_refcount!(code_hash, 2); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(min_balance * 100) + .build_and_unwrap_contract(); - // Sharing should also work with the usual instantiate call - let Contract { addr: addr2, .. } = builder::bare_instantiate(Code::Existing(code_hash)) - .value(min_balance * 100) - .salt(Some([2; 32])) - .build_and_unwrap_contract(); - assert_refcount!(code_hash, 3); + let result = builder::bare_call(addr).collect_events(CollectEvents::UnsafeCollect).build(); - // Terminating one contract should decrement the refcount - assert_ok!(builder::call(addr0).build()); - assert_refcount!(code_hash, 2); + let events = result.events.unwrap(); + assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); + assert!(!events.is_empty()); + assert_eq!(events, System::events()); + }); +} - // remove another one - assert_ok!(builder::call(addr1).build()); - assert_refcount!(code_hash, 1); +#[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); - // Pristine code should still be there - PristineCode::::get(code_hash).unwrap(); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(min_balance * 100) + .build_and_unwrap_contract(); - // remove the last contract - assert_ok!(builder::call(addr2).build()); - assert_refcount!(code_hash, 0); + let result = builder::bare_call(addr).build(); - // refcount is `0` but code should still exists because it needs to be removed manually - assert!(crate::PristineCode::::contains_key(&code_hash)); - }); - } + let events = result.events; + assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); + assert!(!System::events().is_empty()); + assert!(events.is_none()); + }); +} - #[test] - fn debug_message_works() { - let (wasm, _code_hash) = compile_module("debug_message_works").unwrap(); +#[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); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - 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 Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - // the dispatchables always run without debugging - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr, - 0, - GAS_LIMIT, - deposit_limit::(), - vec![] - )); - }); - } + // Instantiate the sr25519_verify contract. + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_contract(); - #[test] - fn debug_message_invalid_utf8() { - let (wasm, _code_hash) = compile_module("debug_message_invalid_utf8").unwrap(); + 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, + ]; - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); - assert_ok!(result.result); - assert!(result.debug_message.is_empty()); - }); - } + // 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, + ]; - #[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 Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(caller_code)) - .value(min_balance * 100) - .build_and_unwrap_contract(); + let mut params = vec![]; + params.extend_from_slice(&signature); + params.extend_from_slice(&public_key); + params.extend_from_slice(message); - let Contract { addr: addr_dummy, .. } = - builder::bare_instantiate(Code::Upload(dummy_code)) - .value(min_balance * 100) - .build_and_unwrap_contract(); + builder::bare_call(addr).data(params).build_and_unwrap_result() + }; - let Contract { addr: addr_call_runtime, .. } = - builder::bare_instantiate(Code::Upload(call_runtime_code)) - .value(min_balance * 100) - .build_and_unwrap_contract(); + // verification should succeed for "hello world" + assert_return_code!(call_with(&b"hello world"), RuntimeReturnCode::Success); - // 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), - ]; + // verification should fail for other messages + assert_return_code!(call_with(&b"hello worlD"), RuntimeReturnCode::Sr25519VerifyFailed); + }); +} - // 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), - ]; +#[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; - 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).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) - .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) - .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) - .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); - } - } - }); - } + let execute = || { + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - #[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 Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(caller_code)) - .value(min_balance * 100) - .salt(Some([0; 32])) + // Instantiate both contracts. + let caller = builder::bare_instantiate(Code::Upload(wasm_caller.clone())) + .build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(wasm_callee.clone())) .build_and_unwrap_contract(); - // 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), + // Give caller proxy access to Alice. + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(ALICE), + caller.account_id.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 result = builder::bare_call(addr_caller).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 + + let data = ( + (ED - DepositPerItem::get()) as u32, // storage length + addr_callee, + transfer_proxy_call, ); - }); - } - #[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 Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(caller_code)) - .value(min_balance * 100) - .salt(Some([0; 32])) - .build_and_unwrap_contract(); + builder::call(caller.addr).data(data.encode()).build() + }) + }; - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(callee_code)) - .value(min_balance * 100) - .salt(Some([1; 32])) - .build_and_unwrap_contract(); + // With a low enough deposit per byte, the call should succeed. + let result = execute().unwrap(); - // 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![], - }); + // 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); +} - // Call runtime to re-enter back to contracts engine by - // calling dummy contract - let result = - builder::bare_call(addr_caller).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 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_ADDR + }), + topics: vec![], + },] + ); + }); +} - #[test] - fn ecdsa_recover() { - let (wasm, _code_hash) = compile_module("ecdsa_recover").unwrap(); +#[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(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - // Instantiate the ecdsa_recover contract. - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(100_000) - .build_and_unwrap_contract(); + // Drop previous events + initialize_block(2); - #[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).data(params).build_and_unwrap_result(); - assert!(!result.did_revert()); - assert_eq!(result.data, EXPECTED_COMPRESSED_PUBLIC_KEY); - }) - } + assert_noop!( + Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, deposit_insufficient,), + >::StorageDepositLimitExhausted, + ); - #[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); + assert_eq!(System::events(), vec![]); + }); +} - let result = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .collect_events(CollectEvents::UnsafeCollect) - .build(); +#[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); - let events = result.events.unwrap(); - assert!(!events.is_empty()); - assert_eq!(events, System::events()); - }); - } + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, deposit_insufficient); - #[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); + // Drop previous events + initialize_block(2); - let result = - builder::bare_instantiate(Code::Upload(wasm)).value(min_balance * 100).build(); + assert_noop!( + Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,), + >::StorageDepositNotEnoughFunds, + ); - let events = result.events; - assert!(!System::events().is_empty()); - assert!(events.is_none()); - }); - } + assert_eq!(System::events(), vec![]); + }); +} - #[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); +#[test] +fn remove_code_works() { + let (wasm, code_hash) = compile_module("dummy").unwrap(); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .build_and_unwrap_contract(); + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let result = - builder::bare_call(addr).collect_events(CollectEvents::UnsafeCollect).build(); + // Drop previous events + initialize_block(2); - let events = result.events.unwrap(); - assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); - assert!(!events.is_empty()); - assert_eq!(events, System::events()); - }); - } + 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)); - #[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); + assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE_ADDR + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeRemoved { + code_hash, + deposit_released: deposit_expected, + remover: ALICE_ADDR + }), + topics: vec![], + }, + ] + ); + }); +} - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .build_and_unwrap_contract(); +#[test] +fn remove_code_wrong_origin() { + let (wasm, code_hash) = compile_module("dummy").unwrap(); - let result = builder::bare_call(addr).build(); + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let events = result.events; - assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); - assert!(!System::events().is_empty()); - assert!(events.is_none()); - }); - } + // Drop previous events + initialize_block(2); - #[test] - fn sr25519_verify() { - let (wasm, _code_hash) = compile_module("sr25519_verify").unwrap(); + 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)); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash), + sp_runtime::traits::BadOrigin, + ); - // Instantiate the sr25519_verify contract. - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(100_000) - .build_and_unwrap_contract(); + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE_ADDR + }), + topics: vec![], + },] + ); + }); +} - 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, - ]; +#[test] +fn remove_code_in_use() { + let (wasm, code_hash) = compile_module("dummy").unwrap(); - // 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, - ]; + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let mut params = vec![]; - params.extend_from_slice(&signature); - params.extend_from_slice(&public_key); - params.extend_from_slice(message); + assert_ok!(builder::instantiate_with_code(wasm).build()); - builder::bare_call(addr).data(params).build_and_unwrap_result() - }; + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + >::CodeInUse, + ); - // verification should succeed for "hello world" - assert_return_code!(call_with(&b"hello world"), RuntimeReturnCode::Success); + assert_eq!(System::events(), vec![]); + }); +} - // verification should fail for other messages - assert_return_code!(call_with(&b"hello worlD"), RuntimeReturnCode::Sr25519VerifyFailed); - }); - } +#[test] +fn remove_code_not_found() { + let (_wasm, code_hash) = compile_module("dummy").unwrap(); - #[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; + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let execute = || { - ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + // Drop previous events + initialize_block(2); - // Instantiate both contracts. - let caller = builder::bare_instantiate(Code::Upload(wasm_caller.clone())) - .build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(wasm_callee.clone())) - .build_and_unwrap_contract(); - - // Give caller proxy access to Alice. - assert_ok!(Proxy::add_proxy( - RuntimeOrigin::signed(ALICE), - caller.account_id.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, - ); + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + >::CodeNotFound, + ); - builder::call(caller.addr).data(data.encode()).build() - }) - }; + assert_eq!(System::events(), vec![]); + }); +} - // With a low enough deposit per byte, the call should succeed. - let result = execute().unwrap(); +#[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(); - // 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); - } + // Drop previous events + initialize_block(2); - #[test] - fn upload_code_works() { - let (wasm, code_hash) = compile_module("dummy").unwrap(); + // Instantiate the BOB contract. + let Contract { addr, account_id } = + builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); - // Drop previous events - initialize_block(2); + // Make sure the account exists even though no free balance was send + assert_eq!(::Currency::free_balance(&account_id), min_balance); + assert_eq!( + ::Currency::total_balance(&account_id), + min_balance + test_utils::contract_info_storage_deposit(&addr) + ); - assert!(!PristineCode::::contains_key(&code_hash)); + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE_ADDR + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: account_id.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: account_id.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id, + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE_ADDR, + contract: addr, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE_ADDR, + to: addr, + amount: test_utils::contract_info_storage_deposit(&addr), + } + ), + topics: vec![], + }, + ] + ); + }); +} - 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)); +#[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 Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(wasm)) + .value(value) + .build_and_unwrap_contract(); + + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); + // Make sure the account exists even though not enough free balance was send + assert_eq!(::Currency::free_balance(&account_id), min_balance + value); + assert_eq!( + ::Currency::total_balance(&account_id), + min_balance + value + test_utils::contract_info_storage_deposit(&addr) + ); - assert_eq!( - System::events(), - vec![EventRecord { + assert_eq!( + System::events(), + vec![ + EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash, @@ -2596,2057 +2843,1740 @@ mod run_tests { uploader: ALICE_ADDR }), topics: vec![], - },] - ); - }); - } + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: account_id.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: account_id.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: 50, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE_ADDR, + contract: addr, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE_ADDR, + to: addr, + amount: test_utils::contract_info_storage_deposit(&addr), + } + ), + topics: vec![], + }, + ] + ); + }); +} - #[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); +#[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 Contract { addr, account_id } = + builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); + + let mut deposit = test_utils::contract_info_storage_deposit(&addr); + + // Drop previous events + initialize_block(2); + + // Create storage + assert_ok!(builder::call(addr).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).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).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: account_id.clone(), + amount: 42, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: addr, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE_ADDR, + to: addr, + amount: charged0, + } + ), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: addr, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE_ADDR, + to: addr, + amount: charged1, + } + ), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: addr, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndReleased { + from: addr, + to: ALICE_ADDR, + amount: refunded0, + } + ), + topics: vec![], + }, + ] + ); + }); +} - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); +#[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 Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, account_id } = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + + 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(&account_id), min_balance); + assert_eq!( + callee.total_deposit(), + deposit + test_utils::contract_info_storage_deposit(&addr_callee) + ); + }); +} - // Drop previous events - initialize_block(2); +#[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_noop!( - Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, deposit_insufficient,), - >::StorageDepositLimitExhausted, - ); + assert_ne!(code_hash, new_code_hash); - assert_eq!(System::events(), vec![]); - }); - } + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - #[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); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, deposit_insufficient); + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + new_wasm, + deposit_limit::(), + )); - // Drop previous events - initialize_block(2); + // Drop previous events + initialize_block(2); - assert_noop!( - Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,), - >::StorageDepositNotEnoughFunds, - ); + 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![]); - }); - } + // only root can execute this extrinsic + assert_noop!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), addr, 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_ADDR, 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, 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, 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, + new_code_hash, + old_code_hash: code_hash, + }), + topics: vec![], + },] + ); + }); +} - #[test] - fn remove_code_works() { - let (wasm, code_hash) = compile_module("dummy").unwrap(); +#[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(); - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(wasm)) + .value(value) + .build_and_unwrap_contract(); - // Drop previous events - initialize_block(2); + // 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)); + let info_deposit = test_utils::contract_info_storage_deposit(&addr); - assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); - assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeRemoved { - code_hash, - deposit_released: deposit_expected, - remover: ALICE_ADDR - }), - topics: vec![], - }, - ] - ); - }); - } + assert_eq!( + test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account_id), + info_deposit + ); - #[test] - fn remove_code_wrong_origin() { - let (wasm, code_hash) = compile_module("dummy").unwrap(); + assert_eq!( + ::Currency::total_balance(&account_id), + info_deposit + value + min_balance + ); - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + // 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(), + &account_id, + ::Currency::total_balance(&account_id), + ); - // Drop previous events - initialize_block(2); + // Slashing only removed the balance held. + assert_eq!(::Currency::total_balance(&account_id), value + min_balance); + }); +} - 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)); +#[test] +fn contract_reverted() { + let (wasm, code_hash) = compile_module("return_with_data").unwrap(); - assert_noop!( - Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash), - sp_runtime::traits::BadOrigin, - ); + 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(); - assert_eq!( - System::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - },] - ); - }); - } - - #[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 Contract { addr, account_id } = - builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - - // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); - - // Make sure the account exists even though no free balance was send - assert_eq!(::Currency::free_balance(&account_id), min_balance); - assert_eq!( - ::Currency::total_balance(&account_id), - 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_ADDR - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: account_id.clone(), - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: account_id.clone(), - free_balance: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id, - amount: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, - ] - ); - }); - } - - #[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 Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(wasm)) - .value(value) - .build_and_unwrap_contract(); - - // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); - // Make sure the account exists even though not enough free balance was send - assert_eq!(::Currency::free_balance(&account_id), min_balance + value); - assert_eq!( - ::Currency::total_balance(&account_id), - 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_ADDR - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: account_id.clone() - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: account_id.clone(), - free_balance: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: 50, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, - ] - ); - }); - } - - #[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 Contract { addr, account_id } = - builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - - let mut deposit = test_utils::contract_info_storage_deposit(&addr); - - // Drop previous events - initialize_block(2); - - // Create storage - assert_ok!(builder::call(addr).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).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).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: account_id.clone(), - amount: 42, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: charged0, - } - ), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: charged1, - } - ), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndReleased { - from: addr, - to: ALICE_ADDR, - amount: refunded0, - } - ), - topics: vec![], - }, - ] - ); - }); - } - - #[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 Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, account_id } = - builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); - - 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(&account_id), 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); + // 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, + ); - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + builder::instantiate_with_code(wasm).data(input.clone()).build(), + >::ContractReverted, + ); - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); + // 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.addr)); + + // Pass empty flags and therefore successfully instantiate the contract for later use. + let Contract { addr, .. } = builder::bare_instantiate(Code::Existing(code_hash)) + .data(ReturnFlags::empty().bits().encode()) + .build_and_unwrap_contract(); + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + builder::call(addr).data(input.clone()).build(), + >::ContractReverted, + ); - assert_ok!(Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - new_wasm, - deposit_limit::(), - )); + // Calling directly: revert leads to success but the flags indicate the error + let result = builder::bare_call(addr).data(input).build_and_unwrap_result(); + assert_eq!(result.flags, flags); + assert_eq!(result.data, buffer); + }); +} - // Drop previous events - initialize_block(2); +#[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(); - assert_eq!(get_contract(&addr).code_hash, code_hash); - assert_refcount!(&code_hash, 1); - assert_refcount!(&new_code_hash, 0); + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - // only root can execute this extrinsic - assert_noop!( - Contracts::set_code(RuntimeOrigin::signed(ALICE), addr, 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_ADDR, 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, 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, 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 { + // Instantiate the 'caller' + let Contract { addr: contract_addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) + .value(300_000) + .build_and_unwrap_contract(); + // 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) + .data(new_code_hash.as_ref().to_vec()) + .debug(DebugInfo::UnsafeDebug) + .build_and_unwrap_result(); + assert_return_code!(result, 1); + + // Second calls new contract code that returns 2 + let result = builder::bare_call(contract_addr) + .debug(DebugInfo::UnsafeDebug) + .build_and_unwrap_result(); + assert_return_code!(result, 2); + + // Checking for the last event only + assert_eq!( + &System::events(), + &[ + EventRecord { phase: Phase::Initialization, - event: RuntimeEvent::Contracts(pallet_revive::Event::ContractCodeUpdated { - contract: addr, + event: RuntimeEvent::Contracts(crate::Event::ContractCodeUpdated { + contract: contract_addr, 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 Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(wasm)) - .value(value) - .build_and_unwrap_contract(); - - // 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(), - &account_id - ), - info_deposit - ); - - assert_eq!( - ::Currency::total_balance(&account_id), - 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(), - &account_id, - ::Currency::total_balance(&account_id), - ); - - // Slashing only removed the balance held. - assert_eq!(::Currency::total_balance(&account_id), 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.addr)); - - // Pass empty flags and therefore successfully instantiate the contract for later use. - let Contract { addr, .. } = builder::bare_instantiate(Code::Existing(code_hash)) - .data(ReturnFlags::empty().bits().encode()) - .build_and_unwrap_contract(); - - // Calling extrinsic: revert leads to an error - assert_err_ignore_postinfo!( - builder::call(addr).data(input.clone()).build(), - >::ContractReverted, - ); - - // Calling directly: revert leads to success but the flags indicate the error - let result = builder::bare_call(addr).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: contract_addr, .. } = - builder::bare_instantiate(Code::Upload(wasm)) - .value(300_000) - .build_and_unwrap_contract(); - // 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) - .data(new_code_hash.as_ref().to_vec()) - .debug(DebugInfo::UnsafeDebug) - .build_and_unwrap_result(); - assert_return_code!(result, 1); - - // Second calls new contract code that returns 2 - let result = builder::bare_call(contract_addr) - .debug(DebugInfo::UnsafeDebug) - .build_and_unwrap_result(); - assert_return_code!(result, 2); - - // Checking for the last event only - assert_eq!( - &System::events(), - &[ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::ContractCodeUpdated { - contract: contract_addr, - new_code_hash, - old_code_hash: code_hash, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract_addr, - }), - topics: vec![], - }, - ], - ); - }); - } - - #[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, - ); + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: contract_addr, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: contract_addr, + }), + topics: vec![], + }, + ], + ); + }); +} - // Instantiate the BOB contract. - let Contract { addr, account_id } = - builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); +#[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, + ); - 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(&account_id), - info_deposit + min_balance - ); + // Instantiate the BOB contract. + let Contract { addr, account_id } = + builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - // 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) - .storage_deposit_limit(2) - .data(1u32.to_le_bytes().to_vec()) - .build(), - >::StorageDepositLimitExhausted, - ); + 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(&account_id), + info_deposit + min_balance + ); - // 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) - .storage_deposit_limit(3) + // 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) + .storage_deposit_limit(2) .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) - .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); + .build(), + >::StorageDepositLimitExhausted, + ); - // Create both contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + // 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) + .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) + .storage_deposit_limit(3) + .data(5u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + }); +} - // 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) - .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) - .storage_deposit_limit(13) - .data((100u32, &addr_callee, U256::from(0u64)).encode()) - .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 Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + + // 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) + .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) + .storage_deposit_limit(13) + .data((100u32, &addr_callee, U256::from(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) - .storage_deposit_limit(14) - .data((101u32, &addr_callee, U256::from(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) + .storage_deposit_limit(14) + .data((101u32, &addr_callee, U256::from(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) - .storage_deposit_limit(16) - .data((102u32, &addr_callee, U256::from(1u64)).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) + .storage_deposit_limit(16) + .data((102u32, &addr_callee, U256::from(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) - .storage_deposit_limit(0) - .data((87u32, &addr_callee, U256::from(0u64)).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) + .storage_deposit_limit(0) + .data((87u32, &addr_callee, U256::from(0u64)).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); - let _ = ::Currency::set_balance(&ALICE, 511); + 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) - .data((512u32, &addr_callee, U256::from(1u64)).encode()) - .build(), - >::StorageDepositLimitExhausted, - ); + // Require more than the sender's balance. + // We don't set a special limit for the nested call. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .data((512u32, &addr_callee, U256::from(1u64)).encode()) + .build(), + >::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) - .storage_deposit_limit(1) - .data((87u32, &addr_callee, U256::from(1u64)).encode()) - .build()); - }); - } + // 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) + .storage_deposit_limit(1) + .data((87u32, &addr_callee, U256::from(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 Contract { addr: addr_caller, account_id: caller_id } = - builder::bare_instantiate(Code::Upload(wasm_caller)) - .value(10_000u64) // this balance is later passed to the deployed contract - .build_and_unwrap_contract(); - // Deploy a contract to get its occupied storage size - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm_callee)) - .data(vec![0, 0, 0, 0]) +#[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 Contract { addr: addr_caller, account_id: caller_id } = + builder::bare_instantiate(Code::Upload(wasm_caller)) + .value(10_000u64) // this balance is later passed to the deployed contract .build_and_unwrap_contract(); - - 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) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 1) - .data((0u32, &code_hash_callee, U256::from(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) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 2) - .data((1u32, &code_hash_callee, U256::from(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) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 2) - .data( - (0u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 1)) - .encode() - ) - .build(), - >::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) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 3) // enough parent limit - .data( - (1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 2)) - .encode() - ) - .build(), - >::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) + // Deploy a contract to get its occupied storage size + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm_callee)) + .data(vec![0, 0, 0, 0]) + .build_and_unwrap_contract(); + + 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) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 4 + 2) - .data( - (1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 3 + 2)) - .encode(), - ) - .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(&caller_id), ED); - // Get address of the deployed contract. - let addr_callee = H160::from_slice(&returned.data[0..20]); - let callee_account_id = ::AddressMapper::to_account_id(&addr_callee); - // 10_000 should be sent to callee from the caller contract, plus ED to be sent from the - // origin. - assert_eq!(::Currency::free_balance(&callee_account_id), 10_000 + ED); - // The origin should be charged with: - // - callee instantiation deposit = (callee_info_len + 2) - // - callee account ED - // - for writing an item of 1 byte to storage = 3 Balance - // - Immutable data storage item deposit - 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 Contract { addr, account_id } = - builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - - 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(&account_id), - 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) - .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 Contract { addr, account_id } = - builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - - 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(&account_id), - min_balance + info_deposit - ); - - // check that the deposit can't bring the account below the existential deposit - assert_err_ignore_postinfo!( - builder::call(addr) - .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 Contract { addr, account_id } = - builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - - 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(&account_id), - 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) - .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| sp_core::H256(sp_io::hashing::keccak_256(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())) - .origin(RuntimeOrigin::signed(ALICE_FALLBACK)) - .data(input.encode()) - .build() - }; - - // Call contract with the given input. - let call = |addr_caller: &H160, input: &(u32, H256)| { - builder::bare_call(*addr_caller) - .origin(RuntimeOrigin::signed(ALICE_FALLBACK)) - .data(input.encode()) - .build() - }; - const ED: u64 = 2000; - ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { - let _ = Balances::set_balance(&ALICE_FALLBACK, 1_000_000); - - // Instantiate with lock_delegate_dependency should fail since the code is not yet on - // chain. - assert_err!( - instantiate(&lock_delegate_dependency_input).result, - Error::::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_FALLBACK), - code.clone(), - deposit_limit::(), - ) - .unwrap(); - deposit = deposit_per_code; - } - - // Instantiate should now work. - let addr_caller = instantiate(&lock_delegate_dependency_input).result.unwrap().addr; - let caller_account_id = ::AddressMapper::to_account_id(&addr_caller); - - // There should be a dependency and a deposit. - let contract = test_utils::get_contract(&addr_caller); - - let dependency_deposit = &CodeHashLockupDepositPercent::get().mul_ceil(deposit); - assert_eq!( - contract.delegate_dependencies().get(&callee_hashes[0]), - Some(dependency_deposit) - ); - assert_eq!( - test_utils::get_balance_on_hold( - &HoldReason::StorageDepositReserve.into(), - &caller_account_id - ), - dependency_deposit + contract.storage_base_deposit() - ); - - // Removing the code should fail, since we have added a dependency. - assert_err!( - Contracts::remove_code(RuntimeOrigin::signed(ALICE_FALLBACK), callee_hashes[0]), - >::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(), - &caller_account_id - ), - 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) - .storage_deposit_limit(dependency_deposit - 1) - .data(lock_delegate_dependency_input.encode()) - .build() - .result, - Error::::StorageDepositLimitExhausted - ); + .storage_deposit_limit(callee_info_len + 2 + ED + 1) + .data((0u32, &code_hash_callee, U256::from(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) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 2) + .data((1u32, &code_hash_callee, U256::from(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) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 2) + .data((0u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 1)).encode()) + .build(), + >::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) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 3) // enough parent limit + .data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 2)).encode()) + .build(), + >::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) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 4 + 2) + .data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 3 + 2)).encode()) + .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(&caller_id), ED); + // Get address of the deployed contract. + let addr_callee = H160::from_slice(&returned.data[0..20]); + let callee_account_id = ::AddressMapper::to_account_id(&addr_callee); + // 10_000 should be sent to callee from the caller contract, plus ED to be sent from the + // origin. + assert_eq!(::Currency::free_balance(&callee_account_id), 10_000 + ED); + // The origin should be charged with: + // - callee instantiation deposit = (callee_info_len + 2) + // - callee account ED + // - for writing an item of 1 byte to storage = 3 Balance + // - Immutable data storage item deposit + 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)) + }); +} - // Since we unlocked the dependency we should now be able to remove the code. - assert_ok!(Contracts::remove_code( - RuntimeOrigin::signed(ALICE_FALLBACK), - callee_hashes[0] - )); +#[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 Contract { addr, account_id } = + builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); + + 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(&account_id), + info_deposit + min_balance + ); - // Calling should fail since the delegated contract is not on chain anymore. - assert_err!(call(&addr_caller, &noop_input).result, Error::::ContractTrapped); + // check that the hold is honored + ::Currency::hold( + &HoldReason::CodeUploadDepositReserve.into(), + &BOB, + bobs_balance - min_balance, + ) + .unwrap(); + assert_err_ignore_postinfo!( + builder::call(addr) + .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); + }); +} - // Add the dependency back. - Contracts::upload_code( - RuntimeOrigin::signed(ALICE_FALLBACK), - callee_codes[0].clone(), - deposit_limit::(), - ) - .unwrap(); - call(&addr_caller, &lock_delegate_dependency_input).result.unwrap(); +#[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 Contract { addr, account_id } = + builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); + + 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(&account_id), + min_balance + info_deposit + ); - // Call terminate should work, and return the deposit. - let balance_before = test_utils::get_balance(&ALICE_FALLBACK); - assert_ok!(call(&addr_caller, &terminate_input).result); - assert_eq!( - test_utils::get_balance(&ALICE_FALLBACK), - ED + balance_before + contract.storage_base_deposit() + dependency_deposit - ); + // check that the deposit can't bring the account below the existential deposit + assert_err_ignore_postinfo!( + builder::call(addr) + .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); + }); +} - // Terminate should also remove the dependency, so we can remove the code. - assert_ok!(Contracts::remove_code( - RuntimeOrigin::signed(ALICE_FALLBACK), - callee_hashes[0] - )); - }); - } +#[test] +fn 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 Contract { addr, account_id } = + builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); + + 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(&account_id), + info_deposit + min_balance + ); - #[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(); + // 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) + .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); + }); +} - // 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] +fn locking_delegate_dependency_works() { + // set hash lock up deposit to 30%, to test deposit calculation. + CODE_HASH_LOCKUP_DEPOSIT_PERCENT.with(|c| *c.borrow_mut() = Perbill::from_percent(30)); + + let (wasm_caller, self_code_hash) = compile_module("locking_delegate_dependency").unwrap(); + let callee_codes: Vec<_> = + (0..limits::DELEGATE_DEPENDENCIES + 1).map(|idx| dummy_unique(idx)).collect(); + let callee_hashes: Vec<_> = callee_codes + .iter() + .map(|c| sp_core::H256(sp_io::hashing::keccak_256(c))) + .collect(); + + // 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())) + .origin(RuntimeOrigin::signed(ALICE_FALLBACK)) + .data(input.encode()) + .build() + }; - // 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(); + // Call contract with the given input. + let call = |addr_caller: &H160, input: &(u32, H256)| { + builder::bare_call(*addr_caller) + .origin(RuntimeOrigin::signed(ALICE_FALLBACK)) + .data(input.encode()) + .build() + }; + const ED: u64 = 2000; + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = Balances::set_balance(&ALICE_FALLBACK, 1_000_000); + + // Instantiate with lock_delegate_dependency should fail since the code is not yet on + // chain. + assert_err!( + instantiate(&lock_delegate_dependency_input).result, + Error::::CodeNotFound + ); - // Upload the dummy contract, - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - dummy_wasm.clone(), + // 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_FALLBACK), + code.clone(), deposit_limit::(), ) .unwrap(); + deposit = deposit_per_code; + } - // 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(); + // Instantiate should now work. + let addr_caller = instantiate(&lock_delegate_dependency_input).result.unwrap().addr; + let caller_account_id = ::AddressMapper::to_account_id(&addr_caller); - let addr = res.result.unwrap().addr; - let account_id = ::AddressMapper::to_account_id(&addr); - 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(); + // There should be a dependency and a deposit. + let contract = test_utils::get_contract(&addr_caller); - // 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); + let dependency_deposit = &CodeHashLockupDepositPercent::get().mul_ceil(deposit); + assert_eq!( + contract.delegate_dependencies().get(&callee_hashes[0]), + Some(dependency_deposit) + ); + assert_eq!( + test_utils::get_balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &caller_account_id + ), + dependency_deposit + contract.storage_base_deposit() + ); - assert_eq!( - res.storage_deposit.charge_or_zero(), - deposit + Contracts::min_balance() - ); + // Removing the code should fail, since we have added a dependency. + assert_err!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE_FALLBACK), callee_hashes[0]), + >::CodeInUse + ); - // call set_code_hash - builder::bare_call(addr) - .data(dummy_code_hash.encode()) - .build_and_unwrap_result(); + // Locking an already existing dependency should fail. + assert_err!( + call(&addr_caller, &lock_delegate_dependency_input).result, + Error::::DelegateDependencyAlreadyExists + ); - // 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); + // Locking self should fail. + assert_err!( + call(&addr_caller, &(1u32, self_code_hash)).result, + Error::::CannotAddSelfAsDelegateDependency + ); - assert_eq!( - test_utils::get_balance_on_hold( - &HoldReason::StorageDepositReserve.into(), - &account_id - ), - deposit - ); - }); + // 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(); } - } - - #[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(); + assert_err!( + call(&addr_caller, &(1u32, *callee_hashes.last().unwrap())).result, + Error::::MaxDelegateDependenciesReached + ); - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB_ADDR, code_hash), - DispatchError::BadOrigin, - ); - }); - } + // Unlocking all dependency should work. + for hash in &callee_hashes[..callee_hashes.len() - 1] { + call(&addr_caller, &(2u32, *hash)).result.unwrap(); + } - #[test] - fn none_cannot_call_code() { - ExtBuilder::default().build().execute_with(|| { - assert_err_ignore_postinfo!( - builder::call(BOB_ADDR).origin(RuntimeOrigin::none()).build(), - DispatchError::BadOrigin, - ); - }); - } + // Dependency should be removed, and deposit should be returned. + let contract = test_utils::get_contract(&addr_caller); + assert!(contract.delegate_dependencies().is_empty()); + assert_eq!( + test_utils::get_balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &caller_account_id + ), + contract.storage_base_deposit() + ); - #[test] - fn root_can_call() { - let (wasm, _) = compile_module("dummy").unwrap(); + // Removing a nonexistent dependency should fail. + assert_err!( + call(&addr_caller, &unlock_delegate_dependency_input).result, + Error::::DelegateDependencyNotFound + ); - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + // Locking a dependency with a storage limit too low should fail. + assert_err!( + builder::bare_call(addr_caller) + .storage_deposit_limit(dependency_deposit - 1) + .data(lock_delegate_dependency_input.encode()) + .build() + .result, + Error::::StorageDepositLimitExhausted + ); - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); + // Since we unlocked the dependency we should now be able to remove the code. + assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE_FALLBACK), callee_hashes[0])); - // Call the contract. - assert_ok!(builder::call(addr).origin(RuntimeOrigin::root()).build()); - }); - } + // Calling should fail since the delegated contract is not on chain anymore. + assert_err!(call(&addr_caller, &noop_input).result, Error::::ContractTrapped); - #[test] - fn root_cannot_instantiate_with_code() { - let (wasm, _) = compile_module("dummy").unwrap(); + // Add the dependency back. + Contracts::upload_code( + RuntimeOrigin::signed(ALICE_FALLBACK), + 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_FALLBACK); + assert_ok!(call(&addr_caller, &terminate_input).result); + assert_eq!( + test_utils::get_balance(&ALICE_FALLBACK), + ED + balance_before + contract.storage_base_deposit() + dependency_deposit + ); - ExtBuilder::default().build().execute_with(|| { - assert_err_ignore_postinfo!( - builder::instantiate_with_code(wasm).origin(RuntimeOrigin::root()).build(), - DispatchError::BadOrigin - ); - }); - } + // Terminate should also remove the dependency, so we can remove the code. + assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE_FALLBACK), callee_hashes[0])); + }); +} - #[test] - fn root_cannot_instantiate() { - let (_, code_hash) = compile_module("dummy").unwrap(); +#[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(); - ExtBuilder::default().build().execute_with(|| { - assert_err_ignore_postinfo!( - builder::instantiate(code_hash).origin(RuntimeOrigin::root()).build(), - DispatchError::BadOrigin - ); - }); - } + // 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] - fn only_upload_origin_can_upload() { - let (wasm, _) = compile_module("dummy").unwrap(); - UploadAccount::set(Some(ALICE)); + // 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 _ = 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 - ); + let lockup_deposit_percent = CodeHashLockupDepositPercent::get(); - // Only alice is allowed to upload contract code. - assert_ok!(Contracts::upload_code( + // Upload the dummy contract, + Contracts::upload_code( RuntimeOrigin::signed(ALICE), - wasm.clone(), + dummy_wasm.clone(), deposit_limit::(), - )); - }); - } + ) + .unwrap(); - #[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); + // 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, + }; - assert_err_ignore_postinfo!( - builder::instantiate_with_code(code.clone()) - .origin(RuntimeOrigin::root()) - .build(), - DispatchError::BadOrigin - ); + // Instantiate the set_code_hash contract. + let res = builder::bare_instantiate(code).build(); - assert_err_ignore_postinfo!( - builder::instantiate_with_code(code.clone()) - .origin(RuntimeOrigin::signed(BOB)) - .build(), - DispatchError::BadOrigin - ); + let addr = res.result.unwrap().addr; + let account_id = ::AddressMapper::to_account_id(&addr); + 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(); - // Only Alice can instantiate - assert_ok!(builder::instantiate_with_code(code).build()); + // 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); - // 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 - ); - }); - } + assert_eq!(res.storage_deposit.charge_or_zero(), deposit + Contracts::min_balance()); - #[test] - fn balance_of_api() { - let (wasm, _code_hash) = compile_module("balance_of").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = Balances::set_balance(&ALICE, 1_000_000); - let _ = Balances::set_balance(&ALICE_FALLBACK, 1_000_000); - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(wasm.to_vec())).build_and_unwrap_contract(); - - // The fixture asserts a non-zero returned free balance of the account; - // The ALICE_FALLBACK account is endowed; - // Hence we should not revert - assert_ok!(builder::call(addr).data(ALICE_ADDR.0.to_vec()).build()); - - // The fixture asserts a non-zero returned free balance of the account; - // The ETH_BOB account is not endowed; - // Hence we should revert - assert_err_ignore_postinfo!( - builder::call(addr).data(BOB_ADDR.0.to_vec()).build(), - >::ContractTrapped + // call set_code_hash + builder::bare_call(addr) + .data(dummy_code_hash.encode()) + .build_and_unwrap_result(); + + // Check updated storage_deposit + let code_deposit = test_utils::get_code_deposit(&dummy_code_hash); + let deposit = base_deposit + lockup_deposit_percent.mul_ceil(code_deposit); + assert_eq!(test_utils::get_contract(&addr).storage_base_deposit(), deposit); + + assert_eq!( + test_utils::get_balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &account_id + ), + deposit ); }); } +} - #[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); +#[test] +fn block_hash_works() { + let (code, _) = compile_module("block_hash").unwrap(); - // Instantiate the BOB contract without any extra balance. - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(wasm.to_vec())).build_and_unwrap_contract(); + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - 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).value(value).build()); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let value = 1; - // Calling with value will trap the contract. - assert_err_ignore_postinfo!( - builder::call(addr).value(value).build(), - >::ContractTrapped - ); - }); - } + // The genesis config sets to the block number to 1 + let block_hash = [1; 32]; + frame_system::BlockHash::::insert( + &crate::BlockNumberFor::::from(0u32), + ::Hash::from(&block_hash), + ); + assert_ok!(builder::call(addr) + .data((U256::zero(), H256::from(block_hash)).encode()) + .build()); - #[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); + // A block number out of range returns the zero value + assert_ok!(builder::call(addr).data((U256::from(1), H256::zero()).encode()).build()); + }); +} - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); +#[test] +fn root_cannot_upload_code() { + let (wasm, _) = compile_module("dummy").unwrap(); - 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).data(i.encode()).build(); - assert_ok!(result.result); - result.gas_consumed - }) - .collect::>() - .try_into() - .unwrap() - }; + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::upload_code(RuntimeOrigin::root(), wasm, deposit_limit::()), + DispatchError::BadOrigin, + ); + }); +} - 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 root_cannot_remove_code() { + let (_, code_hash) = compile_module("dummy").unwrap(); - #[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); + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::remove_code(RuntimeOrigin::root(), code_hash), + DispatchError::BadOrigin, + ); + }); +} - // Create both contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); +#[test] +fn signed_cannot_set_code() { + let (_, code_hash) = compile_module("dummy").unwrap(); - // Read-only call fails when modifying storage. - assert_err_ignore_postinfo!( - builder::call(addr_caller).data((&addr_callee, 100u32).encode()).build(), - >::ContractTrapped - ); - }); - } + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB_ADDR, code_hash), + DispatchError::BadOrigin, + ); + }); +} - #[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); +#[test] +fn none_cannot_call_code() { + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + builder::call(BOB_ADDR).origin(RuntimeOrigin::none()).build(), + DispatchError::BadOrigin, + ); + }); +} - // Create both contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); - - // 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 root_can_call() { + let (wasm, _) = compile_module("dummy").unwrap(); - #[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); + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - // Create contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(wasm_read_only_caller)) - .build_and_unwrap_contract(); - let Contract { addr: addr_subsequent_caller, .. } = - builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - // Subsequent call input. - let input = (&addr_callee, pallet_revive_uapi::CallFlags::empty().bits(), 0u64, 100u32); + // Call the contract. + assert_ok!(builder::call(addr).origin(RuntimeOrigin::root()).build()); + }); +} - // 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 root_cannot_instantiate_with_code() { + let (wasm, _) = compile_module("dummy").unwrap(); - #[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); + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + builder::instantiate_with_code(wasm).origin(RuntimeOrigin::root()).build(), + DispatchError::BadOrigin + ); + }); +} - // Create both contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); +#[test] +fn root_cannot_instantiate() { + let (_, code_hash) = compile_module("dummy").unwrap(); - assert_ok!(builder::call(addr_caller).data(addr_callee.encode()).build()); - }); - } + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).origin(RuntimeOrigin::root()).build(), + DispatchError::BadOrigin + ); + }); +} - #[test] - fn create1_with_value_works() { - let (code, code_hash) = compile_module("create1_with_value").unwrap(); - let value = 42; - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); +#[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 + ); - // Create the contract: Constructor does nothing. - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(BOB), + wasm.clone(), + deposit_limit::(), + ), + DispatchError::BadOrigin + ); - // Call the contract: Deploys itself using create1 and the expected value - assert_ok!(builder::call(addr).value(value).data(code_hash.encode()).build()); + // Only alice is allowed to upload contract code. + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + deposit_limit::(), + )); + }); +} - // We should see the expected balance at the expected account - let address = crate::address::create1(&addr, 0); - let account_id = ::AddressMapper::to_account_id(&address); - let usable_balance = ::Currency::usable_balance(&account_id); - assert_eq!(usable_balance, value); - }); - } +#[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 + ); - #[test] - fn static_data_limit_is_enforced() { - let (oom_rw_trailing, _) = compile_module("oom_rw_trailing").unwrap(); - let (oom_rw_included, _) = compile_module("oom_rw_included").unwrap(); - let (oom_ro, _) = compile_module("oom_ro").unwrap(); + assert_err_ignore_postinfo!( + builder::instantiate_with_code(code.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .build(), + DispatchError::BadOrigin + ); - ExtBuilder::default().build().execute_with(|| { - let _ = Balances::set_balance(&ALICE, 1_000_000); + // Only Alice can instantiate + assert_ok!(builder::instantiate_with_code(code).build()); - assert_err!( - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - oom_rw_trailing, - deposit_limit::(), - ), - >::StaticMemoryTooLarge - ); + // 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 + ); + }); +} - assert_err!( - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - oom_rw_included, - deposit_limit::(), - ), - >::BlobTooLarge - ); +#[test] +fn balance_of_api() { + let (wasm, _code_hash) = compile_module("balance_of").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + let _ = Balances::set_balance(&ALICE_FALLBACK, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(wasm.to_vec())).build_and_unwrap_contract(); + + // The fixture asserts a non-zero returned free balance of the account; + // The ALICE_FALLBACK account is endowed; + // Hence we should not revert + assert_ok!(builder::call(addr).data(ALICE_ADDR.0.to_vec()).build()); + + // The fixture asserts a non-zero returned free balance of the account; + // The ETH_BOB account is not endowed; + // Hence we should revert + assert_err_ignore_postinfo!( + builder::call(addr).data(BOB_ADDR.0.to_vec()).build(), + >::ContractTrapped + ); + }); +} - assert_err!( - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - oom_ro, - deposit_limit::(), - ), - >::BlobTooLarge - ); - }); - } +#[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 Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(wasm.to_vec())).build_and_unwrap_contract(); + + 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).value(value).build()); + + let value = 1; + // Calling with value will trap the contract. + assert_err_ignore_postinfo!( + builder::call(addr).value(value).build(), + >::ContractTrapped + ); + }); +} - #[test] - fn call_diverging_out_len_works() { - let (code, _) = compile_module("call_diverging_out_len").unwrap(); +#[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 Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + 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).data(i.encode()).build(); + assert_ok!(result.result); + result.gas_consumed + }) + .collect::>() + .try_into() + .unwrap() + }; - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + 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); + }); +} - // Create the contract: Constructor does nothing - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); +#[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 Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + + // Read-only call fails when modifying storage. + assert_err_ignore_postinfo!( + builder::call(addr_caller).data((&addr_callee, 100u32).encode()).build(), + >::ContractTrapped + ); + }); +} - // Call the contract: It will issue calls and deploys, asserting on - // correct output if the supplied output length was smaller than - // than what the callee returned. - assert_ok!(builder::call(addr).build()); - }); - } +#[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 Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + + // 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 chain_id_works() { - let (code, _) = compile_module("chain_id").unwrap(); +#[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 Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(wasm_read_only_caller)) + .build_and_unwrap_contract(); + let Contract { addr: addr_subsequent_caller, .. } = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + + // 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 + ); + }); +} - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); +#[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 Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + + assert_ok!(builder::call(addr_caller).data(addr_callee.encode()).build()); + }); +} - let chain_id = U256::from(::ChainId::get()); - let received = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_result(); - assert_eq!(received.result.data, chain_id.encode()); - }); - } +#[test] +fn create1_with_value_works() { + let (code, code_hash) = compile_module("create1_with_value").unwrap(); + let value = 42; + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create the contract: Constructor does nothing. + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: Deploys itself using create1 and the expected value + assert_ok!(builder::call(addr).value(value).data(code_hash.encode()).build()); + + // We should see the expected balance at the expected account + let address = crate::address::create1(&addr, 0); + let account_id = ::AddressMapper::to_account_id(&address); + let usable_balance = ::Currency::usable_balance(&account_id); + assert_eq!(usable_balance, value); + }); +} - #[test] - fn return_data_api_works() { - let (code_return_data_api, _) = compile_module("return_data_api").unwrap(); - let (code_return_with_data, hash_return_with_data) = - compile_module("return_with_data").unwrap(); +#[test] +fn static_data_limit_is_enforced() { + let (oom_rw_trailing, _) = compile_module("oom_rw_trailing").unwrap(); + let (oom_rw_included, _) = compile_module("oom_rw_included").unwrap(); + let (oom_ro, _) = compile_module("oom_ro").unwrap(); - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); - // Upload the io echoing fixture for later use - assert_ok!(Contracts::upload_code( + assert_err!( + Contracts::upload_code( RuntimeOrigin::signed(ALICE), - code_return_with_data, + oom_rw_trailing, deposit_limit::(), - )); + ), + >::StaticMemoryTooLarge + ); - // Create fixture: Constructor does nothing - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code_return_data_api)) - .build_and_unwrap_contract(); + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + oom_rw_included, + deposit_limit::(), + ), + >::BlobTooLarge + ); - // Call the contract: It will issue calls and deploys, asserting on - assert_ok!(builder::call(addr) - .value(10 * 1024) - .data(hash_return_with_data.encode()) - .build()); - }); - } + assert_err!( + Contracts::upload_code(RuntimeOrigin::signed(ALICE), oom_ro, deposit_limit::(),), + >::BlobTooLarge + ); + }); +} - #[test] - fn immutable_data_works() { - let (code, _) = compile_module("immutable_data").unwrap(); +#[test] +fn call_diverging_out_len_works() { + let (code, _) = compile_module("call_diverging_out_len").unwrap(); - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let data = [0xfe; 8]; + // Create the contract: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - // Create fixture: Constructor sets the immtuable data - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .data(data.to_vec()) - .build_and_unwrap_contract(); + // Call the contract: It will issue calls and deploys, asserting on + // correct output if the supplied output length was smaller than + // than what the callee returned. + assert_ok!(builder::call(addr).build()); + }); +} - // Storing immmutable data charges storage deposit; verify it explicitly. - assert_eq!( - test_utils::get_balance_on_hold( - &HoldReason::StorageDepositReserve.into(), - &::AddressMapper::to_account_id(&addr) - ), - test_utils::contract_info_storage_deposit(&addr) - ); - assert_eq!(test_utils::get_contract(&addr).immutable_data_len(), data.len() as u32); +#[test] +fn chain_id_works() { + let (code, _) = compile_module("chain_id").unwrap(); - // Call the contract: Asserts the input to equal the immutable data - assert_ok!(builder::call(addr).data(data.to_vec()).build()); - }); - } + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - #[test] - fn sbrk_cannot_be_deployed() { - let (code, _) = compile_module("sbrk").unwrap(); + let chain_id = U256::from(::ChainId::get()); + let received = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_result(); + assert_eq!(received.result.data, chain_id.encode()); + }); +} - ExtBuilder::default().build().execute_with(|| { - let _ = Balances::set_balance(&ALICE, 1_000_000); +#[test] +fn return_data_api_works() { + let (code_return_data_api, _) = compile_module("return_data_api").unwrap(); + let (code_return_with_data, hash_return_with_data) = + compile_module("return_with_data").unwrap(); - assert_err!( - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - code.clone(), - deposit_limit::(), - ), - >::InvalidInstruction - ); + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - assert_err!( - builder::bare_instantiate(Code::Upload(code)).build().result, - >::InvalidInstruction - ); - }); - } + // Upload the io echoing fixture for later use + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + code_return_with_data, + deposit_limit::(), + )); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code_return_data_api)) + .build_and_unwrap_contract(); + + // Call the contract: It will issue calls and deploys, asserting on + assert_ok!(builder::call(addr) + .value(10 * 1024) + .data(hash_return_with_data.encode()) + .build()); + }); +} - #[test] - fn overweight_basic_block_cannot_be_deployed() { - let (code, _) = compile_module("basic_block").unwrap(); +#[test] +fn immutable_data_works() { + let (code, _) = compile_module("immutable_data").unwrap(); - ExtBuilder::default().build().execute_with(|| { - let _ = Balances::set_balance(&ALICE, 1_000_000); + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - assert_err!( - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - code.clone(), - deposit_limit::(), - ), - >::BasicBlockTooLarge - ); + let data = [0xfe; 8]; - assert_err!( - builder::bare_instantiate(Code::Upload(code)).build().result, - >::BasicBlockTooLarge - ); - }); - } + // Create fixture: Constructor sets the immtuable data + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .data(data.to_vec()) + .build_and_unwrap_contract(); - #[test] - fn origin_api_works() { - let (code, _) = compile_module("origin").unwrap(); + // Storing immmutable data charges storage deposit; verify it explicitly. + assert_eq!( + test_utils::get_balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &::AddressMapper::to_account_id(&addr) + ), + test_utils::contract_info_storage_deposit(&addr) + ); + assert_eq!(test_utils::get_contract(&addr).immutable_data_len(), data.len() as u32); - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + // Call the contract: Asserts the input to equal the immutable data + assert_ok!(builder::call(addr).data(data.to_vec()).build()); + }); +} - // Create fixture: Constructor does nothing - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); +#[test] +fn sbrk_cannot_be_deployed() { + let (code, _) = compile_module("sbrk").unwrap(); - // Call the contract: Asserts the origin API to work as expected - assert_ok!(builder::call(addr).build()); - }); - } + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + code.clone(), + deposit_limit::(), + ), + >::InvalidInstruction + ); - #[test] - fn code_hash_works() { - let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap(); - let (dummy_code, code_hash) = compile_module("dummy").unwrap(); + assert_err!( + builder::bare_instantiate(Code::Upload(code)).build().result, + >::InvalidInstruction + ); + }); +} - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); +#[test] +fn overweight_basic_block_cannot_be_deployed() { + let (code, _) = compile_module("basic_block").unwrap(); - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code_hash_code)).build_and_unwrap_contract(); - let Contract { addr: dummy_addr, .. } = - builder::bare_instantiate(Code::Upload(dummy_code)).build_and_unwrap_contract(); - - // code hash of dummy contract - assert_ok!(builder::call(addr).data((dummy_addr, code_hash).encode()).build()); - // code has of itself - assert_ok!(builder::call(addr).data((addr, self_code_hash).encode()).build()); - - // EOA doesn't exists - assert_err!( - builder::bare_call(addr) - .data((BOB_ADDR, crate::exec::EMPTY_CODE_HASH).encode()) - .build() - .result, - Error::::ContractTrapped - ); - // non-existing will return zero - assert_ok!(builder::call(addr).data((BOB_ADDR, H256::zero()).encode()).build()); + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); - // create EOA - let _ = ::Currency::set_balance( - &::AddressMapper::to_account_id(&BOB_ADDR), - 1_000_000, - ); + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + code.clone(), + deposit_limit::(), + ), + >::BasicBlockTooLarge + ); - // EOA returns empty code hash - assert_ok!(builder::call(addr) - .data((BOB_ADDR, crate::exec::EMPTY_CODE_HASH).encode()) - .build()); - }); - } + assert_err!( + builder::bare_instantiate(Code::Upload(code)).build().result, + >::BasicBlockTooLarge + ); + }); +} - #[test] - fn code_size_works() { - let (tester_code, _) = compile_module("extcodesize").unwrap(); - let tester_code_len = tester_code.len() as u64; +#[test] +fn origin_api_works() { + let (code, _) = compile_module("origin").unwrap(); - let (dummy_code, _) = compile_module("dummy").unwrap(); - let dummy_code_len = dummy_code.len() as u64; + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let Contract { addr: tester_addr, .. } = - builder::bare_instantiate(Code::Upload(tester_code)).build_and_unwrap_contract(); - let Contract { addr: dummy_addr, .. } = - builder::bare_instantiate(Code::Upload(dummy_code)).build_and_unwrap_contract(); + // Call the contract: Asserts the origin API to work as expected + assert_ok!(builder::call(addr).build()); + }); +} - // code size of another contract address - assert_ok!(builder::call(tester_addr) - .data((dummy_addr, dummy_code_len).encode()) - .build()); +#[test] +fn code_hash_works() { + let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap(); + let (dummy_code, code_hash) = compile_module("dummy").unwrap(); - // code size of own contract address - assert_ok!(builder::call(tester_addr) - .data((tester_addr, tester_code_len).encode()) - .build()); + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - // code size of non contract accounts - assert_ok!(builder::call(tester_addr).data(([8u8; 20], 0u64).encode()).build()); - }); - } + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code_hash_code)).build_and_unwrap_contract(); + let Contract { addr: dummy_addr, .. } = + builder::bare_instantiate(Code::Upload(dummy_code)).build_and_unwrap_contract(); - #[test] - fn origin_must_be_mapped() { - let (code, hash) = compile_module("dummy").unwrap(); + // code hash of dummy contract + assert_ok!(builder::call(addr).data((dummy_addr, code_hash).encode()).build()); + // code has of itself + assert_ok!(builder::call(addr).data((addr, self_code_hash).encode()).build()); - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - ::Currency::set_balance(&ALICE, 1_000_000); - ::Currency::set_balance(&EVE, 1_000_000); + // EOA doesn't exists + assert_err!( + builder::bare_call(addr) + .data((BOB_ADDR, crate::exec::EMPTY_CODE_HASH).encode()) + .build() + .result, + Error::::ContractTrapped + ); + // non-existing will return zero + assert_ok!(builder::call(addr).data((BOB_ADDR, H256::zero()).encode()).build()); - let eve = RuntimeOrigin::signed(EVE); + // create EOA + let _ = ::Currency::set_balance( + &::AddressMapper::to_account_id(&BOB_ADDR), + 1_000_000, + ); - // alice can instantiate as she doesn't need a mapping - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + // EOA returns empty code hash + assert_ok!(builder::call(addr) + .data((BOB_ADDR, crate::exec::EMPTY_CODE_HASH).encode()) + .build()); + }); +} - // without a mapping eve can neither call nor instantiate - assert_err!( - builder::bare_call(addr).origin(eve.clone()).build().result, - >::AccountUnmapped - ); - assert_err!( - builder::bare_instantiate(Code::Existing(hash)) - .origin(eve.clone()) - .build() - .result, - >::AccountUnmapped - ); +#[test] +fn code_size_works() { + let (tester_code, _) = compile_module("extcodesize").unwrap(); + let tester_code_len = tester_code.len() as u64; - // after mapping eve is usable as an origin - >::map_account(eve.clone()).unwrap(); - assert_ok!(builder::bare_call(addr).origin(eve.clone()).build().result); - assert_ok!(builder::bare_instantiate(Code::Existing(hash)).origin(eve).build().result); - }); - } + let (dummy_code, _) = compile_module("dummy").unwrap(); + let dummy_code_len = dummy_code.len() as u64; - #[test] - fn mapped_address_works() { - let (code, _) = compile_module("terminate_and_send_to_eve").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - ::Currency::set_balance(&ALICE, 1_000_000); - - // without a mapping everything will be send to the fallback account - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); - assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 0); - builder::bare_call(addr).build_and_unwrap_result(); - assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 100); - - // after mapping it will be sent to the real eve account - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - // need some balance to pay for the map deposit - ::Currency::set_balance(&EVE, 1_000); - >::map_account(RuntimeOrigin::signed(EVE)).unwrap(); - builder::bare_call(addr).build_and_unwrap_result(); - assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 100); - assert_eq!(::Currency::total_balance(&EVE), 1_100); - }); - } + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); - #[test] - fn block_hash_works() { - let (code, _) = compile_module("block_hash").unwrap(); + let Contract { addr: tester_addr, .. } = + builder::bare_instantiate(Code::Upload(tester_code)).build_and_unwrap_contract(); + let Contract { addr: dummy_addr, .. } = + builder::bare_instantiate(Code::Upload(dummy_code)).build_and_unwrap_contract(); - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); + // code size of another contract address + assert_ok!(builder::call(tester_addr).data((dummy_addr, dummy_code_len).encode()).build()); - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + // code size of own contract address + assert_ok!(builder::call(tester_addr) + .data((tester_addr, tester_code_len).encode()) + .build()); - // The genesis config sets to the block number to 1 - let block_hash = [1; 32]; - frame_system::BlockHash::::insert( - &crate::BlockNumberFor::::from(0u32), - ::Hash::from(&block_hash), - ); - assert_ok!(builder::call(addr) - .data((U256::zero(), H256::from(block_hash)).encode()) - .build()); + // code size of non contract accounts + assert_ok!(builder::call(tester_addr).data(([8u8; 20], 0u64).encode()).build()); + }); +} - // A block number out of range returns the zero value - assert_ok!(builder::call(addr).data((U256::from(1), H256::zero()).encode()).build()); - }); - } +#[test] +fn origin_must_be_mapped() { + let (code, hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + ::Currency::set_balance(&ALICE, 1_000_000); + ::Currency::set_balance(&EVE, 1_000_000); + + let eve = RuntimeOrigin::signed(EVE); + + // alice can instantiate as she doesn't need a mapping + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // without a mapping eve can neither call nor instantiate + assert_err!( + builder::bare_call(addr).origin(eve.clone()).build().result, + >::AccountUnmapped + ); + assert_err!( + builder::bare_instantiate(Code::Existing(hash)) + .origin(eve.clone()) + .build() + .result, + >::AccountUnmapped + ); + + // after mapping eve is usable as an origin + >::map_account(eve.clone()).unwrap(); + assert_ok!(builder::bare_call(addr).origin(eve.clone()).build().result); + assert_ok!(builder::bare_instantiate(Code::Existing(hash)).origin(eve).build().result); + }); +} + +#[test] +fn mapped_address_works() { + let (code, _) = compile_module("terminate_and_send_to_eve").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + ::Currency::set_balance(&ALICE, 1_000_000); + + // without a mapping everything will be send to the fallback account + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); + assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 0); + builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 100); + + // after mapping it will be sent to the real eve account + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + // need some balance to pay for the map deposit + ::Currency::set_balance(&EVE, 1_000); + >::map_account(RuntimeOrigin::signed(EVE)).unwrap(); + builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 100); + assert_eq!(::Currency::total_balance(&EVE), 1_100); + }); } diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs index 1e94d5cafb81..7c4fbba71f65 100644 --- a/substrate/frame/revive/src/tests/test_debug.rs +++ b/substrate/frame/revive/src/tests/test_debug.rs @@ -23,6 +23,7 @@ use crate::{ test_utils::*, }; use frame_support::traits::Currency; +use pretty_assertions::assert_eq; use sp_core::H160; use std::cell::RefCell; @@ -99,146 +100,139 @@ impl CallSpan for TestCallSpan { } } -/// 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(); - #[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 current_stack() -> Vec { + DEBUG_EXECUTION_TRACE.with(|stack| stack.borrow().clone()) + } - fn deploy(wasm: Vec) -> H160 { - Contracts::bare_instantiate( - RuntimeOrigin::signed(ALICE), - 0, - GAS_LIMIT, - deposit_limit::(), - Code::Upload(wasm), - vec![], - Some([0u8; 32]), - DebugInfo::Skip, - CollectEvents::Skip, - ) - .result - .unwrap() - .addr - } + fn deploy(wasm: Vec) -> H160 { + Contracts::bare_instantiate( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + deposit_limit::(), + Code::Upload(wasm), + vec![], + Some([0u8; 32]), + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .addr + } - fn constructor_frame(contract_address: &H160, after: bool) -> DebugFrame { - DebugFrame { - contract_address: *contract_address, - call: ExportedFunction::Constructor, - input: vec![], - result: if after { Some(vec![]) } else { None }, - } + fn constructor_frame(contract_address: &H160, after: bool) -> DebugFrame { + DebugFrame { + contract_address: *contract_address, + call: ExportedFunction::Constructor, + input: vec![], + result: if after { Some(vec![]) } else { None }, } + } - fn call_frame(contract_address: &H160, args: Vec, after: bool) -> DebugFrame { - DebugFrame { - contract_address: *contract_address, - call: ExportedFunction::Call, - input: args, - result: if after { Some(vec![]) } else { None }, - } + fn call_frame(contract_address: &H160, args: Vec, after: bool) -> DebugFrame { + DebugFrame { + contract_address: *contract_address, + call: ExportedFunction::Call, + input: args, + result: if after { Some(vec![]) } else { None }, } - - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = Balances::deposit_creating(&ALICE, 1_000_000); - - assert_eq!(current_stack(), vec![]); - - let addr_caller = deploy(wasm_caller); - let addr_callee = deploy(wasm_callee); - - assert_eq!( - current_stack(), - vec![ - constructor_frame(&addr_caller, false), - constructor_frame(&addr_caller, true), - constructor_frame(&addr_callee, false), - constructor_frame(&addr_callee, true), - ] - ); - - let main_args = (100u32, &addr_callee.clone()).encode(); - let inner_args = (100u32).encode(); - - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr_caller, - 0, - GAS_LIMIT, - deposit_limit::(), - 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); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + assert_eq!(current_stack(), vec![]); + + let addr_caller = deploy(wasm_caller); + let addr_callee = deploy(wasm_callee); + + assert_eq!( + current_stack(), + vec![ + constructor_frame(&addr_caller, false), + constructor_frame(&addr_caller, true), + constructor_frame(&addr_callee, false), + constructor_frame(&addr_callee, true), + ] + ); + + let main_args = (100u32, &addr_callee.clone()).encode(); + let inner_args = (100u32).encode(); + + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr_caller, + 0, + GAS_LIMIT, + deposit_limit::(), + 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), + ] + ); + }); +} - 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 - Some([0x41; 32]), - DebugInfo::Skip, - CollectEvents::Skip, - ) - .result - .unwrap() - .addr; - - // no interception yet - assert_ok!(Contracts::call( +#[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 + Some([0x41; 32]), + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .addr; + + // no interception yet + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + account_id, + 0, + GAS_LIMIT, + deposit_limit::(), + vec![], + )); + + // intercept calls to this contract + INTERCEPTED_ADDRESS.with(|i| *i.borrow_mut() = Some(account_id)); + + assert_err_ignore_postinfo!( + Contracts::call( RuntimeOrigin::signed(ALICE), account_id, 0, GAS_LIMIT, deposit_limit::(), vec![], - )); - - // intercept calls to this contract - INTERCEPTED_ADDRESS.with(|i| *i.borrow_mut() = Some(account_id)); - - assert_err_ignore_postinfo!( - Contracts::call( - RuntimeOrigin::signed(ALICE), - account_id, - 0, - GAS_LIMIT, - deposit_limit::(), - vec![], - ), - >::ContractReverted, - ); - }); - } + ), + >::ContractReverted, + ); + }); } diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index 6779f551113c..f10c4f5fddf8 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -26,7 +26,7 @@ pub use crate::wasm::runtime::SyscallDoc; #[cfg(test)] pub use runtime::HIGHEST_API_VERSION; -#[cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] +#[cfg(feature = "runtime-benchmarks")] pub use crate::wasm::runtime::{ReturnData, TrapReason}; pub use crate::wasm::runtime::{ApiVersion, Memory, Runtime, RuntimeCosts}; diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 28d6a2c3fb01..7f50658c4e16 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -610,12 +610,6 @@ tuples-96 = [ "frame-support-procedural?/tuples-96", "frame-support?/tuples-96", ] -riscv = [ - "pallet-revive-eth-rpc?/riscv", - "pallet-revive-fixtures?/riscv", - "pallet-revive-mock-network?/riscv", - "pallet-revive?/riscv", -] [package.edition] workspace = true From 2700dbf2dda8b7f593447c939e1a26dacdb8ce45 Mon Sep 17 00:00:00 2001 From: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:22:12 +0200 Subject: [PATCH 2/6] `candidate-validation`: RFC103 implementation (#5847) Part of /~https://github.com/paritytech/polkadot-sdk/issues/5047 On top of /~https://github.com/paritytech/polkadot-sdk/pull/5679 --------- Signed-off-by: Andrei Sandu Co-authored-by: GitHub Action --- Cargo.lock | 1 + .../node/core/candidate-validation/Cargo.toml | 2 + .../node/core/candidate-validation/src/lib.rs | 166 +++++-- .../core/candidate-validation/src/tests.rs | 407 +++++++++++++++++- polkadot/node/primitives/src/lib.rs | 4 + polkadot/primitives/src/vstaging/mod.rs | 12 + prdoc/pr_5847.prdoc | 19 + 7 files changed, 571 insertions(+), 40 deletions(-) create mode 100644 prdoc/pr_5847.prdoc diff --git a/Cargo.lock b/Cargo.lock index a6c2bc1fd318..1f171ad756c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14485,6 +14485,7 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-primitives-test-helpers", + "rstest", "sp-application-crypto 30.0.0", "sp-core 28.0.0", "sp-keyring", diff --git a/polkadot/node/core/candidate-validation/Cargo.toml b/polkadot/node/core/candidate-validation/Cargo.toml index fcacc38cae65..87855dbce415 100644 --- a/polkadot/node/core/candidate-validation/Cargo.toml +++ b/polkadot/node/core/candidate-validation/Cargo.toml @@ -38,3 +38,5 @@ polkadot-node-subsystem-test-helpers = { workspace = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } polkadot-primitives-test-helpers = { workspace = true } +rstest = { workspace = true } +polkadot-primitives = { workspace = true, features = ["test"] } diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs index a48669c24825..1e732e2f1f03 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -37,7 +37,7 @@ use polkadot_node_subsystem::{ overseer, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, SubsystemResult, SubsystemSender, }; -use polkadot_node_subsystem_util as util; +use polkadot_node_subsystem_util::{self as util, runtime::ClaimQueueSnapshot}; use polkadot_overseer::ActiveLeavesUpdate; use polkadot_parachain_primitives::primitives::ValidationResult as WasmValidationResult; use polkadot_primitives::{ @@ -46,8 +46,9 @@ use polkadot_primitives::{ DEFAULT_LENIENT_PREPARATION_TIMEOUT, DEFAULT_PRECHECK_PREPARATION_TIMEOUT, }, vstaging::{ - CandidateDescriptorV2 as CandidateDescriptor, CandidateEvent, + transpose_claim_queue, CandidateDescriptorV2 as CandidateDescriptor, CandidateEvent, CandidateReceiptV2 as CandidateReceipt, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, }, AuthorityDiscoveryId, CandidateCommitments, ExecutorParams, Hash, PersistedValidationData, PvfExecKind as RuntimePvfExecKind, PvfPrepKind, SessionIndex, ValidationCode, @@ -148,6 +149,25 @@ impl CandidateValidationSubsystem { } } +// Returns the claim queue at relay parent and logs a warning if it is not available. +async fn claim_queue(relay_parent: Hash, sender: &mut Sender) -> Option +where + Sender: SubsystemSender, +{ + match util::runtime::fetch_claim_queue(sender, relay_parent).await { + Ok(maybe_cq) => maybe_cq, + Err(err) => { + gum::warn!( + target: LOG_TARGET, + ?relay_parent, + ?err, + "Claim queue not available" + ); + None + }, + } +} + fn handle_validation_message( mut sender: S, validation_host: ValidationHost, @@ -167,24 +187,40 @@ where exec_kind, response_sender, .. - } => async move { - let _timer = metrics.time_validate_from_exhaustive(); - let res = validate_candidate_exhaustive( - validation_host, - validation_data, - validation_code, - candidate_receipt, - pov, - executor_params, - exec_kind, - &metrics, - ) - .await; + } => + async move { + let _timer = metrics.time_validate_from_exhaustive(); + let relay_parent = candidate_receipt.descriptor.relay_parent(); + + let maybe_claim_queue = claim_queue(relay_parent, &mut sender).await; + + let maybe_expected_session_index = + match util::request_session_index_for_child(relay_parent, &mut sender) + .await + .await + { + Ok(Ok(expected_session_index)) => Some(expected_session_index), + _ => None, + }; + + let res = validate_candidate_exhaustive( + maybe_expected_session_index, + validation_host, + validation_data, + validation_code, + candidate_receipt, + pov, + executor_params, + exec_kind, + &metrics, + maybe_claim_queue, + ) + .await; - metrics.on_validation_event(&res); - let _ = response_sender.send(res); - } - .boxed(), + metrics.on_validation_event(&res); + let _ = response_sender.send(res); + } + .boxed(), CandidateValidationMessage::PreCheck { relay_parent, validation_code_hash, @@ -637,6 +673,7 @@ where } async fn validate_candidate_exhaustive( + maybe_expected_session_index: Option, mut validation_backend: impl ValidationBackend + Send, persisted_validation_data: PersistedValidationData, validation_code: ValidationCode, @@ -645,11 +682,13 @@ async fn validate_candidate_exhaustive( executor_params: ExecutorParams, exec_kind: PvfExecKind, metrics: &Metrics, + maybe_claim_queue: Option, ) -> Result { let _timer = metrics.time_validate_candidate_exhaustive(); - let validation_code_hash = validation_code.hash(); + let relay_parent = candidate_receipt.descriptor.relay_parent(); let para_id = candidate_receipt.descriptor.para_id(); + gum::debug!( target: LOG_TARGET, ?validation_code_hash, @@ -657,6 +696,27 @@ async fn validate_candidate_exhaustive( "About to validate a candidate.", ); + // We only check the session index for backing. + match (exec_kind, candidate_receipt.descriptor.session_index()) { + (PvfExecKind::Backing | PvfExecKind::BackingSystemParas, Some(session_index)) => { + let Some(expected_session_index) = maybe_expected_session_index else { + let error = "cannot fetch session index from the runtime"; + gum::warn!( + target: LOG_TARGET, + ?relay_parent, + error, + ); + + return Err(ValidationFailed(error.into())) + }; + + if session_index != expected_session_index { + return Ok(ValidationResult::Invalid(InvalidCandidate::InvalidSessionIndex)) + } + }, + (_, _) => {}, + }; + if let Err(e) = perform_basic_checks( &candidate_receipt.descriptor, persisted_validation_data.max_pov_size, @@ -754,15 +814,21 @@ async fn validate_candidate_exhaustive( gum::info!(target: LOG_TARGET, ?para_id, "Invalid candidate (para_head)"); Ok(ValidationResult::Invalid(InvalidCandidate::ParaHeadHashMismatch)) } else { - let outputs = CandidateCommitments { - head_data: res.head_data, - upward_messages: res.upward_messages, - horizontal_messages: res.horizontal_messages, - new_validation_code: res.new_validation_code, - processed_downward_messages: res.processed_downward_messages, - hrmp_watermark: res.hrmp_watermark, + let committed_candidate_receipt = CommittedCandidateReceipt { + descriptor: candidate_receipt.descriptor.clone(), + commitments: CandidateCommitments { + head_data: res.head_data, + upward_messages: res.upward_messages, + horizontal_messages: res.horizontal_messages, + new_validation_code: res.new_validation_code, + processed_downward_messages: res.processed_downward_messages, + hrmp_watermark: res.hrmp_watermark, + }, }; - if candidate_receipt.commitments_hash != outputs.hash() { + + if candidate_receipt.commitments_hash != + committed_candidate_receipt.commitments.hash() + { gum::info!( target: LOG_TARGET, ?para_id, @@ -773,7 +839,48 @@ async fn validate_candidate_exhaustive( // invalid. Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch)) } else { - Ok(ValidationResult::Valid(outputs, (*persisted_validation_data).clone())) + let core_index = candidate_receipt.descriptor.core_index(); + + match (core_index, exec_kind) { + // Core selectors are optional for V2 descriptors, but we still check the + // descriptor core index. + ( + Some(_core_index), + PvfExecKind::Backing | PvfExecKind::BackingSystemParas, + ) => { + let Some(claim_queue) = maybe_claim_queue else { + let error = "cannot fetch the claim queue from the runtime"; + gum::warn!( + target: LOG_TARGET, + ?relay_parent, + error + ); + + return Err(ValidationFailed(error.into())) + }; + + if let Err(err) = committed_candidate_receipt + .check_core_index(&transpose_claim_queue(claim_queue.0)) + { + gum::warn!( + target: LOG_TARGET, + ?err, + candidate_hash = ?candidate_receipt.hash(), + "Candidate core index is invalid", + ); + return Ok(ValidationResult::Invalid( + InvalidCandidate::InvalidCoreIndex, + )) + } + }, + // No checks for approvals and disputes + (_, _) => {}, + } + + Ok(ValidationResult::Valid( + committed_candidate_receipt.commitments, + (*persisted_validation_data).clone(), + )) } }, } @@ -1003,6 +1110,7 @@ fn perform_basic_checks( return Err(InvalidCandidate::CodeHashMismatch) } + // No-op for `v2` receipts. if let Err(()) = candidate.check_collator_signature() { return Err(InvalidCandidate::BadSignature) } diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs index 997a347631a0..391247858ed6 100644 --- a/polkadot/node/core/candidate-validation/src/tests.rs +++ b/polkadot/node/core/candidate-validation/src/tests.rs @@ -14,7 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{ + collections::BTreeMap, + sync::atomic::{AtomicUsize, Ordering}, +}; use super::*; use crate::PvfExecKind; @@ -26,12 +29,18 @@ use polkadot_node_subsystem::messages::AllMessages; use polkadot_node_subsystem_util::reexports::SubsystemContext; use polkadot_overseer::ActivatedLeaf; use polkadot_primitives::{ - vstaging::CandidateDescriptorV2, CandidateDescriptor, CoreIndex, GroupIndex, HeadData, - Id as ParaId, OccupiedCoreAssumption, SessionInfo, UpwardMessage, ValidatorId, + vstaging::{ + CandidateDescriptorV2, ClaimQueueOffset, CoreSelector, MutateDescriptorV2, UMPSignal, + UMP_SEPARATOR, + }, + CandidateDescriptor, CoreIndex, GroupIndex, HeadData, Id as ParaId, OccupiedCoreAssumption, + SessionInfo, UpwardMessage, ValidatorId, }; use polkadot_primitives_test_helpers::{ dummy_collator, dummy_collator_signature, dummy_hash, make_valid_candidate_descriptor, + make_valid_candidate_descriptor_v2, }; +use rstest::rstest; use sp_core::{sr25519::Public, testing::TaskExecutor}; use sp_keyring::Sr25519Keyring; use sp_keystore::{testing::MemoryKeystore, Keystore}; @@ -467,25 +476,24 @@ impl ValidationBackend for MockValidateCandidateBackend { } #[test] -fn candidate_validation_ok_is_ok() { +fn session_index_checked_only_in_backing() { let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; let pov = PoV { block_data: BlockData(vec![1; 32]) }; let head_data = HeadData(vec![1, 1, 1]); let validation_code = ValidationCode(vec![2; 16]); - let descriptor = make_valid_candidate_descriptor( + let descriptor = make_valid_candidate_descriptor_v2( ParaId::from(1_u32), dummy_hash(), - validation_data.hash(), + CoreIndex(0), + 100, + dummy_hash(), pov.hash(), validation_code.hash(), head_data.hash(), dummy_hash(), - Sr25519Keyring::Alice, - ) - .into(); - + ); let check = perform_basic_checks( &descriptor, validation_data.max_pov_size, @@ -514,15 +522,59 @@ fn candidate_validation_ok_is_ok() { let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: commitments.hash() }; + // The session index is invalid + let v = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + PvfExecKind::Backing, + &Default::default(), + Default::default(), + )) + .unwrap(); + + assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::InvalidSessionIndex)); + + // Approval doesn't fail since the check is ommited. let v = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + PvfExecKind::Approval, + &Default::default(), + Default::default(), + )) + .unwrap(); + + assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { + assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); + assert_eq!(outputs.upward_messages, Vec::::new()); + assert_eq!(outputs.horizontal_messages, Vec::new()); + assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); + assert_eq!(outputs.hrmp_watermark, 0); + assert_eq!(used_validation_data, validation_data); + }); + + // Approval doesn't fail since the check is ommited. + let v = executor::block_on(validate_candidate_exhaustive( + Some(1), MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result)), validation_data.clone(), validation_code, candidate_receipt, Arc::new(pov), ExecutorParams::default(), - PvfExecKind::Backing, + PvfExecKind::Dispute, &Default::default(), + Default::default(), )) .unwrap(); @@ -536,6 +588,323 @@ fn candidate_validation_ok_is_ok() { }); } +#[rstest] +#[case(true)] +#[case(false)] +fn candidate_validation_ok_is_ok(#[case] v2_descriptor: bool) { + let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; + + let pov = PoV { block_data: BlockData(vec![1; 32]) }; + let head_data = HeadData(vec![1, 1, 1]); + let validation_code = ValidationCode(vec![2; 16]); + + let descriptor = if v2_descriptor { + make_valid_candidate_descriptor_v2( + ParaId::from(1_u32), + dummy_hash(), + CoreIndex(1), + 1, + dummy_hash(), + pov.hash(), + validation_code.hash(), + head_data.hash(), + dummy_hash(), + ) + } else { + make_valid_candidate_descriptor( + ParaId::from(1_u32), + dummy_hash(), + validation_data.hash(), + pov.hash(), + validation_code.hash(), + head_data.hash(), + dummy_hash(), + Sr25519Keyring::Alice, + ) + .into() + }; + + let check = perform_basic_checks( + &descriptor, + validation_data.max_pov_size, + &pov, + &validation_code.hash(), + ); + assert!(check.is_ok()); + + let mut validation_result = WasmValidationResult { + head_data, + new_validation_code: Some(vec![2, 2, 2].into()), + upward_messages: Default::default(), + horizontal_messages: Default::default(), + processed_downward_messages: 0, + hrmp_watermark: 0, + }; + + if v2_descriptor { + validation_result.upward_messages.force_push(UMP_SEPARATOR); + validation_result + .upward_messages + .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode()); + } + + let commitments = CandidateCommitments { + head_data: validation_result.head_data.clone(), + upward_messages: validation_result.upward_messages.clone(), + horizontal_messages: validation_result.horizontal_messages.clone(), + new_validation_code: validation_result.new_validation_code.clone(), + processed_downward_messages: validation_result.processed_downward_messages, + hrmp_watermark: validation_result.hrmp_watermark, + }; + + let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: commitments.hash() }; + let mut cq = BTreeMap::new(); + let _ = cq.insert(CoreIndex(0), vec![1.into(), 2.into()].into()); + let _ = cq.insert(CoreIndex(1), vec![1.into(), 1.into()].into()); + + let v = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result)), + validation_data.clone(), + validation_code, + candidate_receipt, + Arc::new(pov), + ExecutorParams::default(), + PvfExecKind::Backing, + &Default::default(), + Some(ClaimQueueSnapshot(cq)), + )) + .unwrap(); + + assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { + assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); + assert_eq!(outputs.upward_messages, commitments.upward_messages); + assert_eq!(outputs.horizontal_messages, Vec::new()); + assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); + assert_eq!(outputs.hrmp_watermark, 0); + assert_eq!(used_validation_data, validation_data); + }); +} + +#[test] +fn invalid_session_or_core_index() { + let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; + + let pov = PoV { block_data: BlockData(vec![1; 32]) }; + let head_data = HeadData(vec![1, 1, 1]); + let validation_code = ValidationCode(vec![2; 16]); + + let descriptor = make_valid_candidate_descriptor_v2( + ParaId::from(1_u32), + dummy_hash(), + CoreIndex(1), + 100, + dummy_hash(), + pov.hash(), + validation_code.hash(), + head_data.hash(), + dummy_hash(), + ); + + let check = perform_basic_checks( + &descriptor, + validation_data.max_pov_size, + &pov, + &validation_code.hash(), + ); + assert!(check.is_ok()); + + let mut validation_result = WasmValidationResult { + head_data, + new_validation_code: Some(vec![2, 2, 2].into()), + upward_messages: Default::default(), + horizontal_messages: Default::default(), + processed_downward_messages: 0, + hrmp_watermark: 0, + }; + + validation_result.upward_messages.force_push(UMP_SEPARATOR); + validation_result + .upward_messages + .force_push(UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(0)).encode()); + + let commitments = CandidateCommitments { + head_data: validation_result.head_data.clone(), + upward_messages: validation_result.upward_messages.clone(), + horizontal_messages: validation_result.horizontal_messages.clone(), + new_validation_code: validation_result.new_validation_code.clone(), + processed_downward_messages: validation_result.processed_downward_messages, + hrmp_watermark: validation_result.hrmp_watermark, + }; + + let mut candidate_receipt = + CandidateReceipt { descriptor, commitments_hash: commitments.hash() }; + + let err = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + PvfExecKind::Backing, + &Default::default(), + Default::default(), + )) + .unwrap(); + + assert_matches!(err, ValidationResult::Invalid(InvalidCandidate::InvalidSessionIndex)); + + let err = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + PvfExecKind::BackingSystemParas, + &Default::default(), + Default::default(), + )) + .unwrap(); + + assert_matches!(err, ValidationResult::Invalid(InvalidCandidate::InvalidSessionIndex)); + + candidate_receipt.descriptor.set_session_index(1); + + let result = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + PvfExecKind::Backing, + &Default::default(), + Some(Default::default()), + )) + .unwrap(); + assert_matches!(result, ValidationResult::Invalid(InvalidCandidate::InvalidCoreIndex)); + + let result = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + PvfExecKind::BackingSystemParas, + &Default::default(), + Some(Default::default()), + )) + .unwrap(); + assert_matches!(result, ValidationResult::Invalid(InvalidCandidate::InvalidCoreIndex)); + + let v = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + PvfExecKind::Approval, + &Default::default(), + Default::default(), + )) + .unwrap(); + + // Validation doesn't fail for approvals, core/session index is not checked. + assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { + assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); + assert_eq!(outputs.upward_messages, commitments.upward_messages); + assert_eq!(outputs.horizontal_messages, Vec::new()); + assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); + assert_eq!(outputs.hrmp_watermark, 0); + assert_eq!(used_validation_data, validation_data); + }); + + // Dispute check passes because we don't check core or session index + let v = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + PvfExecKind::Dispute, + &Default::default(), + Default::default(), + )) + .unwrap(); + + // Validation doesn't fail for approvals, core/session index is not checked. + assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { + assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); + assert_eq!(outputs.upward_messages, commitments.upward_messages); + assert_eq!(outputs.horizontal_messages, Vec::new()); + assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); + assert_eq!(outputs.hrmp_watermark, 0); + assert_eq!(used_validation_data, validation_data); + }); + + // Populate claim queue. + let mut cq = BTreeMap::new(); + let _ = cq.insert(CoreIndex(0), vec![1.into(), 2.into()].into()); + let _ = cq.insert(CoreIndex(1), vec![1.into(), 2.into()].into()); + + let v = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + PvfExecKind::Backing, + &Default::default(), + Some(ClaimQueueSnapshot(cq.clone())), + )) + .unwrap(); + + assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { + assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); + assert_eq!(outputs.upward_messages, commitments.upward_messages); + assert_eq!(outputs.horizontal_messages, Vec::new()); + assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); + assert_eq!(outputs.hrmp_watermark, 0); + assert_eq!(used_validation_data, validation_data); + }); + + let v = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + PvfExecKind::BackingSystemParas, + &Default::default(), + Some(ClaimQueueSnapshot(cq)), + )) + .unwrap(); + + assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { + assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); + assert_eq!(outputs.upward_messages, commitments.upward_messages); + assert_eq!(outputs.horizontal_messages, Vec::new()); + assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); + assert_eq!(outputs.hrmp_watermark, 0); + assert_eq!(used_validation_data, validation_data); + }); +} + #[test] fn candidate_validation_bad_return_is_invalid() { let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; @@ -566,6 +935,7 @@ fn candidate_validation_bad_return_is_invalid() { let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() }; let v = executor::block_on(validate_candidate_exhaustive( + Some(1), MockValidateCandidateBackend::with_hardcoded_result(Err(ValidationError::Invalid( WasmInvalidCandidate::HardTimeout, ))), @@ -576,6 +946,7 @@ fn candidate_validation_bad_return_is_invalid() { ExecutorParams::default(), PvfExecKind::Backing, &Default::default(), + Default::default(), )) .unwrap(); @@ -647,6 +1018,7 @@ fn candidate_validation_one_ambiguous_error_is_valid() { let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: commitments.hash() }; let v = executor::block_on(validate_candidate_exhaustive( + Some(1), MockValidateCandidateBackend::with_hardcoded_result_list(vec![ Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)), Ok(validation_result), @@ -658,6 +1030,7 @@ fn candidate_validation_one_ambiguous_error_is_valid() { ExecutorParams::default(), PvfExecKind::Approval, &Default::default(), + Default::default(), )) .unwrap(); @@ -688,6 +1061,7 @@ fn candidate_validation_multiple_ambiguous_errors_is_invalid() { let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() }; let v = executor::block_on(validate_candidate_exhaustive( + Some(1), MockValidateCandidateBackend::with_hardcoded_result_list(vec![ Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)), Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)), @@ -699,6 +1073,7 @@ fn candidate_validation_multiple_ambiguous_errors_is_invalid() { ExecutorParams::default(), PvfExecKind::Approval, &Default::default(), + Default::default(), )) .unwrap(); @@ -806,6 +1181,7 @@ fn candidate_validation_retry_on_error_helper( let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() }; return executor::block_on(validate_candidate_exhaustive( + Some(1), MockValidateCandidateBackend::with_hardcoded_result_list(mock_errors), validation_data, validation_code, @@ -814,6 +1190,7 @@ fn candidate_validation_retry_on_error_helper( ExecutorParams::default(), exec_kind, &Default::default(), + Default::default(), )) } @@ -847,6 +1224,7 @@ fn candidate_validation_timeout_is_internal_error() { let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() }; let v = executor::block_on(validate_candidate_exhaustive( + Some(1), MockValidateCandidateBackend::with_hardcoded_result(Err(ValidationError::Invalid( WasmInvalidCandidate::HardTimeout, ))), @@ -857,6 +1235,7 @@ fn candidate_validation_timeout_is_internal_error() { ExecutorParams::default(), PvfExecKind::Backing, &Default::default(), + Default::default(), )); assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::Timeout))); @@ -896,6 +1275,7 @@ fn candidate_validation_commitment_hash_mismatch_is_invalid() { }; let result = executor::block_on(validate_candidate_exhaustive( + Some(1), MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result)), validation_data, validation_code, @@ -904,6 +1284,7 @@ fn candidate_validation_commitment_hash_mismatch_is_invalid() { ExecutorParams::default(), PvfExecKind::Backing, &Default::default(), + Default::default(), )) .unwrap(); @@ -947,6 +1328,7 @@ fn candidate_validation_code_mismatch_is_invalid() { >(pool.clone()); let v = executor::block_on(validate_candidate_exhaustive( + Some(1), MockValidateCandidateBackend::with_hardcoded_result(Err(ValidationError::Invalid( WasmInvalidCandidate::HardTimeout, ))), @@ -957,6 +1339,7 @@ fn candidate_validation_code_mismatch_is_invalid() { ExecutorParams::default(), PvfExecKind::Backing, &Default::default(), + Default::default(), )) .unwrap(); @@ -1007,6 +1390,7 @@ fn compressed_code_works() { let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: commitments.hash() }; let v = executor::block_on(validate_candidate_exhaustive( + Some(1), MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result)), validation_data, validation_code, @@ -1015,6 +1399,7 @@ fn compressed_code_works() { ExecutorParams::default(), PvfExecKind::Backing, &Default::default(), + Default::default(), )); assert_matches!(v, Ok(ValidationResult::Valid(_, _))); diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index e2e7aa92b11c..6985e86098b0 100644 --- a/polkadot/node/primitives/src/lib.rs +++ b/polkadot/node/primitives/src/lib.rs @@ -348,6 +348,10 @@ pub enum InvalidCandidate { CodeHashMismatch, /// Validation has generated different candidate commitments. CommitmentsHashMismatch, + /// The candidate receipt contains an invalid session index. + InvalidSessionIndex, + /// The candidate receipt contains an invalid core index. + InvalidCoreIndex, } /// Result of the validation of the candidate. diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index 265fcd899d74..21aab41902be 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -208,6 +208,10 @@ pub trait MutateDescriptorV2 { fn set_erasure_root(&mut self, erasure_root: Hash); /// Set the para head of the descriptor. fn set_para_head(&mut self, para_head: Hash); + /// Set the core index of the descriptor. + fn set_core_index(&mut self, core_index: CoreIndex); + /// Set the session index of the descriptor. + fn set_session_index(&mut self, session_index: SessionIndex); } #[cfg(feature = "test")] @@ -228,6 +232,14 @@ impl MutateDescriptorV2 for CandidateDescriptorV2 { self.version = version; } + fn set_core_index(&mut self, core_index: CoreIndex) { + self.core_index = core_index.0 as u16; + } + + fn set_session_index(&mut self, session_index: SessionIndex) { + self.session_index = session_index; + } + fn set_persisted_validation_data_hash(&mut self, persisted_validation_data_hash: Hash) { self.persisted_validation_data_hash = persisted_validation_data_hash; } diff --git a/prdoc/pr_5847.prdoc b/prdoc/pr_5847.prdoc new file mode 100644 index 000000000000..fdbf6423da60 --- /dev/null +++ b/prdoc/pr_5847.prdoc @@ -0,0 +1,19 @@ +title: '`candidate-validation`: RFC103 implementation' +doc: +- audience: Node Dev + description: | + Introduces support for new v2 descriptor `core_index` and `session_index` fields. + The subsystem will check the values of the new fields only during backing validations. +crates: +- name: polkadot-node-primitives + bump: major +- name: polkadot-primitives + bump: major +- name: cumulus-relay-chain-inprocess-interface + bump: minor +- name: cumulus-relay-chain-interface + bump: minor +- name: cumulus-client-consensus-aura + bump: minor +- name: polkadot-node-core-candidate-validation + bump: major From fa52407856c11ee138a32f2ba744f774fac984d5 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Fri, 1 Nov 2024 06:30:26 -0700 Subject: [PATCH 3/6] Update Treasury to Support Relay Chain Block Number Provider (#3970) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The goal of this PR is to have the treasury pallet work on a parachain which does not produce blocks on a regular schedule, thus can use the relay chain as a block provider. Because blocks are not produced regularly, we cannot make the assumption that block number increases monotonically, and thus have new logic to handle multiple spend periods passing between blocks. --------- Co-authored-by: Bastian Köcher Co-authored-by: Muharem --- .../collectives-westend/src/fellowship/mod.rs | 1 + polkadot/runtime/common/src/impls.rs | 1 + polkadot/runtime/rococo/src/lib.rs | 1 + polkadot/runtime/westend/src/lib.rs | 1 + prdoc/pr_3970.prdoc | 17 +++ substrate/bin/node/runtime/src/lib.rs | 1 + substrate/frame/bounties/src/benchmarking.rs | 29 ++-- substrate/frame/bounties/src/lib.rs | 26 ++-- substrate/frame/bounties/src/tests.rs | 120 ++++++--------- .../frame/child-bounties/src/benchmarking.rs | 16 +- substrate/frame/child-bounties/src/lib.rs | 18 ++- substrate/frame/child-bounties/src/tests.rs | 139 +++++++----------- substrate/frame/tips/src/tests.rs | 2 + substrate/frame/treasury/src/lib.rs | 84 +++++++++-- substrate/frame/treasury/src/tests.rs | 66 +++++++-- 15 files changed, 302 insertions(+), 220 deletions(-) create mode 100644 prdoc/pr_3970.prdoc diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs index 942e0c294dd0..1e8212cf6ac2 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs @@ -333,4 +333,5 @@ impl pallet_treasury::Config for Runtime { sp_core::ConstU8<1>, ConstU32<1000>, >; + type BlockNumberProvider = crate::System; } diff --git a/polkadot/runtime/common/src/impls.rs b/polkadot/runtime/common/src/impls.rs index b6a93cf53685..2f79d223d3c0 100644 --- a/polkadot/runtime/common/src/impls.rs +++ b/polkadot/runtime/common/src/impls.rs @@ -366,6 +366,7 @@ mod tests { type Paymaster = PayFromAccount; type BalanceConverter = UnityAssetBalanceConversion; type PayoutPeriod = ConstU64<0>; + type BlockNumberProvider = System; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 5bcde612cf1b..e02a28353d29 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -543,6 +543,7 @@ impl pallet_treasury::Config for Runtime { AssetRate, >; type PayoutPeriod = PayoutSpendPeriod; + type BlockNumberProvider = System; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = polkadot_runtime_common::impls::benchmarks::TreasuryArguments; } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 970ef5318994..251d92b03bbb 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -823,6 +823,7 @@ impl pallet_treasury::Config for Runtime { AssetRate, >; type PayoutPeriod = PayoutSpendPeriod; + type BlockNumberProvider = System; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = polkadot_runtime_common::impls::benchmarks::TreasuryArguments; } diff --git a/prdoc/pr_3970.prdoc b/prdoc/pr_3970.prdoc new file mode 100644 index 000000000000..5c20e7444782 --- /dev/null +++ b/prdoc/pr_3970.prdoc @@ -0,0 +1,17 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Update Treasury to Support Block Number Provider + +doc: + - audience: Runtime Dev + description: | + The goal of this PR is to have the treasury pallet work on a parachain which does not produce blocks on a regular schedule, thus can use the relay chain as a block provider. Because blocks are not produced regularly, we cannot make the assumption that block number increases monotonically, and thus have new logic to handle multiple spend periods passing between blocks. To migrate existing treasury implementations, simply add `type BlockNumberProvider = System` to have the same behavior as before. + +crates: +- name: pallet-treasury + bump: major +- name: pallet-bounties + bump: minor +- name: pallet-child-bounties + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 12e8dc3e5077..7712d8ba9540 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1266,6 +1266,7 @@ impl pallet_treasury::Config for Runtime { type Paymaster = PayAssetFromAccount; type BalanceConverter = AssetRate; type PayoutPeriod = SpendPayoutPeriod; + type BlockNumberProvider = System; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } diff --git a/substrate/frame/bounties/src/benchmarking.rs b/substrate/frame/bounties/src/benchmarking.rs index de93ba5c4ce7..6fa60e6938b6 100644 --- a/substrate/frame/bounties/src/benchmarking.rs +++ b/substrate/frame/bounties/src/benchmarking.rs @@ -26,13 +26,17 @@ use frame_benchmarking::v1::{ account, benchmarks_instance_pallet, whitelisted_caller, BenchmarkError, }; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; -use sp_runtime::traits::Bounded; +use sp_runtime::traits::{BlockNumberProvider, Bounded}; use crate::Pallet as Bounties; use pallet_treasury::Pallet as Treasury; const SEED: u32 = 0; +fn set_block_number, I: 'static>(n: BlockNumberFor) { + >::BlockNumberProvider::set_block_number(n); +} + // Create bounties that are approved for use in `on_initialize`. fn create_approved_bounties, I: 'static>(n: u32) -> Result<(), BenchmarkError> { for i in 0..n { @@ -78,7 +82,8 @@ fn create_bounty, I: 'static>( let approve_origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; Bounties::::approve_bounty(approve_origin.clone(), bounty_id)?; - Treasury::::on_initialize(BlockNumberFor::::zero()); + set_block_number::(T::SpendPeriod::get()); + Treasury::::on_initialize(frame_system::Pallet::::block_number()); Bounties::::propose_curator(approve_origin, bounty_id, curator_lookup.clone(), fee)?; Bounties::::accept_curator(RawOrigin::Signed(curator).into(), bounty_id)?; Ok((curator_lookup, bounty_id)) @@ -116,16 +121,17 @@ benchmarks_instance_pallet! { let bounty_id = BountyCount::::get() - 1; let approve_origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; Bounties::::approve_bounty(approve_origin.clone(), bounty_id)?; - Treasury::::on_initialize(BlockNumberFor::::zero()); + set_block_number::(T::SpendPeriod::get()); + Treasury::::on_initialize(frame_system::Pallet::::block_number()); }: _(approve_origin, bounty_id, curator_lookup, fee) // Worst case when curator is inactive and any sender unassigns the curator. unassign_curator { setup_pot_account::(); let (curator_lookup, bounty_id) = create_bounty::()?; - Treasury::::on_initialize(BlockNumberFor::::zero()); + Treasury::::on_initialize(frame_system::Pallet::::block_number()); let bounty_id = BountyCount::::get() - 1; - frame_system::Pallet::::set_block_number(T::BountyUpdatePeriod::get() + 2u32.into()); + set_block_number::(T::SpendPeriod::get() + T::BountyUpdatePeriod::get() + 2u32.into()); let caller = whitelisted_caller(); }: _(RawOrigin::Signed(caller), bounty_id) @@ -137,14 +143,15 @@ benchmarks_instance_pallet! { let bounty_id = BountyCount::::get() - 1; let approve_origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; Bounties::::approve_bounty(approve_origin.clone(), bounty_id)?; - Treasury::::on_initialize(BlockNumberFor::::zero()); + set_block_number::(T::SpendPeriod::get()); + Treasury::::on_initialize(frame_system::Pallet::::block_number()); Bounties::::propose_curator(approve_origin, bounty_id, curator_lookup, fee)?; }: _(RawOrigin::Signed(curator), bounty_id) award_bounty { setup_pot_account::(); let (curator_lookup, bounty_id) = create_bounty::()?; - Treasury::::on_initialize(BlockNumberFor::::zero()); + Treasury::::on_initialize(frame_system::Pallet::::block_number()); let bounty_id = BountyCount::::get() - 1; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; @@ -155,7 +162,7 @@ benchmarks_instance_pallet! { claim_bounty { setup_pot_account::(); let (curator_lookup, bounty_id) = create_bounty::()?; - Treasury::::on_initialize(BlockNumberFor::::zero()); + Treasury::::on_initialize(frame_system::Pallet::::block_number()); let bounty_id = BountyCount::::get() - 1; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; @@ -164,7 +171,7 @@ benchmarks_instance_pallet! { let beneficiary = T::Lookup::unlookup(beneficiary_account.clone()); Bounties::::award_bounty(RawOrigin::Signed(curator.clone()).into(), bounty_id, beneficiary)?; - frame_system::Pallet::::set_block_number(T::BountyDepositPayoutDelay::get() + 1u32.into()); + set_block_number::(T::SpendPeriod::get() + T::BountyDepositPayoutDelay::get() + 1u32.into()); ensure!(T::Currency::free_balance(&beneficiary_account).is_zero(), "Beneficiary already has balance"); }: _(RawOrigin::Signed(curator), bounty_id) @@ -184,7 +191,7 @@ benchmarks_instance_pallet! { close_bounty_active { setup_pot_account::(); let (curator_lookup, bounty_id) = create_bounty::()?; - Treasury::::on_initialize(BlockNumberFor::::zero()); + Treasury::::on_initialize(frame_system::Pallet::::block_number()); let bounty_id = BountyCount::::get() - 1; let approve_origin = T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; @@ -196,7 +203,7 @@ benchmarks_instance_pallet! { extend_bounty_expiry { setup_pot_account::(); let (curator_lookup, bounty_id) = create_bounty::()?; - Treasury::::on_initialize(BlockNumberFor::::zero()); + Treasury::::on_initialize(frame_system::Pallet::::block_number()); let bounty_id = BountyCount::::get() - 1; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; diff --git a/substrate/frame/bounties/src/lib.rs b/substrate/frame/bounties/src/lib.rs index e30d6fa2d143..9d5931f4a608 100644 --- a/substrate/frame/bounties/src/lib.rs +++ b/substrate/frame/bounties/src/lib.rs @@ -96,7 +96,7 @@ use frame_support::traits::{ }; use sp_runtime::{ - traits::{AccountIdConversion, BadOrigin, Saturating, StaticLookup, Zero}, + traits::{AccountIdConversion, BadOrigin, BlockNumberProvider, Saturating, StaticLookup, Zero}, DispatchResult, Permill, RuntimeDebug, }; @@ -488,7 +488,7 @@ pub mod pallet { // If the sender is not the curator, and the curator is inactive, // slash the curator. if sender != *curator { - let block_number = frame_system::Pallet::::block_number(); + let block_number = Self::treasury_block_number(); if *update_due < block_number { slash_curator(curator, &mut bounty.curator_deposit); // Continue to change bounty status below... @@ -552,8 +552,8 @@ pub mod pallet { T::Currency::reserve(curator, deposit)?; bounty.curator_deposit = deposit; - let update_due = frame_system::Pallet::::block_number() + - T::BountyUpdatePeriod::get(); + let update_due = + Self::treasury_block_number() + T::BountyUpdatePeriod::get(); bounty.status = BountyStatus::Active { curator: curator.clone(), update_due }; @@ -607,8 +607,7 @@ pub mod pallet { bounty.status = BountyStatus::PendingPayout { curator: signer, beneficiary: beneficiary.clone(), - unlock_at: frame_system::Pallet::::block_number() + - T::BountyDepositPayoutDelay::get(), + unlock_at: Self::treasury_block_number() + T::BountyDepositPayoutDelay::get(), }; Ok(()) @@ -639,10 +638,7 @@ pub mod pallet { if let BountyStatus::PendingPayout { curator, beneficiary, unlock_at } = bounty.status { - ensure!( - frame_system::Pallet::::block_number() >= unlock_at, - Error::::Premature - ); + ensure!(Self::treasury_block_number() >= unlock_at, Error::::Premature); let bounty_account = Self::bounty_account_id(bounty_id); let balance = T::Currency::free_balance(&bounty_account); let fee = bounty.fee.min(balance); // just to be safe @@ -795,7 +791,7 @@ pub mod pallet { match bounty.status { BountyStatus::Active { ref curator, ref mut update_due } => { ensure!(*curator == signer, Error::::RequireCurator); - *update_due = (frame_system::Pallet::::block_number() + + *update_due = (Self::treasury_block_number() + T::BountyUpdatePeriod::get()) .max(*update_due); }, @@ -860,6 +856,14 @@ impl, I: 'static> Pallet { } impl, I: 'static> Pallet { + /// Get the block number used in the treasury pallet. + /// + /// It may be configured to use the relay chain block number on a parachain. + pub fn treasury_block_number() -> BlockNumberFor { + >::BlockNumberProvider::current_block_number() + } + + /// Calculate the deposit required for a curator. pub fn calculate_curator_deposit(fee: &BalanceOf) -> BalanceOf { let mut deposit = T::CuratorDepositMultiplier::get() * *fee; diff --git a/substrate/frame/bounties/src/tests.rs b/substrate/frame/bounties/src/tests.rs index c152391d807a..37bcadddae5b 100644 --- a/substrate/frame/bounties/src/tests.rs +++ b/substrate/frame/bounties/src/tests.rs @@ -40,6 +40,12 @@ use super::Event as BountiesEvent; type Block = frame_system::mocking::MockBlock; +// This function directly jumps to a block number, and calls `on_initialize`. +fn go_to_block(n: u64) { + ::BlockNumberProvider::set_block_number(n); + >::on_initialize(n); +} + frame_support::construct_runtime!( pub enum Test { @@ -98,6 +104,7 @@ impl pallet_treasury::Config for Test { type Paymaster = PayFromAccount; type BalanceConverter = UnityAssetBalanceConversion; type PayoutPeriod = ConstU64<10>; + type BlockNumberProvider = System; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -120,6 +127,7 @@ impl pallet_treasury::Config for Test { type Paymaster = PayFromAccount; type BalanceConverter = UnityAssetBalanceConversion; type PayoutPeriod = ConstU64<10>; + type BlockNumberProvider = System; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -186,7 +194,9 @@ impl ExtBuilder { .build_storage() .unwrap() .into(); - ext.execute_with(|| System::set_block_number(1)); + ext.execute_with(|| { + ::BlockNumberProvider::set_block_number(1) + }); ext } @@ -232,7 +242,7 @@ fn accepted_spend_proposal_ignored_outside_spend_period() { assert_ok!({ Treasury::spend_local(RuntimeOrigin::root(), 100, 3) }); - >::on_initialize(1); + go_to_block(1); assert_eq!(Balances::free_balance(3), 0); assert_eq!(Treasury::pot(), 100); }); @@ -245,7 +255,7 @@ fn unused_pot_should_diminish() { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(pallet_balances::TotalIssuance::::get(), init_total_issuance + 100); - >::on_initialize(2); + go_to_block(2); assert_eq!(Treasury::pot(), 50); assert_eq!(pallet_balances::TotalIssuance::::get(), init_total_issuance + 50); }); @@ -259,7 +269,7 @@ fn accepted_spend_proposal_enacted_on_spend_period() { assert_ok!({ Treasury::spend_local(RuntimeOrigin::root(), 100, 3) }); - >::on_initialize(2); + go_to_block(2); assert_eq!(Balances::free_balance(3), 100); assert_eq!(Treasury::pot(), 0); }); @@ -273,11 +283,11 @@ fn pot_underflow_should_not_diminish() { assert_ok!({ Treasury::spend_local(RuntimeOrigin::root(), 150, 3) }); - >::on_initialize(2); + go_to_block(2); assert_eq!(Treasury::pot(), 100); // Pot hasn't changed assert_ok!(Balances::deposit_into_existing(&Treasury::account_id(), 100)); - >::on_initialize(4); + go_to_block(4); assert_eq!(Balances::free_balance(3), 150); // Fund has been spent assert_eq!(Treasury::pot(), 25); // Pot has finally changed }); @@ -294,12 +304,12 @@ fn treasury_account_doesnt_get_deleted() { assert_ok!({ Treasury::spend_local(RuntimeOrigin::root(), treasury_balance, 3) }); - >::on_initialize(2); + go_to_block(2); assert_eq!(Treasury::pot(), 100); // Pot hasn't changed assert_ok!({ Treasury::spend_local(RuntimeOrigin::root(), Treasury::pot(), 3) }); - >::on_initialize(4); + go_to_block(4); assert_eq!(Treasury::pot(), 0); // Pot is emptied assert_eq!(Balances::free_balance(Treasury::account_id()), 1); // but the account is still there }); @@ -322,7 +332,8 @@ fn inexistent_account_works() { assert_ok!({ Treasury::spend_local(RuntimeOrigin::root(), 99, 3) }); assert_ok!({ Treasury::spend_local(RuntimeOrigin::root(), 1, 3) }); - >::on_initialize(2); + go_to_block(2); + assert_eq!(Treasury::pot(), 0); // Pot hasn't changed assert_eq!(Balances::free_balance(3), 0); // Balance of `3` hasn't changed @@ -330,7 +341,7 @@ fn inexistent_account_works() { assert_eq!(Treasury::pot(), 99); // Pot now contains funds assert_eq!(Balances::free_balance(Treasury::account_id()), 100); // Account does exist - >::on_initialize(4); + go_to_block(4); assert_eq!(Treasury::pot(), 0); // Pot has changed assert_eq!(Balances::free_balance(3), 99); // Balance of `3` has changed @@ -340,8 +351,6 @@ fn inexistent_account_works() { #[test] fn propose_bounty_works() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); - Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); @@ -377,8 +386,6 @@ fn propose_bounty_works() { #[test] fn propose_bounty_validation_works() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); - Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); @@ -406,7 +413,6 @@ fn propose_bounty_validation_works() { #[test] fn close_bounty_works() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_noop!(Bounties::close_bounty(RuntimeOrigin::root(), 0), Error::::InvalidIndex); @@ -431,7 +437,6 @@ fn close_bounty_works() { #[test] fn approve_bounty_works() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_noop!( Bounties::approve_bounty(RuntimeOrigin::root(), 0), @@ -466,7 +471,7 @@ fn approve_bounty_works() { assert_eq!(Balances::reserved_balance(0), deposit); assert_eq!(Balances::free_balance(0), 100 - deposit); - >::on_initialize(2); + go_to_block(2); // return deposit assert_eq!(Balances::reserved_balance(0), 0); @@ -492,7 +497,6 @@ fn approve_bounty_works() { #[test] fn assign_curator_works() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_noop!( @@ -504,8 +508,7 @@ fn assign_curator_works() { assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_noop!( Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 50), @@ -562,14 +565,12 @@ fn assign_curator_works() { #[test] fn unassign_curator_works() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); let fee = 4; @@ -615,15 +616,13 @@ fn unassign_curator_works() { #[test] fn award_and_claim_bounty_works() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&4, 10); assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); let fee = 4; assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, fee)); @@ -653,8 +652,7 @@ fn award_and_claim_bounty_works() { assert_noop!(Bounties::claim_bounty(RuntimeOrigin::signed(1), 0), Error::::Premature); - System::set_block_number(5); - >::on_initialize(5); + go_to_block(5); assert_ok!(Balances::transfer_allow_death( RuntimeOrigin::signed(0), @@ -682,23 +680,20 @@ fn award_and_claim_bounty_works() { #[test] fn claim_handles_high_fee() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&4, 30); assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 49)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); assert_ok!(Bounties::award_bounty(RuntimeOrigin::signed(4), 0, 3)); - System::set_block_number(5); - >::on_initialize(5); + go_to_block(5); // make fee > balance let res = Balances::slash(&Bounties::bounty_account_id(0), 10); @@ -723,16 +718,13 @@ fn claim_handles_high_fee() { #[test] fn cancel_and_refund() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); - Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Balances::transfer_allow_death( RuntimeOrigin::signed(0), @@ -766,14 +758,12 @@ fn cancel_and_refund() { #[test] fn award_and_cancel() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 0, 10)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(0), 0)); @@ -809,14 +799,12 @@ fn award_and_cancel() { #[test] fn expire_and_unassign() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 1, 10)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(1), 0)); @@ -824,16 +812,14 @@ fn expire_and_unassign() { assert_eq!(Balances::free_balance(1), 93); assert_eq!(Balances::reserved_balance(1), 5); - System::set_block_number(22); - >::on_initialize(22); + go_to_block(22); assert_noop!( Bounties::unassign_curator(RuntimeOrigin::signed(0), 0), Error::::Premature ); - System::set_block_number(23); - >::on_initialize(23); + go_to_block(23); assert_ok!(Bounties::unassign_curator(RuntimeOrigin::signed(0), 0)); @@ -857,7 +843,6 @@ fn expire_and_unassign() { #[test] fn extend_expiry() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&4, 10); assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); @@ -869,8 +854,7 @@ fn extend_expiry() { Error::::UnexpectedStatus ); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 10)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); @@ -878,8 +862,7 @@ fn extend_expiry() { assert_eq!(Balances::free_balance(4), 5); assert_eq!(Balances::reserved_balance(4), 5); - System::set_block_number(10); - >::on_initialize(10); + go_to_block(10); assert_noop!( Bounties::extend_bounty_expiry(RuntimeOrigin::signed(0), 0, Vec::new()), @@ -913,8 +896,7 @@ fn extend_expiry() { } ); - System::set_block_number(25); - >::on_initialize(25); + go_to_block(25); assert_noop!( Bounties::unassign_curator(RuntimeOrigin::signed(0), 0), @@ -993,13 +975,11 @@ fn genesis_funding_works() { #[test] fn unassign_curator_self() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 1, 10)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(1), 0)); @@ -1007,8 +987,7 @@ fn unassign_curator_self() { assert_eq!(Balances::free_balance(1), 93); assert_eq!(Balances::reserved_balance(1), 5); - System::set_block_number(8); - >::on_initialize(8); + go_to_block(8); assert_ok!(Bounties::unassign_curator(RuntimeOrigin::signed(1), 0)); @@ -1040,7 +1019,6 @@ fn accept_curator_handles_different_deposit_calculations() { let value = 88; let fee = 42; - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&user, 100); // Allow for a larger spend limit: @@ -1048,8 +1026,7 @@ fn accept_curator_handles_different_deposit_calculations() { assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), value, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), bounty_index)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), bounty_index, user, fee)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(user), bounty_index)); @@ -1070,8 +1047,7 @@ fn accept_curator_handles_different_deposit_calculations() { assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), value, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), bounty_index)); - System::set_block_number(4); - >::on_initialize(4); + go_to_block(4); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), bounty_index, user, fee)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(user), bounty_index)); @@ -1096,8 +1072,7 @@ fn accept_curator_handles_different_deposit_calculations() { assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), value, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), bounty_index)); - System::set_block_number(6); - >::on_initialize(6); + go_to_block(6); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), bounty_index, user, fee)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(user), bounty_index)); @@ -1114,7 +1089,6 @@ fn approve_bounty_works_second_instance() { // Set burn to 0 to make tracking funds easier. Burn::set(Permill::from_percent(0)); - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&Treasury1::account_id(), 201); assert_eq!(Balances::free_balance(&Treasury::account_id()), 101); @@ -1122,7 +1096,7 @@ fn approve_bounty_works_second_instance() { assert_ok!(Bounties1::propose_bounty(RuntimeOrigin::signed(0), 10, b"12345".to_vec())); assert_ok!(Bounties1::approve_bounty(RuntimeOrigin::root(), 0)); - >::on_initialize(2); + go_to_block(2); >::on_initialize(2); // Bounties 1 is funded... but from where? @@ -1137,8 +1111,6 @@ fn approve_bounty_works_second_instance() { #[test] fn approve_bounty_insufficient_spend_limit_errors() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); - Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); @@ -1155,8 +1127,6 @@ fn approve_bounty_insufficient_spend_limit_errors() { #[test] fn approve_bounty_instance1_insufficient_spend_limit_errors() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); - Balances::make_free_balance_be(&Treasury1::account_id(), 101); assert_eq!(Treasury1::pot(), 100); @@ -1173,7 +1143,6 @@ fn approve_bounty_instance1_insufficient_spend_limit_errors() { #[test] fn propose_curator_insufficient_spend_limit_errors() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); // Temporarily set a larger spend limit; @@ -1181,8 +1150,7 @@ fn propose_curator_insufficient_spend_limit_errors() { assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 51, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); SpendLimit::set(50); // 51 will not work since the limit is 50. @@ -1196,7 +1164,6 @@ fn propose_curator_insufficient_spend_limit_errors() { #[test] fn propose_curator_instance1_insufficient_spend_limit_errors() { ExtBuilder::default().build_and_execute(|| { - System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); // Temporarily set a larger spend limit; @@ -1204,7 +1171,6 @@ fn propose_curator_instance1_insufficient_spend_limit_errors() { assert_ok!(Bounties1::propose_bounty(RuntimeOrigin::signed(0), 11, b"12345".to_vec())); assert_ok!(Bounties1::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); >::on_initialize(2); SpendLimit1::set(10); diff --git a/substrate/frame/child-bounties/src/benchmarking.rs b/substrate/frame/child-bounties/src/benchmarking.rs index b1f6370f3340..68e99e21a456 100644 --- a/substrate/frame/child-bounties/src/benchmarking.rs +++ b/substrate/frame/child-bounties/src/benchmarking.rs @@ -25,6 +25,7 @@ use alloc::{vec, vec::Vec}; use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller, BenchmarkError}; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; +use sp_runtime::traits::BlockNumberProvider; use crate::Pallet as ChildBounties; use pallet_bounties::Pallet as Bounties; @@ -56,6 +57,10 @@ struct BenchmarkChildBounty { reason: Vec, } +fn set_block_number(n: BlockNumberFor) { + ::BlockNumberProvider::set_block_number(n); +} + fn setup_bounty( user: u32, description: u32, @@ -116,7 +121,8 @@ fn activate_bounty( let approve_origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; Bounties::::approve_bounty(approve_origin, child_bounty_setup.bounty_id)?; - Treasury::::on_initialize(BlockNumberFor::::zero()); + set_block_number::(T::SpendPeriod::get()); + Treasury::::on_initialize(frame_system::Pallet::::block_number()); Bounties::::propose_curator( RawOrigin::Root.into(), child_bounty_setup.bounty_id, @@ -231,8 +237,8 @@ benchmarks! { unassign_curator { setup_pot_account::(); let bounty_setup = activate_child_bounty::(0, T::MaximumReasonLength::get())?; - Treasury::::on_initialize(BlockNumberFor::::zero()); - frame_system::Pallet::::set_block_number(T::BountyUpdatePeriod::get() + 1u32.into()); + Treasury::::on_initialize(frame_system::Pallet::::block_number()); + set_block_number::(T::SpendPeriod::get() + T::BountyUpdatePeriod::get() + 1u32.into()); let caller = whitelisted_caller(); }: _(RawOrigin::Signed(caller), bounty_setup.bounty_id, bounty_setup.child_bounty_id) @@ -268,7 +274,7 @@ benchmarks! { let beneficiary_account: T::AccountId = account("beneficiary", 0, SEED); let beneficiary = T::Lookup::unlookup(beneficiary_account.clone()); - frame_system::Pallet::::set_block_number(T::BountyDepositPayoutDelay::get()); + set_block_number::(T::SpendPeriod::get() + T::BountyDepositPayoutDelay::get()); ensure!(T::Currency::free_balance(&beneficiary_account).is_zero(), "Beneficiary already has balance."); @@ -305,7 +311,7 @@ benchmarks! { close_child_bounty_active { setup_pot_account::(); let bounty_setup = activate_child_bounty::(0, T::MaximumReasonLength::get())?; - Treasury::::on_initialize(BlockNumberFor::::zero()); + Treasury::::on_initialize(frame_system::Pallet::::block_number()); }: close_child_bounty(RawOrigin::Root, bounty_setup.bounty_id, bounty_setup.child_bounty_id) verify { assert_last_event::(Event::Canceled { diff --git a/substrate/frame/child-bounties/src/lib.rs b/substrate/frame/child-bounties/src/lib.rs index 660a30ca5d26..1e970b6ae67c 100644 --- a/substrate/frame/child-bounties/src/lib.rs +++ b/substrate/frame/child-bounties/src/lib.rs @@ -67,7 +67,10 @@ use frame_support::traits::{ }; use sp_runtime::{ - traits::{AccountIdConversion, BadOrigin, CheckedSub, Saturating, StaticLookup, Zero}, + traits::{ + AccountIdConversion, BadOrigin, BlockNumberProvider, CheckedSub, Saturating, StaticLookup, + Zero, + }, DispatchResult, RuntimeDebug, }; @@ -523,7 +526,7 @@ pub mod pallet { let (parent_curator, update_due) = Self::ensure_bounty_active(parent_bounty_id)?; if sender == parent_curator || - update_due < frame_system::Pallet::::block_number() + update_due < Self::treasury_block_number() { // Slash the child-bounty curator if // + the call is made by the parent bounty curator. @@ -602,7 +605,7 @@ pub mod pallet { child_bounty.status = ChildBountyStatus::PendingPayout { curator: signer, beneficiary: beneficiary.clone(), - unlock_at: frame_system::Pallet::::block_number() + + unlock_at: Self::treasury_block_number() + T::BountyDepositPayoutDelay::get(), }; Ok(()) @@ -664,7 +667,7 @@ pub mod pallet { // Ensure block number is elapsed for processing the // claim. ensure!( - frame_system::Pallet::::block_number() >= *unlock_at, + Self::treasury_block_number() >= *unlock_at, BountiesError::::Premature, ); @@ -772,6 +775,13 @@ pub mod pallet { } impl Pallet { + /// Get the block number used in the treasury pallet. + /// + /// It may be configured to use the relay chain block number on a parachain. + pub fn treasury_block_number() -> BlockNumberFor { + ::BlockNumberProvider::current_block_number() + } + // This function will calculate the deposit of a curator. fn calculate_curator_deposit( parent_curator: &T::AccountId, diff --git a/substrate/frame/child-bounties/src/tests.rs b/substrate/frame/child-bounties/src/tests.rs index 125844fa70e2..96d01b03560d 100644 --- a/substrate/frame/child-bounties/src/tests.rs +++ b/substrate/frame/child-bounties/src/tests.rs @@ -42,6 +42,12 @@ use super::Event as ChildBountiesEvent; type Block = frame_system::mocking::MockBlock; type BountiesError = pallet_bounties::Error; +// This function directly jumps to a block number, and calls `on_initialize`. +fn go_to_block(n: u64) { + ::BlockNumberProvider::set_block_number(n); + >::on_initialize(n); +} + frame_support::construct_runtime!( pub enum Test { @@ -98,6 +104,7 @@ impl pallet_treasury::Config for Test { type Paymaster = PayFromAccount; type BalanceConverter = UnityAssetBalanceConversion; type PayoutPeriod = ConstU64<10>; + type BlockNumberProvider = System; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -184,15 +191,14 @@ fn add_child_bounty() { // Curator, child-bounty curator & beneficiary. // Make the parent bounty. - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); let fee = 8; assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, fee)); @@ -278,7 +284,7 @@ fn child_bounty_assign_curator() { // 3, Test for DB state of `ChildBounties`. // Make the parent bounty. - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&4, 101); Balances::make_free_balance_be(&8, 101); @@ -287,8 +293,7 @@ fn child_bounty_assign_curator() { assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); let fee = 4; assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, fee)); @@ -383,7 +388,7 @@ fn child_bounty_assign_curator() { fn award_claim_child_bounty() { new_test_ext().execute_with(|| { // Make the parent bounty. - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Balances::free_balance(Treasury::account_id()), 101); assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); @@ -396,8 +401,7 @@ fn award_claim_child_bounty() { assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); @@ -449,7 +453,7 @@ fn award_claim_child_bounty() { BountiesError::Premature ); - System::set_block_number(9); + go_to_block(9); assert_ok!(ChildBounties::claim_child_bounty(RuntimeOrigin::signed(7), 0, 0)); @@ -474,7 +478,7 @@ fn award_claim_child_bounty() { fn close_child_bounty_added() { new_test_ext().execute_with(|| { // Make the parent bounty. - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Balances::free_balance(Treasury::account_id()), 101); assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); @@ -487,8 +491,7 @@ fn close_child_bounty_added() { assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); @@ -504,7 +507,7 @@ fn close_child_bounty_added() { assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); - System::set_block_number(4); + go_to_block(4); // Close child-bounty. // Wrong origin. @@ -531,7 +534,7 @@ fn close_child_bounty_added() { fn close_child_bounty_active() { new_test_ext().execute_with(|| { // Make the parent bounty. - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Balances::free_balance(Treasury::account_id()), 101); assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); @@ -544,8 +547,7 @@ fn close_child_bounty_active() { assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); @@ -589,7 +591,7 @@ fn close_child_bounty_active() { fn close_child_bounty_pending() { new_test_ext().execute_with(|| { // Make the parent bounty. - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Balances::free_balance(Treasury::account_id()), 101); assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); @@ -602,8 +604,7 @@ fn close_child_bounty_pending() { assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); let parent_fee = 6; assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, parent_fee)); @@ -650,7 +651,7 @@ fn close_child_bounty_pending() { fn child_bounty_added_unassign_curator() { new_test_ext().execute_with(|| { // Make the parent bounty. - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Balances::free_balance(Treasury::account_id()), 101); assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); @@ -663,8 +664,7 @@ fn child_bounty_added_unassign_curator() { assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); @@ -692,7 +692,7 @@ fn child_bounty_added_unassign_curator() { fn child_bounty_curator_proposed_unassign_curator() { new_test_ext().execute_with(|| { // Make the parent bounty. - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Balances::free_balance(Treasury::account_id()), 101); assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); @@ -705,8 +705,7 @@ fn child_bounty_curator_proposed_unassign_curator() { assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); @@ -767,7 +766,7 @@ fn child_bounty_active_unassign_curator() { // bounty. Unassign from random account. Should slash. new_test_ext().execute_with(|| { // Make the parent bounty. - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Balances::free_balance(Treasury::account_id()), 101); assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); @@ -782,8 +781,7 @@ fn child_bounty_active_unassign_curator() { assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); @@ -797,8 +795,7 @@ fn child_bounty_active_unassign_curator() { )); assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); - System::set_block_number(3); - >::on_initialize(3); + go_to_block(3); // Propose and accept curator for child-bounty. let fee = 6; @@ -817,8 +814,7 @@ fn child_bounty_active_unassign_curator() { } ); - System::set_block_number(4); - >::on_initialize(4); + go_to_block(4); // Unassign curator - from reject origin. assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::root(), 0, 0)); @@ -856,8 +852,7 @@ fn child_bounty_active_unassign_curator() { } ); - System::set_block_number(5); - >::on_initialize(5); + go_to_block(5); // Unassign curator again - from parent curator. assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(4), 0, 0)); @@ -893,8 +888,7 @@ fn child_bounty_active_unassign_curator() { } ); - System::set_block_number(6); - >::on_initialize(6); + go_to_block(6); // Unassign curator again - from child-bounty curator. assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(6), 0, 0)); @@ -932,8 +926,7 @@ fn child_bounty_active_unassign_curator() { } ); - System::set_block_number(7); - >::on_initialize(7); + go_to_block(7); // Unassign curator again - from non curator; non reject origin; some random guy. // Bounty update period is not yet complete. @@ -942,8 +935,7 @@ fn child_bounty_active_unassign_curator() { BountiesError::Premature ); - System::set_block_number(20); - >::on_initialize(20); + go_to_block(20); // Unassign child curator from random account after inactivity. assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(3), 0, 0)); @@ -972,7 +964,7 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { // This can happen when the curator of parent bounty has been unassigned. new_test_ext().execute_with(|| { // Make the parent bounty. - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Balances::free_balance(Treasury::account_id()), 101); assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); @@ -987,8 +979,7 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); @@ -1002,8 +993,7 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { )); assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); - System::set_block_number(3); - >::on_initialize(3); + go_to_block(3); // Propose and accept curator for child-bounty. let fee = 8; @@ -1022,14 +1012,12 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { } ); - System::set_block_number(4); - >::on_initialize(4); + go_to_block(4); // Unassign parent bounty curator. assert_ok!(Bounties::unassign_curator(RuntimeOrigin::root(), 0)); - System::set_block_number(5); - >::on_initialize(5); + go_to_block(5); // Try unassign child-bounty curator - from non curator; non reject // origin; some random guy. Bounty update period is not yet complete. @@ -1057,15 +1045,13 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit); assert_eq!(Balances::reserved_balance(8), 0); // slashed - System::set_block_number(6); - >::on_initialize(6); + go_to_block(6); // Propose and accept curator for parent-bounty again. assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 5, 6)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(5), 0)); - System::set_block_number(7); - >::on_initialize(7); + go_to_block(7); // Propose and accept curator for child-bounty again. let fee = 2; @@ -1084,8 +1070,7 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { } ); - System::set_block_number(8); - >::on_initialize(8); + go_to_block(8); assert_noop!( ChildBounties::unassign_curator(RuntimeOrigin::signed(3), 0, 0), @@ -1095,8 +1080,7 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { // Unassign parent bounty curator again. assert_ok!(Bounties::unassign_curator(RuntimeOrigin::signed(5), 0)); - System::set_block_number(9); - >::on_initialize(9); + go_to_block(9); // Unassign curator again - from parent curator. assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(7), 0, 0)); @@ -1123,7 +1107,7 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { fn close_parent_with_child_bounty() { new_test_ext().execute_with(|| { // Make the parent bounty. - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Balances::free_balance(Treasury::account_id()), 101); assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); @@ -1142,8 +1126,7 @@ fn close_parent_with_child_bounty() { Error::::ParentBountyNotActive ); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); @@ -1157,8 +1140,7 @@ fn close_parent_with_child_bounty() { )); assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); - System::set_block_number(4); - >::on_initialize(4); + go_to_block(4); // Try close parent-bounty. // Child bounty active, can't close parent. @@ -1167,8 +1149,6 @@ fn close_parent_with_child_bounty() { BountiesError::HasActiveChildBounty ); - System::set_block_number(2); - // Close child-bounty. assert_ok!(ChildBounties::close_child_bounty(RuntimeOrigin::root(), 0, 0)); @@ -1187,7 +1167,7 @@ fn children_curator_fee_calculation_test() { // from parent bounty fee when claiming bounties. new_test_ext().execute_with(|| { // Make the parent bounty. - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Balances::free_balance(Treasury::account_id()), 101); assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); @@ -1199,8 +1179,7 @@ fn children_curator_fee_calculation_test() { assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); @@ -1214,8 +1193,7 @@ fn children_curator_fee_calculation_test() { )); assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); - System::set_block_number(4); - >::on_initialize(4); + go_to_block(4); let fee = 6; @@ -1245,7 +1223,7 @@ fn children_curator_fee_calculation_test() { } ); - System::set_block_number(9); + go_to_block(9); // Claim child-bounty. assert_ok!(ChildBounties::claim_child_bounty(RuntimeOrigin::signed(7), 0, 0)); @@ -1256,7 +1234,7 @@ fn children_curator_fee_calculation_test() { // Award the parent bounty. assert_ok!(Bounties::award_bounty(RuntimeOrigin::signed(4), 0, 9)); - System::set_block_number(15); + go_to_block(15); // Claim the parent bounty. assert_ok!(Bounties::claim_bounty(RuntimeOrigin::signed(9), 0)); @@ -1282,7 +1260,7 @@ fn accept_curator_handles_different_deposit_calculations() { let parent_value = 1_000_000; let parent_fee = 10_000; - System::set_block_number(1); + go_to_block(1); Balances::make_free_balance_be(&Treasury::account_id(), parent_value * 3); Balances::make_free_balance_be(&parent_curator, parent_fee * 100); assert_ok!(Bounties::propose_bounty( @@ -1292,8 +1270,7 @@ fn accept_curator_handles_different_deposit_calculations() { )); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), parent_index)); - System::set_block_number(2); - >::on_initialize(2); + go_to_block(2); assert_ok!(Bounties::propose_curator( RuntimeOrigin::root(), @@ -1319,8 +1296,7 @@ fn accept_curator_handles_different_deposit_calculations() { child_value, b"12345-p1".to_vec() )); - System::set_block_number(3); - >::on_initialize(3); + go_to_block(3); assert_ok!(ChildBounties::propose_curator( RuntimeOrigin::signed(parent_curator), parent_index, @@ -1354,8 +1330,7 @@ fn accept_curator_handles_different_deposit_calculations() { child_value, b"12345-p1".to_vec() )); - System::set_block_number(4); - >::on_initialize(4); + go_to_block(4); assert_ok!(ChildBounties::propose_curator( RuntimeOrigin::signed(parent_curator), parent_index, @@ -1387,8 +1362,7 @@ fn accept_curator_handles_different_deposit_calculations() { child_value, b"12345-p1".to_vec() )); - System::set_block_number(5); - >::on_initialize(5); + go_to_block(5); assert_ok!(ChildBounties::propose_curator( RuntimeOrigin::signed(parent_curator), parent_index, @@ -1423,8 +1397,7 @@ fn accept_curator_handles_different_deposit_calculations() { child_value, b"12345-p1".to_vec() )); - System::set_block_number(5); - >::on_initialize(5); + go_to_block(5); assert_ok!(ChildBounties::propose_curator( RuntimeOrigin::signed(parent_curator), parent_index, diff --git a/substrate/frame/tips/src/tests.rs b/substrate/frame/tips/src/tests.rs index 7e4a9368ad0c..f6f130b7e261 100644 --- a/substrate/frame/tips/src/tests.rs +++ b/substrate/frame/tips/src/tests.rs @@ -119,6 +119,7 @@ impl pallet_treasury::Config for Test { type Paymaster = PayFromAccount; type BalanceConverter = UnityAssetBalanceConversion; type PayoutPeriod = ConstU64<10>; + type BlockNumberProvider = System; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -141,6 +142,7 @@ impl pallet_treasury::Config for Test { type Paymaster = PayFromAccount; type BalanceConverter = UnityAssetBalanceConversion; type PayoutPeriod = ConstU64<10>; + type BlockNumberProvider = System; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } diff --git a/substrate/frame/treasury/src/lib.rs b/substrate/frame/treasury/src/lib.rs index ad74495ce090..b21a36949357 100644 --- a/substrate/frame/treasury/src/lib.rs +++ b/substrate/frame/treasury/src/lib.rs @@ -89,8 +89,11 @@ use scale_info::TypeInfo; use alloc::{boxed::Box, collections::btree_map::BTreeMap}; use sp_runtime::{ - traits::{AccountIdConversion, CheckedAdd, Saturating, StaticLookup, Zero}, - Permill, RuntimeDebug, + traits::{ + AccountIdConversion, BlockNumberProvider, CheckedAdd, One, Saturating, StaticLookup, + UniqueSaturatedInto, Zero, + }, + PerThing, Permill, RuntimeDebug, }; use frame_support::{ @@ -103,6 +106,7 @@ use frame_support::{ weights::Weight, BoundedVec, PalletId, }; +use frame_system::pallet_prelude::BlockNumberFor; pub use pallet::*; pub use weights::WeightInfo; @@ -275,6 +279,9 @@ pub mod pallet { /// Helper type for benchmarks. #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper: ArgumentsFactory; + + /// Provider for the block number. Normally this is the `frame_system` pallet. + type BlockNumberProvider: BlockNumberProvider>; } /// Number of proposals that have been made. @@ -322,6 +329,10 @@ pub mod pallet { OptionQuery, >; + /// The blocknumber for the last triggered spend period. + #[pallet::storage] + pub(crate) type LastSpendPeriod = StorageValue<_, BlockNumberFor, OptionQuery>; + #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig, I: 'static = ()> { @@ -414,7 +425,8 @@ pub mod pallet { impl, I: 'static> Hooks> for Pallet { /// ## Complexity /// - `O(A)` where `A` is the number of approvals - fn on_initialize(n: frame_system::pallet_prelude::BlockNumberFor) -> Weight { + fn on_initialize(_do_not_use_local_block_number: BlockNumberFor) -> Weight { + let block_number = T::BlockNumberProvider::current_block_number(); let pot = Self::pot(); let deactivated = Deactivated::::get(); if pot != deactivated { @@ -428,17 +440,29 @@ pub mod pallet { } // Check to see if we should spend some funds! - if (n % T::SpendPeriod::get()).is_zero() { - Self::spend_funds() + let last_spend_period = LastSpendPeriod::::get() + // This unwrap should only occur one time on any blockchain. + // `update_last_spend_period` will populate the `LastSpendPeriod` storage if it is + // empty. + .unwrap_or_else(|| Self::update_last_spend_period()); + let blocks_since_last_spend_period = block_number.saturating_sub(last_spend_period); + let safe_spend_period = T::SpendPeriod::get().max(BlockNumberFor::::one()); + + // Safe because of `max(1)` above. + let (spend_periods_passed, extra_blocks) = ( + blocks_since_last_spend_period / safe_spend_period, + blocks_since_last_spend_period % safe_spend_period, + ); + let new_last_spend_period = block_number.saturating_sub(extra_blocks); + if spend_periods_passed > BlockNumberFor::::zero() { + Self::spend_funds(spend_periods_passed, new_last_spend_period) } else { Weight::zero() } } #[cfg(feature = "try-runtime")] - fn try_state( - _: frame_system::pallet_prelude::BlockNumberFor, - ) -> Result<(), sp_runtime::TryRuntimeError> { + fn try_state(_: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { Self::do_try_state()?; Ok(()) } @@ -594,7 +618,7 @@ pub mod pallet { let max_amount = T::SpendOrigin::ensure_origin(origin)?; let beneficiary = T::BeneficiaryLookup::lookup(*beneficiary)?; - let now = frame_system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); let valid_from = valid_from.unwrap_or(now); let expire_at = valid_from.saturating_add(T::PayoutPeriod::get()); ensure!(expire_at > now, Error::::SpendExpired); @@ -672,7 +696,7 @@ pub mod pallet { pub fn payout(origin: OriginFor, index: SpendIndex) -> DispatchResult { ensure_signed(origin)?; let mut spend = Spends::::get(index).ok_or(Error::::InvalidIndex)?; - let now = frame_system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); ensure!(now >= spend.valid_from, Error::::EarlyPayout); ensure!(spend.expire_at > now, Error::::SpendExpired); ensure!( @@ -718,7 +742,7 @@ pub mod pallet { ensure_signed(origin)?; let mut spend = Spends::::get(index).ok_or(Error::::InvalidIndex)?; - let now = frame_system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); if now > spend.expire_at && !matches!(spend.status, State::Attempted { .. }) { // spend has expired and no further status update is expected. @@ -792,6 +816,25 @@ impl, I: 'static> Pallet { T::PalletId::get().into_account_truncating() } + // Backfill the `LastSpendPeriod` storage, assuming that no configuration has changed + // since introducing this code. Used specifically for a migration-less switch to populate + // `LastSpendPeriod`. + fn update_last_spend_period() -> BlockNumberFor { + let block_number = T::BlockNumberProvider::current_block_number(); + let spend_period = T::SpendPeriod::get().max(BlockNumberFor::::one()); + let time_since_last_spend = block_number % spend_period; + // If it happens that this logic runs directly on a spend period block, we need to backdate + // to the last spend period so a spend still occurs this block. + let last_spend_period = if time_since_last_spend.is_zero() { + block_number.saturating_sub(spend_period) + } else { + // Otherwise, this is the last time we had a spend period. + block_number.saturating_sub(time_since_last_spend) + }; + LastSpendPeriod::::put(last_spend_period); + last_spend_period + } + /// Public function to proposal_count storage. pub fn proposal_count() -> ProposalIndex { ProposalCount::::get() @@ -808,7 +851,11 @@ impl, I: 'static> Pallet { } /// Spend some money! returns number of approvals before spend. - pub fn spend_funds() -> Weight { + pub fn spend_funds( + spend_periods_passed: BlockNumberFor, + new_last_spend_period: BlockNumberFor, + ) -> Weight { + LastSpendPeriod::::put(new_last_spend_period); let mut total_weight = Weight::zero(); let mut budget_remaining = Self::pot(); @@ -860,10 +907,15 @@ impl, I: 'static> Pallet { &mut missed_any, ); - if !missed_any { - // burn some proportion of the remaining budget if we run a surplus. - let burn = (T::Burn::get() * budget_remaining).min(budget_remaining); - budget_remaining -= burn; + if !missed_any && !T::Burn::get().is_zero() { + // Get the amount of treasury that should be left after potentially multiple spend + // periods have passed. + let one_minus_burn = T::Burn::get().left_from_one(); + let percent_left = + one_minus_burn.saturating_pow(spend_periods_passed.unique_saturated_into()); + let new_budget_remaining = percent_left * budget_remaining; + let burn = budget_remaining.saturating_sub(new_budget_remaining); + budget_remaining = new_budget_remaining; let (debit, credit) = T::Currency::pair(burn); imbalance.subsume(debit); diff --git a/substrate/frame/treasury/src/tests.rs b/substrate/frame/treasury/src/tests.rs index 106bfb530a88..a99dd0dd4449 100644 --- a/substrate/frame/treasury/src/tests.rs +++ b/substrate/frame/treasury/src/tests.rs @@ -97,6 +97,12 @@ fn set_status(id: u64, s: PaymentStatus) { STATUS.with(|m| m.borrow_mut().insert(id, s)); } +// This function directly jumps to a block number, and calls `on_initialize`. +fn go_to_block(n: u64) { + ::BlockNumberProvider::set_block_number(n); + >::on_initialize(n); +} + pub struct TestPay; impl Pay for TestPay { type Beneficiary = u128; @@ -187,6 +193,7 @@ impl Config for Test { type Paymaster = TestPay; type BalanceConverter = MulBy>; type PayoutPeriod = SpendPayoutPeriod; + type BlockNumberProvider = System; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -268,7 +275,7 @@ fn spend_local_origin_permissioning_works() { fn spend_local_origin_works() { ExtBuilder::default().build().execute_with(|| { // Check that accumulate works when we have Some value in Dummy already. - Balances::make_free_balance_be(&Treasury::account_id(), 101); + Balances::make_free_balance_be(&Treasury::account_id(), 102); // approve spend of some amount to beneficiary `6`. assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6)); assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6)); @@ -278,12 +285,12 @@ fn spend_local_origin_works() { assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(12), 20, 6)); assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(13), 50, 6)); // free balance of `6` is zero, spend period has not passed. - >::on_initialize(1); + go_to_block(1); assert_eq!(Balances::free_balance(6), 0); // free balance of `6` is `100`, spend period has passed. - >::on_initialize(2); + go_to_block(2); assert_eq!(Balances::free_balance(6), 100); - // `100` spent, `1` burned. + // `100` spent, `1` burned, `1` in ED. assert_eq!(Treasury::pot(), 0); }); } @@ -304,7 +311,7 @@ fn accepted_spend_proposal_ignored_outside_spend_period() { assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 100, 3)); - >::on_initialize(1); + go_to_block(1); assert_eq!(Balances::free_balance(3), 0); assert_eq!(Treasury::pot(), 100); }); @@ -317,7 +324,7 @@ fn unused_pot_should_diminish() { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(pallet_balances::TotalIssuance::::get(), init_total_issuance + 100); - >::on_initialize(2); + go_to_block(2); assert_eq!(Treasury::pot(), 50); assert_eq!(pallet_balances::TotalIssuance::::get(), init_total_issuance + 50); }); @@ -331,7 +338,7 @@ fn accepted_spend_proposal_enacted_on_spend_period() { assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 100, 3)); - >::on_initialize(2); + go_to_block(2); assert_eq!(Balances::free_balance(3), 100); assert_eq!(Treasury::pot(), 0); }); @@ -345,11 +352,11 @@ fn pot_underflow_should_not_diminish() { assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 150, 3)); - >::on_initialize(2); + go_to_block(2); assert_eq!(Treasury::pot(), 100); // Pot hasn't changed let _ = Balances::deposit_into_existing(&Treasury::account_id(), 100).unwrap(); - >::on_initialize(4); + go_to_block(4); assert_eq!(Balances::free_balance(3), 150); // Fund has been spent assert_eq!(Treasury::pot(), 25); // Pot has finally changed }); @@ -366,12 +373,12 @@ fn treasury_account_doesnt_get_deleted() { assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), treasury_balance, 3)); - >::on_initialize(2); + go_to_block(2); assert_eq!(Treasury::pot(), 100); // Pot hasn't changed assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), Treasury::pot(), 3)); - >::on_initialize(4); + go_to_block(4); assert_eq!(Treasury::pot(), 0); // Pot is emptied assert_eq!(Balances::free_balance(Treasury::account_id()), 1); // but the account is still there }); @@ -395,7 +402,8 @@ fn inexistent_account_works() { assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 99, 3)); assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 1, 3)); - >::on_initialize(2); + go_to_block(2); + assert_eq!(Treasury::pot(), 0); // Pot hasn't changed assert_eq!(Balances::free_balance(3), 0); // Balance of `3` hasn't changed @@ -403,7 +411,7 @@ fn inexistent_account_works() { assert_eq!(Treasury::pot(), 99); // Pot now contains funds assert_eq!(Balances::free_balance(Treasury::account_id()), 100); // Account does exist - >::on_initialize(4); + go_to_block(4); assert_eq!(Treasury::pot(), 0); // Pot has changed assert_eq!(Balances::free_balance(3), 99); // Balance of `3` has changed @@ -936,3 +944,35 @@ fn try_state_spends_invariant_3_works() { ); }); } + +#[test] +fn multiple_spend_periods_work() { + ExtBuilder::default().build().execute_with(|| { + // Check that accumulate works when we have Some value in Dummy already. + // 100 will be spent, 1024 will be the burn amount, 1 for ED + Balances::make_free_balance_be(&Treasury::account_id(), 100 + 1024 + 1); + // approve spend of total amount 100 to beneficiary `6`. + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(11), 10, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(12), 20, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(13), 50, 6)); + // free balance of `6` is zero, spend period has not passed. + go_to_block(1); + assert_eq!(Balances::free_balance(6), 0); + // free balance of `6` is `100`, spend period has passed. + go_to_block(2); + assert_eq!(Balances::free_balance(6), 100); + // `100` spent, 50% burned + assert_eq!(Treasury::pot(), 512); + + // 3 more spends periods pass at once, and an extra block. + go_to_block(2 + (3 * 2) + 1); + // Pot should be reduced by 50% 3 times, so 1/8th the amount. + assert_eq!(Treasury::pot(), 64); + // Even though we are on block 9, the last spend period was block 8. + assert_eq!(LastSpendPeriod::::get(), Some(8)); + }); +} From 68e05636877448d1d9d4944706af63a2f7677a46 Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Mon, 4 Nov 2024 09:39:13 +0200 Subject: [PATCH 4/6] collation-generation: use v2 receipts (#5908) Part of /~https://github.com/paritytech/polkadot-sdk/issues/5047 Plus some cleanups --------- Signed-off-by: Andrei Sandu Co-authored-by: Andrei Sandu Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Co-authored-by: GitHub Action --- Cargo.lock | 2 + polkadot/node/collation-generation/Cargo.toml | 1 + .../node/collation-generation/src/error.rs | 9 +- polkadot/node/collation-generation/src/lib.rs | 576 ++++----- .../node/collation-generation/src/metrics.rs | 68 +- .../node/collation-generation/src/tests.rs | 1078 ++++------------- polkadot/primitives/Cargo.toml | 2 + polkadot/primitives/src/vstaging/mod.rs | 13 + prdoc/pr_5908.prdoc | 14 + 9 files changed, 529 insertions(+), 1234 deletions(-) create mode 100644 prdoc/pr_5908.prdoc diff --git a/Cargo.lock b/Cargo.lock index 1f171ad756c0..520b088f913c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14299,6 +14299,7 @@ dependencies = [ "polkadot-primitives", "polkadot-primitives-test-helpers", "rstest", + "schnellru", "sp-core 28.0.0", "sp-keyring", "sp-maybe-compressed-blob 11.0.0", @@ -15139,6 +15140,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-staking", "sp-std 14.0.0", + "thiserror", ] [[package]] diff --git a/polkadot/node/collation-generation/Cargo.toml b/polkadot/node/collation-generation/Cargo.toml index 855b6b0e86eb..777458673f5b 100644 --- a/polkadot/node/collation-generation/Cargo.toml +++ b/polkadot/node/collation-generation/Cargo.toml @@ -21,6 +21,7 @@ sp-core = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } thiserror = { workspace = true } codec = { features = ["bit-vec", "derive"], workspace = true } +schnellru = { workspace = true } [dev-dependencies] polkadot-node-subsystem-test-helpers = { workspace = true } diff --git a/polkadot/node/collation-generation/src/error.rs b/polkadot/node/collation-generation/src/error.rs index f04e3c4f20b4..68902f58579a 100644 --- a/polkadot/node/collation-generation/src/error.rs +++ b/polkadot/node/collation-generation/src/error.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +use polkadot_primitives::vstaging::CandidateReceiptError; use thiserror::Error; #[derive(Debug, Error)] @@ -30,8 +31,12 @@ pub enum Error { UtilRuntime(#[from] polkadot_node_subsystem_util::runtime::Error), #[error(transparent)] Erasure(#[from] polkadot_erasure_coding::Error), - #[error("Parachain backing state not available in runtime.")] - MissingParaBackingState, + #[error("Collation submitted before initialization")] + SubmittedBeforeInit, + #[error("V2 core index check failed: {0}")] + CandidateReceiptCheck(CandidateReceiptError), + #[error("PoV size {0} exceeded maximum size of {1}")] + POVSizeExceeded(usize, usize), } pub type Result = std::result::Result; diff --git a/polkadot/node/collation-generation/src/lib.rs b/polkadot/node/collation-generation/src/lib.rs index f04f69cbd380..9e975acf10b8 100644 --- a/polkadot/node/collation-generation/src/lib.rs +++ b/polkadot/node/collation-generation/src/lib.rs @@ -32,27 +32,34 @@ #![deny(missing_docs)] use codec::Encode; -use futures::{channel::oneshot, future::FutureExt, join, select}; +use error::{Error, Result}; +use futures::{channel::oneshot, future::FutureExt, select}; use polkadot_node_primitives::{ AvailableData, Collation, CollationGenerationConfig, CollationSecondedSignal, PoV, SubmitCollationParams, }; use polkadot_node_subsystem::{ - messages::{CollationGenerationMessage, CollatorProtocolMessage}, - overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, RuntimeApiError, SpawnedSubsystem, - SubsystemContext, SubsystemError, SubsystemResult, + messages::{CollationGenerationMessage, CollatorProtocolMessage, RuntimeApiMessage}, + overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, + SubsystemContext, SubsystemError, SubsystemResult, SubsystemSender, }; use polkadot_node_subsystem_util::{ - request_async_backing_params, request_availability_cores, request_para_backing_state, - request_persisted_validation_data, request_validation_code, request_validation_code_hash, - request_validators, runtime::fetch_claim_queue, + request_claim_queue, request_persisted_validation_data, request_session_index_for_child, + request_validation_code_hash, request_validators, + runtime::{request_node_features, ClaimQueueSnapshot}, }; use polkadot_primitives::{ collator_signature_payload, - vstaging::{CandidateReceiptV2 as CandidateReceipt, CoreState}, + node_features::FeatureIndex, + vstaging::{ + transpose_claim_queue, CandidateDescriptorV2, CandidateReceiptV2 as CandidateReceipt, + CommittedCandidateReceiptV2, TransposedClaimQueue, + }, CandidateCommitments, CandidateDescriptor, CollatorPair, CoreIndex, Hash, Id as ParaId, - OccupiedCoreAssumption, PersistedValidationData, ScheduledCore, ValidationCodeHash, + NodeFeatures, OccupiedCoreAssumption, PersistedValidationData, SessionIndex, + ValidationCodeHash, }; +use schnellru::{ByLength, LruMap}; use sp_core::crypto::Pair; use std::sync::Arc; @@ -69,6 +76,7 @@ const LOG_TARGET: &'static str = "parachain::collation-generation"; /// Collation Generation Subsystem pub struct CollationGenerationSubsystem { config: Option>, + session_info_cache: SessionInfoCache, metrics: Metrics, } @@ -76,7 +84,7 @@ pub struct CollationGenerationSubsystem { impl CollationGenerationSubsystem { /// Create a new instance of the `CollationGenerationSubsystem`. pub fn new(metrics: Metrics) -> Self { - Self { config: None, metrics } + Self { config: None, metrics, session_info_cache: SessionInfoCache::new() } } /// Run this subsystem @@ -117,19 +125,8 @@ impl CollationGenerationSubsystem { activated, .. }))) => { - // follow the procedure from the guide - if let Some(config) = &self.config { - let metrics = self.metrics.clone(); - if let Err(err) = handle_new_activations( - config.clone(), - activated.into_iter().map(|v| v.hash), - ctx, - metrics, - ) - .await - { - gum::warn!(target: LOG_TARGET, err = ?err, "failed to handle new activations"); - } + if let Err(err) = self.handle_new_activation(activated.map(|v| v.hash), ctx).await { + gum::warn!(target: LOG_TARGET, err = ?err, "failed to handle new activation"); } false @@ -154,14 +151,8 @@ impl CollationGenerationSubsystem { Ok(FromOrchestra::Communication { msg: CollationGenerationMessage::SubmitCollation(params), }) => { - if let Some(config) = &self.config { - if let Err(err) = - handle_submit_collation(params, config, ctx, &self.metrics).await - { - gum::error!(target: LOG_TARGET, ?err, "Failed to submit collation"); - } - } else { - gum::error!(target: LOG_TARGET, "Collation submitted before initialization"); + if let Err(err) = self.handle_submit_collation(params, ctx).await { + gum::error!(target: LOG_TARGET, ?err, "Failed to submit collation"); } false @@ -178,175 +169,132 @@ impl CollationGenerationSubsystem { }, } } -} - -#[overseer::subsystem(CollationGeneration, error=SubsystemError, prefix=self::overseer)] -impl CollationGenerationSubsystem { - fn start(self, ctx: Context) -> SpawnedSubsystem { - let future = async move { - self.run(ctx).await; - Ok(()) - } - .boxed(); - - SpawnedSubsystem { name: "collation-generation-subsystem", future } - } -} -#[overseer::contextbounds(CollationGeneration, prefix = self::overseer)] -async fn handle_new_activations( - config: Arc, - activated: impl IntoIterator, - ctx: &mut Context, - metrics: Metrics, -) -> crate::error::Result<()> { - // follow the procedure from the guide: - // https://paritytech.github.io/polkadot-sdk/book/node/collators/collation-generation.html - - // If there is no collation function provided, bail out early. - // Important: Lookahead collator and slot based collator do not use `CollatorFn`. - if config.collator.is_none() { - return Ok(()) - } - - let para_id = config.para_id; - - let _overall_timer = metrics.time_new_activations(); - - for relay_parent in activated { - let _relay_parent_timer = metrics.time_new_activations_relay_parent(); - - let (availability_cores, validators, async_backing_params) = join!( - request_availability_cores(relay_parent, ctx.sender()).await, - request_validators(relay_parent, ctx.sender()).await, - request_async_backing_params(relay_parent, ctx.sender()).await, - ); - - let availability_cores = availability_cores??; - let async_backing_params = async_backing_params?.ok(); - let n_validators = validators??.len(); - let maybe_claim_queue = fetch_claim_queue(ctx.sender(), relay_parent) - .await - .map_err(crate::error::Error::UtilRuntime)?; - - // The loop bellow will fill in cores that the para is allowed to build on. - let mut cores_to_build_on = Vec::new(); - - // This assumption refers to all cores of the parachain, taking elastic scaling - // into account. - let mut para_assumption = None; - for (core_idx, core) in availability_cores.into_iter().enumerate() { - // This nested assumption refers only to the core being iterated. - let (core_assumption, scheduled_core) = match core { - CoreState::Scheduled(scheduled_core) => - (OccupiedCoreAssumption::Free, scheduled_core), - CoreState::Occupied(occupied_core) => match async_backing_params { - Some(params) if params.max_candidate_depth >= 1 => { - // maximum candidate depth when building on top of a block - // pending availability is necessarily 1 - the depth of the - // pending block is 0 so the child has depth 1. - - // Use claim queue if available, or fallback to `next_up_on_available` - let res = match maybe_claim_queue { - Some(ref claim_queue) => { - // read what's in the claim queue for this core at depth 0. - claim_queue - .get_claim_for(CoreIndex(core_idx as u32), 0) - .map(|para_id| ScheduledCore { para_id, collator: None }) - }, - None => { - // Runtime doesn't support claim queue runtime api. Fallback to - // `next_up_on_available` - occupied_core.next_up_on_available - }, - }; + async fn handle_submit_collation( + &mut self, + params: SubmitCollationParams, + ctx: &mut Context, + ) -> Result<()> { + let Some(config) = &self.config else { + return Err(Error::SubmittedBeforeInit); + }; + let _timer = self.metrics.time_submit_collation(); - match res { - Some(res) => (OccupiedCoreAssumption::Included, res), - None => continue, - } - }, - _ => { - gum::trace!( - target: LOG_TARGET, - core_idx = %core_idx, - relay_parent = ?relay_parent, - "core is occupied. Keep going.", - ); - continue - }, - }, - CoreState::Free => { - gum::trace!( - target: LOG_TARGET, - core_idx = %core_idx, - "core is not assigned to any para. Keep going.", - ); - continue - }, - }; + let SubmitCollationParams { + relay_parent, + collation, + parent_head, + validation_code_hash, + result_sender, + core_index, + } = params; - if scheduled_core.para_id != config.para_id { - gum::trace!( + let mut validation_data = match request_persisted_validation_data( + relay_parent, + config.para_id, + OccupiedCoreAssumption::TimedOut, + ctx.sender(), + ) + .await + .await?? + { + Some(v) => v, + None => { + gum::debug!( target: LOG_TARGET, - core_idx = %core_idx, relay_parent = ?relay_parent, our_para = %config.para_id, - their_para = %scheduled_core.para_id, - "core is not assigned to our para. Keep going.", + "No validation data for para - does it exist at this relay-parent?", ); - } else { - // This does not work for elastic scaling, but it should be enough for single - // core parachains. If async backing runtime is available we later override - // the assumption based on the `para_backing_state` API response. - para_assumption = Some(core_assumption); - // Accumulate cores for building collation(s) outside the loop. - cores_to_build_on.push(CoreIndex(core_idx as u32)); - } - } + return Ok(()) + }, + }; - // Skip to next relay parent if there is no core assigned to us. - if cores_to_build_on.is_empty() { - continue + // We need to swap the parent-head data, but all other fields here will be correct. + validation_data.parent_head = parent_head; + + let claim_queue = request_claim_queue(relay_parent, ctx.sender()).await.await??; + + let session_index = + request_session_index_for_child(relay_parent, ctx.sender()).await.await??; + + let session_info = + self.session_info_cache.get(relay_parent, session_index, ctx.sender()).await?; + let collation = PreparedCollation { + collation, + relay_parent, + para_id: config.para_id, + validation_data, + validation_code_hash, + n_validators: session_info.n_validators, + core_index, + session_index, + }; + + construct_and_distribute_receipt( + collation, + config.key.clone(), + ctx.sender(), + result_sender, + &mut self.metrics, + session_info.v2_receipts, + &transpose_claim_queue(claim_queue), + ) + .await?; + + Ok(()) + } + + async fn handle_new_activation( + &mut self, + maybe_activated: Option, + ctx: &mut Context, + ) -> Result<()> { + let Some(config) = &self.config else { + return Ok(()); + }; + + let Some(relay_parent) = maybe_activated else { return Ok(()) }; + + // If there is no collation function provided, bail out early. + // Important: Lookahead collator and slot based collator do not use `CollatorFn`. + if config.collator.is_none() { + return Ok(()) } - // If at least one core is assigned to us, `para_assumption` is `Some`. - let Some(mut para_assumption) = para_assumption else { continue }; - - // If it is none it means that neither async backing or elastic scaling (which - // depends on it) are supported. We'll use the `para_assumption` we got from - // iterating cores. - if async_backing_params.is_some() { - // We are being very optimistic here, but one of the cores could pend availability some - // more block, ore even time out. - // For timeout assumption the collator can't really know because it doesn't receive - // bitfield gossip. - let para_backing_state = - request_para_backing_state(relay_parent, config.para_id, ctx.sender()) - .await - .await?? - .ok_or(crate::error::Error::MissingParaBackingState)?; - - // Override the assumption about the para's assigned cores. - para_assumption = if para_backing_state.pending_availability.is_empty() { - OccupiedCoreAssumption::Free - } else { - OccupiedCoreAssumption::Included - } + let para_id = config.para_id; + + let _timer = self.metrics.time_new_activation(); + + let session_index = + request_session_index_for_child(relay_parent, ctx.sender()).await.await??; + + let session_info = + self.session_info_cache.get(relay_parent, session_index, ctx.sender()).await?; + let n_validators = session_info.n_validators; + + let claim_queue = + ClaimQueueSnapshot::from(request_claim_queue(relay_parent, ctx.sender()).await.await??); + + let cores_to_build_on = claim_queue + .iter_claims_at_depth(0) + .filter_map(|(core_idx, para_id)| (para_id == config.para_id).then_some(core_idx)) + .collect::>(); + + // Nothing to do if no core assigned to us. + if cores_to_build_on.is_empty() { + return Ok(()) } - gum::debug!( - target: LOG_TARGET, - relay_parent = ?relay_parent, - our_para = %para_id, - ?para_assumption, - "Occupied core(s) assumption", - ); + // We are being very optimistic here, but one of the cores could be pending availability + // for some more blocks, or even time out. We assume all cores are being freed. let mut validation_data = match request_persisted_validation_data( relay_parent, para_id, - para_assumption, + // Just use included assumption always. If there are no pending candidates it's a + // no-op. + OccupiedCoreAssumption::Included, ctx.sender(), ) .await @@ -360,17 +308,20 @@ async fn handle_new_activations( our_para = %para_id, "validation data is not available", ); - continue + return Ok(()) }, }; - let validation_code_hash = match obtain_validation_code_hash_with_assumption( + let validation_code_hash = match request_validation_code_hash( relay_parent, para_id, - para_assumption, + // Just use included assumption always. If there are no pending candidates it's a + // no-op. + OccupiedCoreAssumption::Included, ctx.sender(), ) - .await? + .await + .await?? { Some(v) => v, None => { @@ -380,17 +331,19 @@ async fn handle_new_activations( our_para = %para_id, "validation code hash is not found.", ); - continue + return Ok(()) }, }; let task_config = config.clone(); - let metrics = metrics.clone(); + let metrics = self.metrics.clone(); let mut task_sender = ctx.sender().clone(); ctx.spawn( "chained-collation-builder", Box::pin(async move { + let transposed_claim_queue = transpose_claim_queue(claim_queue.0); + for core_index in cores_to_build_on { let collator_fn = match task_config.collator.as_ref() { Some(x) => x, @@ -411,7 +364,7 @@ async fn handle_new_activations( }; let parent_head = collation.head_data.clone(); - construct_and_distribute_receipt( + if let Err(err) = construct_and_distribute_receipt( PreparedCollation { collation, para_id, @@ -420,13 +373,24 @@ async fn handle_new_activations( validation_code_hash, n_validators, core_index, + session_index, }, task_config.key.clone(), &mut task_sender, result_sender, &metrics, + session_info.v2_receipts, + &transposed_claim_queue, ) - .await; + .await + { + gum::error!( + target: LOG_TARGET, + "Failed to construct and distribute collation: {}", + err + ); + return + } // Chain the collations. All else stays the same as we build the chained // collation on same relay parent. @@ -434,76 +398,64 @@ async fn handle_new_activations( } }), )?; - } - Ok(()) + Ok(()) + } } -#[overseer::contextbounds(CollationGeneration, prefix = self::overseer)] -async fn handle_submit_collation( - params: SubmitCollationParams, - config: &CollationGenerationConfig, - ctx: &mut Context, - metrics: &Metrics, -) -> crate::error::Result<()> { - let _timer = metrics.time_submit_collation(); +#[overseer::subsystem(CollationGeneration, error=SubsystemError, prefix=self::overseer)] +impl CollationGenerationSubsystem { + fn start(self, ctx: Context) -> SpawnedSubsystem { + let future = async move { + self.run(ctx).await; + Ok(()) + } + .boxed(); - let SubmitCollationParams { - relay_parent, - collation, - parent_head, - validation_code_hash, - result_sender, - core_index, - } = params; + SpawnedSubsystem { name: "collation-generation-subsystem", future } + } +} - let validators = request_validators(relay_parent, ctx.sender()).await.await??; - let n_validators = validators.len(); +#[derive(Clone)] +struct PerSessionInfo { + v2_receipts: bool, + n_validators: usize, +} - // We need to swap the parent-head data, but all other fields here will be correct. - let mut validation_data = match request_persisted_validation_data( - relay_parent, - config.para_id, - OccupiedCoreAssumption::TimedOut, - ctx.sender(), - ) - .await - .await?? - { - Some(v) => v, - None => { - gum::debug!( - target: LOG_TARGET, - relay_parent = ?relay_parent, - our_para = %config.para_id, - "No validation data for para - does it exist at this relay-parent?", - ); - return Ok(()) - }, - }; +struct SessionInfoCache(LruMap); - validation_data.parent_head = parent_head; +impl SessionInfoCache { + fn new() -> Self { + Self(LruMap::new(ByLength::new(2))) + } - let collation = PreparedCollation { - collation, - relay_parent, - para_id: config.para_id, - validation_data, - validation_code_hash, - n_validators, - core_index, - }; + async fn get>( + &mut self, + relay_parent: Hash, + session_index: SessionIndex, + sender: &mut Sender, + ) -> Result { + if let Some(info) = self.0.get(&session_index) { + return Ok(info.clone()) + } - construct_and_distribute_receipt( - collation, - config.key.clone(), - ctx.sender(), - result_sender, - metrics, - ) - .await; + let n_validators = + request_validators(relay_parent, &mut sender.clone()).await.await??.len(); - Ok(()) + let node_features = request_node_features(relay_parent, session_index, sender) + .await? + .unwrap_or(NodeFeatures::EMPTY); + + let info = PerSessionInfo { + v2_receipts: node_features + .get(FeatureIndex::CandidateReceiptV2 as usize) + .map(|b| *b) + .unwrap_or(false), + n_validators, + }; + self.0.insert(session_index, info); + Ok(self.0.get(&session_index).expect("Just inserted").clone()) + } } struct PreparedCollation { @@ -514,6 +466,7 @@ struct PreparedCollation { validation_code_hash: ValidationCodeHash, n_validators: usize, core_index: CoreIndex, + session_index: SessionIndex, } /// Takes a prepared collation, along with its context, and produces a candidate receipt @@ -524,7 +477,9 @@ async fn construct_and_distribute_receipt( sender: &mut impl overseer::CollationGenerationSenderTrait, result_sender: Option>, metrics: &Metrics, -) { + v2_receipts: bool, + transposed_claim_queue: &TransposedClaimQueue, +) -> Result<()> { let PreparedCollation { collation, para_id, @@ -533,6 +488,7 @@ async fn construct_and_distribute_receipt( validation_code_hash, n_validators, core_index, + session_index, } = collation; let persisted_validation_data_hash = validation_data.hash(); @@ -550,15 +506,7 @@ async fn construct_and_distribute_receipt( // As such, honest collators never produce an uncompressed PoV which starts with // a compression magic number, which would lead validators to reject the collation. if encoded_size > validation_data.max_pov_size as usize { - gum::debug!( - target: LOG_TARGET, - para_id = %para_id, - size = encoded_size, - max_size = validation_data.max_pov_size, - "PoV exceeded maximum size" - ); - - return + return Err(Error::POVSizeExceeded(encoded_size, validation_data.max_pov_size as usize)) } pov @@ -574,18 +522,7 @@ async fn construct_and_distribute_receipt( &validation_code_hash, ); - let erasure_root = match erasure_root(n_validators, validation_data, pov.clone()) { - Ok(erasure_root) => erasure_root, - Err(err) => { - gum::error!( - target: LOG_TARGET, - para_id = %para_id, - err = ?err, - "failed to calculate erasure root", - ); - return - }, - }; + let erasure_root = erasure_root(n_validators, validation_data, pov.clone())?; let commitments = CandidateCommitments { upward_messages: collation.upward_messages, @@ -596,35 +533,67 @@ async fn construct_and_distribute_receipt( hrmp_watermark: collation.hrmp_watermark, }; - let ccr = CandidateReceipt { - commitments_hash: commitments.hash(), - descriptor: CandidateDescriptor { - signature: key.sign(&signature_payload), - para_id, - relay_parent, - collator: key.public(), - persisted_validation_data_hash, - pov_hash, - erasure_root, - para_head: commitments.head_data.hash(), - validation_code_hash, + let receipt = if v2_receipts { + let ccr = CommittedCandidateReceiptV2 { + descriptor: CandidateDescriptorV2::new( + para_id, + relay_parent, + core_index, + session_index, + persisted_validation_data_hash, + pov_hash, + erasure_root, + commitments.head_data.hash(), + validation_code_hash, + ), + commitments, + }; + + ccr.check_core_index(&transposed_claim_queue) + .map_err(Error::CandidateReceiptCheck)?; + + ccr.to_plain() + } else { + if commitments.selected_core().is_some() { + gum::warn!( + target: LOG_TARGET, + ?pov_hash, + ?relay_parent, + para_id = %para_id, + "Candidate commitments contain UMP signal without v2 receipts being enabled.", + ); + } + CandidateReceipt { + commitments_hash: commitments.hash(), + descriptor: CandidateDescriptor { + signature: key.sign(&signature_payload), + para_id, + relay_parent, + collator: key.public(), + persisted_validation_data_hash, + pov_hash, + erasure_root, + para_head: commitments.head_data.hash(), + validation_code_hash, + } + .into(), } - .into(), }; gum::debug!( target: LOG_TARGET, - candidate_hash = ?ccr.hash(), + candidate_hash = ?receipt.hash(), ?pov_hash, ?relay_parent, para_id = %para_id, + ?core_index, "candidate is generated", ); metrics.on_collation_generated(); sender .send_message(CollatorProtocolMessage::DistributeCollation { - candidate_receipt: ccr, + candidate_receipt: receipt, parent_head_data_hash, pov, parent_head_data, @@ -632,40 +601,15 @@ async fn construct_and_distribute_receipt( core_index, }) .await; -} -async fn obtain_validation_code_hash_with_assumption( - relay_parent: Hash, - para_id: ParaId, - assumption: OccupiedCoreAssumption, - sender: &mut impl overseer::CollationGenerationSenderTrait, -) -> crate::error::Result> { - match request_validation_code_hash(relay_parent, para_id, assumption, sender) - .await - .await? - { - Ok(Some(v)) => Ok(Some(v)), - Ok(None) => Ok(None), - Err(RuntimeApiError::NotSupported { .. }) => { - match request_validation_code(relay_parent, para_id, assumption, sender).await.await? { - Ok(Some(v)) => Ok(Some(v.hash())), - Ok(None) => Ok(None), - Err(e) => { - // We assume that the `validation_code` API is always available, so any error - // is unexpected. - Err(e.into()) - }, - } - }, - Err(e @ RuntimeApiError::Execution { .. }) => Err(e.into()), - } + Ok(()) } fn erasure_root( n_validators: usize, persisted_validation: PersistedValidationData, pov: PoV, -) -> crate::error::Result { +) -> Result { let available_data = AvailableData { validation_data: persisted_validation, pov: Arc::new(pov) }; diff --git a/polkadot/node/collation-generation/src/metrics.rs b/polkadot/node/collation-generation/src/metrics.rs index c7690ec82c4f..80566dcd6fa1 100644 --- a/polkadot/node/collation-generation/src/metrics.rs +++ b/polkadot/node/collation-generation/src/metrics.rs @@ -19,9 +19,7 @@ use polkadot_node_subsystem_util::metrics::{self, prometheus}; #[derive(Clone)] pub(crate) struct MetricsInner { pub(crate) collations_generated_total: prometheus::Counter, - pub(crate) new_activations_overall: prometheus::Histogram, - pub(crate) new_activations_per_relay_parent: prometheus::Histogram, - pub(crate) new_activations_per_availability_core: prometheus::Histogram, + pub(crate) new_activation: prometheus::Histogram, pub(crate) submit_collation: prometheus::Histogram, } @@ -37,26 +35,8 @@ impl Metrics { } /// Provide a timer for new activations which updates on drop. - pub fn time_new_activations(&self) -> Option { - self.0.as_ref().map(|metrics| metrics.new_activations_overall.start_timer()) - } - - /// Provide a timer per relay parents which updates on drop. - pub fn time_new_activations_relay_parent( - &self, - ) -> Option { - self.0 - .as_ref() - .map(|metrics| metrics.new_activations_per_relay_parent.start_timer()) - } - - /// Provide a timer per availability core which updates on drop. - pub fn time_new_activations_availability_core( - &self, - ) -> Option { - self.0 - .as_ref() - .map(|metrics| metrics.new_activations_per_availability_core.start_timer()) + pub fn time_new_activation(&self) -> Option { + self.0.as_ref().map(|metrics| metrics.new_activation.start_timer()) } /// Provide a timer for submitting a collation which updates on drop. @@ -71,44 +51,22 @@ impl metrics::Metrics for Metrics { collations_generated_total: prometheus::register( prometheus::Counter::new( "polkadot_parachain_collations_generated_total", - "Number of collations generated." - )?, - registry, - )?, - new_activations_overall: prometheus::register( - prometheus::Histogram::with_opts( - prometheus::HistogramOpts::new( - "polkadot_parachain_collation_generation_new_activations", - "Time spent within fn handle_new_activations", - ) - )?, - registry, - )?, - new_activations_per_relay_parent: prometheus::register( - prometheus::Histogram::with_opts( - prometheus::HistogramOpts::new( - "polkadot_parachain_collation_generation_per_relay_parent", - "Time spent handling a particular relay parent within fn handle_new_activations" - ) + "Number of collations generated.", )?, registry, )?, - new_activations_per_availability_core: prometheus::register( - prometheus::Histogram::with_opts( - prometheus::HistogramOpts::new( - "polkadot_parachain_collation_generation_per_availability_core", - "Time spent handling a particular availability core for a relay parent in fn handle_new_activations", - ) - )?, + new_activation: prometheus::register( + prometheus::Histogram::with_opts(prometheus::HistogramOpts::new( + "polkadot_parachain_collation_generation_new_activations", + "Time spent within fn handle_new_activation", + ))?, registry, )?, submit_collation: prometheus::register( - prometheus::Histogram::with_opts( - prometheus::HistogramOpts::new( - "polkadot_parachain_collation_generation_submit_collation", - "Time spent preparing and submitting a collation to the network protocol", - ) - )?, + prometheus::Histogram::with_opts(prometheus::HistogramOpts::new( + "polkadot_parachain_collation_generation_submit_collation", + "Time spent preparing and submitting a collation to the network protocol", + ))?, registry, )?, }; diff --git a/polkadot/node/collation-generation/src/tests.rs b/polkadot/node/collation-generation/src/tests.rs index 78b35fde0ea2..f81c14cdf8f9 100644 --- a/polkadot/node/collation-generation/src/tests.rs +++ b/polkadot/node/collation-generation/src/tests.rs @@ -17,26 +17,20 @@ use super::*; use assert_matches::assert_matches; use futures::{ - lock::Mutex, task::{Context as FuturesContext, Poll}, - Future, + Future, StreamExt, }; use polkadot_node_primitives::{BlockData, Collation, CollationResult, MaybeCompressedPoV, PoV}; use polkadot_node_subsystem::{ - errors::RuntimeApiError, messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest}, ActivatedLeaf, }; -use polkadot_node_subsystem_test_helpers::{subsystem_test_harness, TestSubsystemContextHandle}; +use polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; use polkadot_node_subsystem_util::TimeoutExt; use polkadot_primitives::{ - vstaging::async_backing::{BackingState, CandidatePendingAvailability}, - AsyncBackingParams, BlockNumber, CollatorPair, HeadData, PersistedValidationData, - ScheduledCore, ValidationCode, -}; -use polkadot_primitives_test_helpers::{ - dummy_candidate_descriptor_v2, dummy_hash, dummy_head_data, dummy_validator, make_candidate, + node_features, vstaging::CandidateDescriptorVersion, CollatorPair, PersistedValidationData, }; +use polkadot_primitives_test_helpers::dummy_head_data; use rstest::rstest; use sp_keyring::sr25519::Keyring as Sr25519Keyring; use std::{ @@ -63,7 +57,7 @@ fn test_harness>(test: impl FnOnce(VirtualOv async move { let mut virtual_overseer = test_fut.await; // Ensure we have handled all responses. - if let Ok(Some(msg)) = virtual_overseer.rx.try_next() { + if let Some(msg) = virtual_overseer.rx.next().timeout(TIMEOUT).await { panic!("Did not handle all responses: {:?}", msg); } // Conclude. @@ -85,20 +79,6 @@ fn test_collation() -> Collation { } } -fn test_collation_compressed() -> Collation { - let mut collation = test_collation(); - let compressed = collation.proof_of_validity.clone().into_compressed(); - collation.proof_of_validity = MaybeCompressedPoV::Compressed(compressed); - collation -} - -fn test_validation_data() -> PersistedValidationData { - let mut persisted_validation_data = PersistedValidationData::default(); - persisted_validation_data.max_pov_size = 1024; - persisted_validation_data -} - -// Box + Unpin + Send struct TestCollator; impl Future for TestCollator { @@ -137,531 +117,11 @@ fn test_config_no_collator>(para_id: Id) -> CollationGeneration } } -fn scheduled_core_for>(para_id: Id) -> ScheduledCore { - ScheduledCore { para_id: para_id.into(), collator: None } -} - -fn dummy_candidate_pending_availability( - para_id: ParaId, - candidate_relay_parent: Hash, - relay_parent_number: BlockNumber, -) -> CandidatePendingAvailability { - let (candidate, _pvd) = make_candidate( - candidate_relay_parent, - relay_parent_number, - para_id, - dummy_head_data(), - HeadData(vec![1]), - ValidationCode(vec![1, 2, 3]).hash(), - ); - let candidate_hash = candidate.hash(); - - CandidatePendingAvailability { - candidate_hash, - descriptor: candidate.descriptor, - commitments: candidate.commitments, - relay_parent_number, - max_pov_size: 5 * 1024 * 1024, - } -} - -fn dummy_backing_state(pending_availability: Vec) -> BackingState { - let constraints = helpers::dummy_constraints( - 0, - vec![0], - dummy_head_data(), - ValidationCodeHash::from(Hash::repeat_byte(42)), - ); - - BackingState { constraints, pending_availability } -} - -#[rstest] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] -fn requests_availability_per_relay_parent(#[case] runtime_version: u32) { - let activated_hashes: Vec = - vec![[1; 32].into(), [4; 32].into(), [9; 32].into(), [16; 32].into()]; - - let requested_availability_cores = Arc::new(Mutex::new(Vec::new())); - - let overseer_requested_availability_cores = requested_availability_cores.clone(); - let overseer = |mut handle: TestSubsystemContextHandle| async move { - loop { - match handle.try_recv().await { - None => break, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::AvailabilityCores(tx)))) => { - overseer_requested_availability_cores.lock().await.push(hash); - tx.send(Ok(vec![])).unwrap(); - } - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(_hash, RuntimeApiRequest::Validators(tx)))) => { - tx.send(Ok(vec![dummy_validator(); 3])).unwrap(); - } - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::AsyncBackingParams( - tx, - ), - ))) => { - tx.send(Err(RuntimeApiError::NotSupported { runtime_api_name: "doesnt_matter" })).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::Version(tx), - ))) => { - tx.send(Ok(runtime_version)).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::ClaimQueue(tx), - ))) if runtime_version >= RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT => { - tx.send(Ok(BTreeMap::new())).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::ParaBackingState(_para_id, tx), - ))) => { - tx.send(Ok(Some(dummy_backing_state(vec![])))).unwrap(); - }, - Some(msg) => panic!("didn't expect any other overseer requests given no availability cores; got {:?}", msg), - } - } - }; - - let subsystem_activated_hashes = activated_hashes.clone(); - subsystem_test_harness(overseer, |mut ctx| async move { - handle_new_activations( - Arc::new(test_config(123u32)), - subsystem_activated_hashes, - &mut ctx, - Metrics(None), - ) - .await - .unwrap(); - }); - - let mut requested_availability_cores = Arc::try_unwrap(requested_availability_cores) - .expect("overseer should have shut down by now") - .into_inner(); - requested_availability_cores.sort(); - - assert_eq!(requested_availability_cores, activated_hashes); -} - -#[rstest] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] -fn requests_validation_data_for_scheduled_matches(#[case] runtime_version: u32) { - let activated_hashes: Vec = vec![ - Hash::repeat_byte(1), - Hash::repeat_byte(4), - Hash::repeat_byte(9), - Hash::repeat_byte(16), - ]; - - let requested_validation_data = Arc::new(Mutex::new(Vec::new())); - - let overseer_requested_validation_data = requested_validation_data.clone(); - let overseer = |mut handle: TestSubsystemContextHandle| async move { - loop { - match handle.try_recv().await { - None => break, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::AvailabilityCores(tx), - ))) => { - tx.send(Ok(vec![ - CoreState::Free, - // this is weird, see explanation below - CoreState::Scheduled(scheduled_core_for( - (hash.as_fixed_bytes()[0] * 4) as u32, - )), - CoreState::Scheduled(scheduled_core_for( - (hash.as_fixed_bytes()[0] * 5) as u32, - )), - ])) - .unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::PersistedValidationData( - _para_id, - _occupied_core_assumption, - tx, - ), - ))) => { - overseer_requested_validation_data.lock().await.push(hash); - tx.send(Ok(None)).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::Validators(tx), - ))) => { - tx.send(Ok(vec![dummy_validator(); 3])).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::AsyncBackingParams(tx), - ))) => { - tx.send(Err(RuntimeApiError::NotSupported { - runtime_api_name: "doesnt_matter", - })) - .unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::Version(tx), - ))) => { - tx.send(Ok(runtime_version)).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::ClaimQueue(tx), - ))) if runtime_version >= RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT => { - tx.send(Ok(BTreeMap::new())).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::ParaBackingState(_para_id, tx), - ))) => { - tx.send(Ok(Some(dummy_backing_state(vec![])))).unwrap(); - }, - Some(msg) => { - panic!("didn't expect any other overseer requests; got {:?}", msg) - }, - } - } - }; - - subsystem_test_harness(overseer, |mut ctx| async move { - handle_new_activations( - Arc::new(test_config(16)), - activated_hashes, - &mut ctx, - Metrics(None), - ) - .await - .unwrap(); - }); - - let requested_validation_data = Arc::try_unwrap(requested_validation_data) - .expect("overseer should have shut down by now") - .into_inner(); - - // the only activated hash should be from the 4 hash: - // each activated hash generates two scheduled cores: one with its value * 4, one with its value - // * 5 given that the test configuration has a `para_id` of 16, there's only one way to get that - // value: with the 4 hash. - assert_eq!(requested_validation_data, vec![[4; 32].into()]); -} - -#[rstest] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] -fn sends_distribute_collation_message(#[case] runtime_version: u32) { - let activated_hashes: Vec = vec![ - Hash::repeat_byte(1), - Hash::repeat_byte(4), - Hash::repeat_byte(9), - Hash::repeat_byte(16), - ]; - - // empty vec doesn't allocate on the heap, so it's ok we throw it away - let to_collator_protocol = Arc::new(Mutex::new(Vec::new())); - let inner_to_collator_protocol = to_collator_protocol.clone(); - - let overseer = |mut handle: TestSubsystemContextHandle| async move { - loop { - match handle.try_recv().await { - None => break, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::AvailabilityCores(tx), - ))) => { - tx.send(Ok(vec![ - CoreState::Free, - // this is weird, see explanation below - CoreState::Scheduled(scheduled_core_for( - (hash.as_fixed_bytes()[0] * 4) as u32, - )), - CoreState::Scheduled(scheduled_core_for( - (hash.as_fixed_bytes()[0] * 5) as u32, - )), - ])) - .unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::PersistedValidationData( - _para_id, - _occupied_core_assumption, - tx, - ), - ))) => { - tx.send(Ok(Some(test_validation_data()))).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::Validators(tx), - ))) => { - tx.send(Ok(vec![dummy_validator(); 3])).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::ValidationCodeHash( - _para_id, - OccupiedCoreAssumption::Free, - tx, - ), - ))) => { - tx.send(Ok(Some(ValidationCode(vec![1, 2, 3]).hash()))).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::AsyncBackingParams(tx), - ))) => { - tx.send(Err(RuntimeApiError::NotSupported { - runtime_api_name: "doesnt_matter", - })) - .unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::Version(tx), - ))) => { - tx.send(Ok(runtime_version)).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::ClaimQueue(tx), - ))) if runtime_version >= RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT => { - tx.send(Ok(BTreeMap::new())).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::ParaBackingState(_para_id, tx), - ))) => { - tx.send(Ok(Some(dummy_backing_state(vec![])))).unwrap(); - }, - Some(msg @ AllMessages::CollatorProtocol(_)) => { - inner_to_collator_protocol.lock().await.push(msg); - }, - Some(msg) => { - panic!("didn't expect any other overseer requests; got {:?}", msg) - }, - } - } - }; - - let config = Arc::new(test_config(16)); - let subsystem_config = config.clone(); - - subsystem_test_harness(overseer, |mut ctx| async move { - handle_new_activations(subsystem_config, activated_hashes, &mut ctx, Metrics(None)) - .await - .unwrap(); - }); - - let mut to_collator_protocol = Arc::try_unwrap(to_collator_protocol) - .expect("subsystem should have shut down by now") - .into_inner(); - - // we expect a single message to be sent, containing a candidate receipt. - // we don't care too much about the `commitments_hash` right now, but let's ensure that we've - // calculated the correct descriptor - let expect_pov_hash = test_collation_compressed().proof_of_validity.into_compressed().hash(); - let expect_validation_data_hash = test_validation_data().hash(); - let expect_relay_parent = Hash::repeat_byte(4); - let expect_validation_code_hash = ValidationCode(vec![1, 2, 3]).hash(); - let expect_payload = collator_signature_payload( - &expect_relay_parent, - &config.para_id, - &expect_validation_data_hash, - &expect_pov_hash, - &expect_validation_code_hash, - ); - let expect_descriptor = CandidateDescriptor { - signature: config.key.sign(&expect_payload), - para_id: config.para_id, - relay_parent: expect_relay_parent, - collator: config.key.public(), - persisted_validation_data_hash: expect_validation_data_hash, - pov_hash: expect_pov_hash, - erasure_root: dummy_hash(), // this isn't something we're checking right now - para_head: test_collation().head_data.hash(), - validation_code_hash: expect_validation_code_hash, - }; - - assert_eq!(to_collator_protocol.len(), 1); - match AllMessages::from(to_collator_protocol.pop().unwrap()) { - AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation { - candidate_receipt, - .. - }) => { - let CandidateReceipt { descriptor, .. } = candidate_receipt; - // signature generation is non-deterministic, so we can't just assert that the - // expected descriptor is correct. What we can do is validate that the produced - // descriptor has a valid signature, then just copy in the generated signature - // and check the rest of the fields for equality. - assert!(CollatorPair::verify( - &descriptor.signature().unwrap(), - &collator_signature_payload( - &descriptor.relay_parent(), - &descriptor.para_id(), - &descriptor.persisted_validation_data_hash(), - &descriptor.pov_hash(), - &descriptor.validation_code_hash(), - ) - .as_ref(), - &descriptor.collator().unwrap(), - )); - let expect_descriptor = { - let mut expect_descriptor = expect_descriptor; - expect_descriptor.signature = descriptor.signature().clone().unwrap(); - expect_descriptor.erasure_root = descriptor.erasure_root(); - expect_descriptor.into() - }; - assert_eq!(descriptor, expect_descriptor); - }, - _ => panic!("received wrong message type"), - } -} - -#[rstest] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] -fn fallback_when_no_validation_code_hash_api(#[case] runtime_version: u32) { - // This is a variant of the above test, but with the validation code hash API disabled. - - let activated_hashes: Vec = vec![ - Hash::repeat_byte(1), - Hash::repeat_byte(4), - Hash::repeat_byte(9), - Hash::repeat_byte(16), - ]; - - // empty vec doesn't allocate on the heap, so it's ok we throw it away - let to_collator_protocol = Arc::new(Mutex::new(Vec::new())); - let inner_to_collator_protocol = to_collator_protocol.clone(); - - let overseer = |mut handle: TestSubsystemContextHandle| async move { - loop { - match handle.try_recv().await { - None => break, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::AvailabilityCores(tx), - ))) => { - tx.send(Ok(vec![ - CoreState::Free, - CoreState::Scheduled(scheduled_core_for( - (hash.as_fixed_bytes()[0] * 4) as u32, - )), - CoreState::Scheduled(scheduled_core_for( - (hash.as_fixed_bytes()[0] * 5) as u32, - )), - ])) - .unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::PersistedValidationData( - _para_id, - _occupied_core_assumption, - tx, - ), - ))) => { - tx.send(Ok(Some(test_validation_data()))).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::Validators(tx), - ))) => { - tx.send(Ok(vec![dummy_validator(); 3])).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::ValidationCodeHash( - _para_id, - OccupiedCoreAssumption::Free, - tx, - ), - ))) => { - tx.send(Err(RuntimeApiError::NotSupported { - runtime_api_name: "validation_code_hash", - })) - .unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::ValidationCode(_para_id, OccupiedCoreAssumption::Free, tx), - ))) => { - tx.send(Ok(Some(ValidationCode(vec![1, 2, 3])))).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::AsyncBackingParams(tx), - ))) => { - tx.send(Err(RuntimeApiError::NotSupported { - runtime_api_name: "doesnt_matter", - })) - .unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::Version(tx), - ))) => { - tx.send(Ok(runtime_version)).unwrap(); - }, - Some(msg @ AllMessages::CollatorProtocol(_)) => { - inner_to_collator_protocol.lock().await.push(msg); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::ClaimQueue(tx), - ))) if runtime_version >= RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT => { - tx.send(Ok(Default::default())).unwrap(); - }, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::ParaBackingState(_para_id, tx), - ))) => { - tx.send(Ok(Some(dummy_backing_state(vec![])))).unwrap(); - }, - Some(msg) => { - panic!("didn't expect any other overseer requests; got {:?}", msg) - }, - } - } - }; - - let config = Arc::new(test_config(16u32)); - let subsystem_config = config.clone(); - - // empty vec doesn't allocate on the heap, so it's ok we throw it away - subsystem_test_harness(overseer, |mut ctx| async move { - handle_new_activations(subsystem_config, activated_hashes, &mut ctx, Metrics(None)) - .await - .unwrap(); - }); - - let to_collator_protocol = Arc::try_unwrap(to_collator_protocol) - .expect("subsystem should have shut down by now") - .into_inner(); - - let expect_validation_code_hash = ValidationCode(vec![1, 2, 3]).hash(); - - assert_eq!(to_collator_protocol.len(), 1); - match &to_collator_protocol[0] { - AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation { - candidate_receipt, - .. - }) => { - let CandidateReceipt { descriptor, .. } = candidate_receipt; - assert_eq!(expect_validation_code_hash, descriptor.validation_code_hash()); - }, - _ => panic!("received wrong message type"), - } +fn node_features_with_v2_enabled() -> NodeFeatures { + let mut node_features = NodeFeatures::new(); + node_features.resize(node_features::FeatureIndex::CandidateReceiptV2 as usize + 1, false); + node_features.set(node_features::FeatureIndex::CandidateReceiptV2 as u8 as usize, true); + node_features } #[test] @@ -717,31 +177,15 @@ fn submit_collation_leads_to_distribution() { }) .await; - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request(rp, RuntimeApiRequest::Validators(tx))) => { - assert_eq!(rp, relay_parent); - let _ = tx.send(Ok(vec![ - Sr25519Keyring::Alice.public().into(), - Sr25519Keyring::Bob.public().into(), - Sr25519Keyring::Charlie.public().into(), - ])); - } - ); - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request(rp, RuntimeApiRequest::PersistedValidationData(id, a, tx))) => { - assert_eq!(rp, relay_parent); - assert_eq!(id, para_id); - assert_eq!(a, OccupiedCoreAssumption::TimedOut); - - // Candidate receipt should be constructed with the real parent head. - let mut pvd = expected_pvd.clone(); - pvd.parent_head = vec![4, 5, 6].into(); - let _ = tx.send(Ok(Some(pvd))); - } - ); + helpers::handle_runtime_calls_on_submit_collation( + &mut virtual_overseer, + relay_parent, + para_id, + expected_pvd.clone(), + NodeFeatures::EMPTY, + Default::default(), + ) + .await; assert_matches!( overseer_recv(&mut virtual_overseer).await, @@ -762,78 +206,16 @@ fn submit_collation_leads_to_distribution() { }); } -// There is one core in `Occupied` state and async backing is enabled. On new head activation -// `CollationGeneration` should produce and distribute a new collation. -#[rstest] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] -fn distribute_collation_for_occupied_core_with_async_backing_enabled(#[case] runtime_version: u32) { - let activated_hash: Hash = [1; 32].into(); - let para_id = ParaId::from(5); - - // One core, in occupied state. The data in `CoreState` and `ClaimQueue` should match. - let cores: Vec = - vec![CoreState::Occupied(polkadot_primitives::vstaging::OccupiedCore { - next_up_on_available: Some(ScheduledCore { para_id, collator: None }), - occupied_since: 1, - time_out_at: 10, - next_up_on_time_out: Some(ScheduledCore { para_id, collator: None }), - availability: Default::default(), // doesn't matter - group_responsible: polkadot_primitives::GroupIndex(0), - candidate_hash: Default::default(), - candidate_descriptor: dummy_candidate_descriptor_v2(dummy_hash()), - })]; - let claim_queue = BTreeMap::from([(CoreIndex::from(0), VecDeque::from([para_id]))]).into(); - - test_harness(|mut virtual_overseer| async move { - helpers::initialize_collator(&mut virtual_overseer, para_id).await; - helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; - - let pending_availability = - vec![dummy_candidate_pending_availability(para_id, activated_hash, 1)]; - helpers::handle_runtime_calls_on_new_head_activation( - &mut virtual_overseer, - activated_hash, - AsyncBackingParams { max_candidate_depth: 1, allowed_ancestry_len: 1 }, - cores, - runtime_version, - claim_queue, - ) - .await; - helpers::handle_cores_processing_for_a_leaf( - &mut virtual_overseer, - activated_hash, - para_id, - // `CoreState` is `Occupied` => `OccupiedCoreAssumption` is `Included` - OccupiedCoreAssumption::Included, - 1, - pending_availability, - runtime_version, - ) - .await; - - virtual_overseer - }); -} - #[test] -fn distribute_collation_for_occupied_core_pre_async_backing() { +fn distribute_collation_only_for_assigned_para_id_at_offset_0() { let activated_hash: Hash = [1; 32].into(); let para_id = ParaId::from(5); - let total_cores = 3; - - // Use runtime version before async backing - let runtime_version = RuntimeApiRequest::ASYNC_BACKING_STATE_RUNTIME_REQUIREMENT - 1; - let cores = (0..total_cores) + let claim_queue = (0..=5) .into_iter() - .map(|_idx| CoreState::Scheduled(ScheduledCore { para_id, collator: None })) - .collect::>(); - - let claim_queue = cores - .iter() - .enumerate() - .map(|(idx, _core)| (CoreIndex::from(idx as u32), VecDeque::from([para_id]))) + // Set all cores assigned to para_id 5 at the second and third depths. This shouldn't + // matter. + .map(|idx| (CoreIndex(idx), VecDeque::from([ParaId::from(idx), para_id, para_id]))) .collect::>(); test_harness(|mut virtual_overseer| async move { @@ -842,10 +224,8 @@ fn distribute_collation_for_occupied_core_pre_async_backing() { helpers::handle_runtime_calls_on_new_head_activation( &mut virtual_overseer, activated_hash, - AsyncBackingParams { max_candidate_depth: 1, allowed_ancestry_len: 1 }, - cores, - runtime_version, claim_queue, + NodeFeatures::EMPTY, ) .await; @@ -853,11 +233,7 @@ fn distribute_collation_for_occupied_core_pre_async_backing() { &mut virtual_overseer, activated_hash, para_id, - // `CoreState` is `Free` => `OccupiedCoreAssumption` is `Free` - OccupiedCoreAssumption::Free, - total_cores, - vec![], - runtime_version, + vec![5], // Only core 5 is assigned to paraid 5. ) .await; @@ -865,48 +241,22 @@ fn distribute_collation_for_occupied_core_pre_async_backing() { }); } -// There are variable number of cores of cores in `Occupied` state and async backing is enabled. -// On new head activation `CollationGeneration` should produce and distribute a new collation -// with proper assumption about the para candidate chain availability at next block. +// There are variable number of cores assigned to the paraid. +// On new head activation `CollationGeneration` should produce and distribute the right number of +// new collations with proper assumption about the para candidate chain availability at next block. #[rstest] #[case(0)] #[case(1)] #[case(2)] -fn distribute_collation_for_occupied_cores_with_async_backing_enabled_and_elastic_scaling( - #[case] candidates_pending_avail: u32, -) { +#[case(3)] +fn distribute_collation_with_elastic_scaling(#[case] total_cores: u32) { let activated_hash: Hash = [1; 32].into(); let para_id = ParaId::from(5); - // Using latest runtime with the fancy claim queue exposed. - let runtime_version = RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT; - let cores = (0..3) + let claim_queue = (0..total_cores) .into_iter() - .map(|idx| { - CoreState::Occupied(polkadot_primitives::vstaging::OccupiedCore { - next_up_on_available: Some(ScheduledCore { para_id, collator: None }), - occupied_since: 0, - time_out_at: 10, - next_up_on_time_out: Some(ScheduledCore { para_id, collator: None }), - availability: Default::default(), // doesn't matter - group_responsible: polkadot_primitives::GroupIndex(idx as u32), - candidate_hash: Default::default(), - candidate_descriptor: dummy_candidate_descriptor_v2(dummy_hash()), - }) - }) - .collect::>(); - - let pending_availability = (0..candidates_pending_avail) - .into_iter() - .map(|_idx| dummy_candidate_pending_availability(para_id, activated_hash, 0)) - .collect::>(); - - let claim_queue = cores - .iter() - .enumerate() - .map(|(idx, _core)| (CoreIndex::from(idx as u32), VecDeque::from([para_id]))) + .map(|idx| (CoreIndex(idx), VecDeque::from([para_id]))) .collect::>(); - let total_cores = cores.len(); test_harness(|mut virtual_overseer| async move { helpers::initialize_collator(&mut virtual_overseer, para_id).await; @@ -914,10 +264,8 @@ fn distribute_collation_for_occupied_cores_with_async_backing_enabled_and_elasti helpers::handle_runtime_calls_on_new_head_activation( &mut virtual_overseer, activated_hash, - AsyncBackingParams { max_candidate_depth: 1, allowed_ancestry_len: 1 }, - cores, - runtime_version, claim_queue, + NodeFeatures::EMPTY, ) .await; @@ -925,16 +273,7 @@ fn distribute_collation_for_occupied_cores_with_async_backing_enabled_and_elasti &mut virtual_overseer, activated_hash, para_id, - // if at least 1 cores is occupied => `OccupiedCoreAssumption` is `Included` - // else assumption is `Free`. - if candidates_pending_avail > 0 { - OccupiedCoreAssumption::Included - } else { - OccupiedCoreAssumption::Free - }, - total_cores, - pending_availability, - runtime_version, + (0..total_cores).collect(), ) .await; @@ -942,136 +281,128 @@ fn distribute_collation_for_occupied_cores_with_async_backing_enabled_and_elasti }); } -// There are variable number of cores of cores in `Free` state and async backing is enabled. -// On new head activation `CollationGeneration` should produce and distribute a new collation -// with proper assumption about the para candidate chain availability at next block. #[rstest] -#[case(0)] -#[case(1)] -#[case(2)] -fn distribute_collation_for_free_cores_with_async_backing_enabled_and_elastic_scaling( - #[case] total_cores: usize, -) { - let activated_hash: Hash = [1; 32].into(); +#[case(true)] +#[case(false)] +fn test_candidate_receipt_versioning(#[case] v2_receipts: bool) { + let relay_parent = Hash::repeat_byte(0); + let validation_code_hash = ValidationCodeHash::from(Hash::repeat_byte(42)); + let parent_head = dummy_head_data(); let para_id = ParaId::from(5); - // Using latest runtime with the fancy claim queue exposed. - let runtime_version = RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT; - - let cores = (0..total_cores) - .into_iter() - .map(|_idx| CoreState::Scheduled(ScheduledCore { para_id, collator: None })) - .collect::>(); - - let claim_queue = cores - .iter() - .enumerate() - .map(|(idx, _core)| (CoreIndex::from(idx as u32), VecDeque::from([para_id]))) - .collect::>(); + let expected_pvd = PersistedValidationData { + parent_head: parent_head.clone(), + relay_parent_number: 10, + relay_parent_storage_root: Hash::repeat_byte(1), + max_pov_size: 1024, + }; + let node_features = + if v2_receipts { node_features_with_v2_enabled() } else { NodeFeatures::EMPTY }; + let expected_descriptor_version = + if v2_receipts { CandidateDescriptorVersion::V2 } else { CandidateDescriptorVersion::V1 }; test_harness(|mut virtual_overseer| async move { - helpers::initialize_collator(&mut virtual_overseer, para_id).await; - helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; - helpers::handle_runtime_calls_on_new_head_activation( - &mut virtual_overseer, - activated_hash, - AsyncBackingParams { max_candidate_depth: 1, allowed_ancestry_len: 1 }, - cores, - runtime_version, - claim_queue, - ) - .await; + virtual_overseer + .send(FromOrchestra::Communication { + msg: CollationGenerationMessage::Initialize(test_config_no_collator(para_id)), + }) + .await; - helpers::handle_cores_processing_for_a_leaf( + virtual_overseer + .send(FromOrchestra::Communication { + msg: CollationGenerationMessage::SubmitCollation(SubmitCollationParams { + relay_parent, + collation: test_collation(), + parent_head: dummy_head_data(), + validation_code_hash, + result_sender: None, + core_index: CoreIndex(0), + }), + }) + .await; + + helpers::handle_runtime_calls_on_submit_collation( &mut virtual_overseer, - activated_hash, + relay_parent, para_id, - // `CoreState` is `Free` => `OccupiedCoreAssumption` is `Free` - OccupiedCoreAssumption::Free, - total_cores, - vec![], - runtime_version, + expected_pvd.clone(), + node_features, + [(CoreIndex(0), [para_id].into_iter().collect())].into_iter().collect(), ) .await; + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation { + candidate_receipt, + parent_head_data_hash, + .. + }) => { + let CandidateReceipt { descriptor, .. } = candidate_receipt; + assert_eq!(parent_head_data_hash, parent_head.hash()); + assert_eq!(descriptor.persisted_validation_data_hash(), expected_pvd.hash()); + assert_eq!(descriptor.para_head(), dummy_head_data().hash()); + assert_eq!(descriptor.validation_code_hash(), validation_code_hash); + // Check that the right version was indeed used. + assert_eq!(descriptor.version(), expected_descriptor_version); + } + ); + virtual_overseer }); } -// There is one core in `Occupied` state and async backing is disabled. On new head activation -// no new collation should be generated. -#[rstest] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] -fn no_collation_is_distributed_for_occupied_core_with_async_backing_disabled( - #[case] runtime_version: u32, -) { - let activated_hash: Hash = [1; 32].into(); +#[test] +fn v2_receipts_failed_core_index_check() { + let relay_parent = Hash::repeat_byte(0); + let validation_code_hash = ValidationCodeHash::from(Hash::repeat_byte(42)); + let parent_head = dummy_head_data(); let para_id = ParaId::from(5); - - // One core, in occupied state. The data in `CoreState` and `ClaimQueue` should match. - let cores: Vec = - vec![CoreState::Occupied(polkadot_primitives::vstaging::OccupiedCore { - next_up_on_available: Some(ScheduledCore { para_id, collator: None }), - occupied_since: 1, - time_out_at: 10, - next_up_on_time_out: Some(ScheduledCore { para_id, collator: None }), - availability: Default::default(), // doesn't matter - group_responsible: polkadot_primitives::GroupIndex(0), - candidate_hash: Default::default(), - candidate_descriptor: dummy_candidate_descriptor_v2(dummy_hash()), - })]; - let claim_queue = BTreeMap::from([(CoreIndex::from(0), VecDeque::from([para_id]))]).into(); + let expected_pvd = PersistedValidationData { + parent_head: parent_head.clone(), + relay_parent_number: 10, + relay_parent_storage_root: Hash::repeat_byte(1), + max_pov_size: 1024, + }; test_harness(|mut virtual_overseer| async move { - helpers::initialize_collator(&mut virtual_overseer, para_id).await; - helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; + virtual_overseer + .send(FromOrchestra::Communication { + msg: CollationGenerationMessage::Initialize(test_config_no_collator(para_id)), + }) + .await; - helpers::handle_runtime_calls_on_new_head_activation( + virtual_overseer + .send(FromOrchestra::Communication { + msg: CollationGenerationMessage::SubmitCollation(SubmitCollationParams { + relay_parent, + collation: test_collation(), + parent_head: dummy_head_data(), + validation_code_hash, + result_sender: None, + core_index: CoreIndex(0), + }), + }) + .await; + + helpers::handle_runtime_calls_on_submit_collation( &mut virtual_overseer, - activated_hash, - AsyncBackingParams { max_candidate_depth: 0, allowed_ancestry_len: 0 }, - cores, - runtime_version, - claim_queue, + relay_parent, + para_id, + expected_pvd.clone(), + node_features_with_v2_enabled(), + // Core index commitment is on core 0 but don't add any assignment for core 0. + [(CoreIndex(1), [para_id].into_iter().collect())].into_iter().collect(), ) .await; + // No collation is distributed. + virtual_overseer }); } - mod helpers { - use polkadot_primitives::{ - async_backing::{Constraints, InboundHrmpLimitations}, - BlockNumber, - }; - use super::*; - - // A set for dummy constraints for `ParaBackingState`` - pub(crate) fn dummy_constraints( - min_relay_parent_number: BlockNumber, - valid_watermarks: Vec, - required_parent: HeadData, - validation_code_hash: ValidationCodeHash, - ) -> Constraints { - Constraints { - min_relay_parent_number, - max_pov_size: 5 * 1024 * 1024, - max_code_size: 1_000_000, - ump_remaining: 10, - ump_remaining_bytes: 1_000, - max_ump_num_per_candidate: 10, - dmp_remaining_messages: vec![], - hrmp_inbound: InboundHrmpLimitations { valid_watermarks }, - hrmp_channels_out: vec![], - max_hrmp_num_per_candidate: 0, - required_parent, - validation_code_hash, - upgrade_restriction: None, - future_validation_code: None, - } - } + use std::collections::{BTreeMap, VecDeque}; // Sends `Initialize` with a collator config pub async fn initialize_collator(virtual_overseer: &mut VirtualOverseer, para_id: ParaId) { @@ -1098,22 +429,18 @@ mod helpers { .await; } - // Handle all runtime calls performed in `handle_new_activations`. Conditionally expects a - // `CLAIM_QUEUE_RUNTIME_REQUIREMENT` call if the passed `runtime_version` is greater or equal to - // `CLAIM_QUEUE_RUNTIME_REQUIREMENT` + // Handle all runtime calls performed in `handle_new_activation`. pub async fn handle_runtime_calls_on_new_head_activation( virtual_overseer: &mut VirtualOverseer, activated_hash: Hash, - async_backing_params: AsyncBackingParams, - cores: Vec, - runtime_version: u32, claim_queue: BTreeMap>, + node_features: NodeFeatures, ) { assert_matches!( overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::AvailabilityCores(tx))) => { + AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::SessionIndexForChild(tx))) => { assert_eq!(hash, activated_hash); - let _ = tx.send(Ok(cores)); + tx.send(Ok(1)).unwrap(); } ); @@ -1121,73 +448,46 @@ mod helpers { overseer_recv(virtual_overseer).await, AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::Validators(tx))) => { assert_eq!(hash, activated_hash); - let _ = tx.send(Ok(vec![ + tx.send(Ok(vec![ Sr25519Keyring::Alice.public().into(), Sr25519Keyring::Bob.public().into(), Sr25519Keyring::Charlie.public().into(), - ])); + ])).unwrap(); } ); - let async_backing_response = - if runtime_version >= RuntimeApiRequest::ASYNC_BACKING_STATE_RUNTIME_REQUIREMENT { - Ok(async_backing_params) - } else { - Err(RuntimeApiError::NotSupported { runtime_api_name: "async_backing_params" }) - }; - assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::AsyncBackingParams( - tx, - ), - )) => { + hash, + RuntimeApiRequest::NodeFeatures(session_index, tx), + )) => { + assert_eq!(1, session_index); assert_eq!(hash, activated_hash); - let _ = tx.send(async_backing_response); + + tx.send(Ok(node_features)).unwrap(); } ); assert_matches!( overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::Version(tx), - )) => { + AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::ClaimQueue(tx))) => { assert_eq!(hash, activated_hash); - let _ = tx.send(Ok(runtime_version)); + tx.send(Ok(claim_queue)).unwrap(); } ); - - if runtime_version == RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT { - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::ClaimQueue(tx), - )) => { - assert_eq!(hash, activated_hash); - let _ = tx.send(Ok(claim_queue.into())); - } - ); - } } - // Handles all runtime requests performed in `handle_new_activations` for the case when a + // Handles all runtime requests performed in `handle_new_activation` for the case when a // collation should be prepared for the new leaf pub async fn handle_cores_processing_for_a_leaf( virtual_overseer: &mut VirtualOverseer, activated_hash: Hash, para_id: ParaId, - expected_occupied_core_assumption: OccupiedCoreAssumption, - cores_assigned: usize, - pending_availability: Vec, - runtime_version: u32, + cores_assigned: Vec, ) { // Expect no messages if no cores is assigned to the para - if cores_assigned == 0 { - assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + if cores_assigned.is_empty() { return } @@ -1201,23 +501,12 @@ mod helpers { max_pov_size: 1024, }; - if runtime_version >= RuntimeApiRequest::ASYNC_BACKING_STATE_RUNTIME_REQUIREMENT { - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::ParaBackingState(p_id, tx)) - ) if parent == activated_hash && p_id == para_id => { - tx.send(Ok(Some(dummy_backing_state(pending_availability)))).unwrap(); - } - ); - } - assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::PersistedValidationData(id, a, tx))) => { assert_eq!(hash, activated_hash); assert_eq!(id, para_id); - assert_eq!(a, expected_occupied_core_assumption); + assert_eq!(a, OccupiedCoreAssumption::Included); let _ = tx.send(Ok(Some(pvd.clone()))); } @@ -1235,20 +524,22 @@ mod helpers { )) => { assert_eq!(hash, activated_hash); assert_eq!(id, para_id); - assert_eq!(assumption, expected_occupied_core_assumption); + assert_eq!(assumption, OccupiedCoreAssumption::Included); let _ = tx.send(Ok(Some(validation_code_hash))); } ); - for _ in 0..cores_assigned { + for core in cores_assigned { assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation{ candidate_receipt, parent_head_data_hash, + core_index, .. }) => { + assert_eq!(CoreIndex(core), core_index); assert_eq!(parent_head_data_hash, parent_head.hash()); assert_eq!(candidate_receipt.descriptor().persisted_validation_data_hash(), pvd.hash()); assert_eq!(candidate_receipt.descriptor().para_head(), dummy_head_data().hash()); @@ -1257,4 +548,69 @@ mod helpers { ); } } + + // Handles all runtime requests performed in `handle_submit_collation` + pub async fn handle_runtime_calls_on_submit_collation( + virtual_overseer: &mut VirtualOverseer, + relay_parent: Hash, + para_id: ParaId, + expected_pvd: PersistedValidationData, + node_features: NodeFeatures, + claim_queue: BTreeMap>, + ) { + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(rp, RuntimeApiRequest::PersistedValidationData(id, a, tx))) => { + assert_eq!(rp, relay_parent); + assert_eq!(id, para_id); + assert_eq!(a, OccupiedCoreAssumption::TimedOut); + + tx.send(Ok(Some(expected_pvd))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::ClaimQueue(tx), + )) => { + assert_eq!(rp, relay_parent); + tx.send(Ok(claim_queue)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(rp, RuntimeApiRequest::SessionIndexForChild(tx))) => { + assert_eq!(rp, relay_parent); + tx.send(Ok(1)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(rp, RuntimeApiRequest::Validators(tx))) => { + assert_eq!(rp, relay_parent); + tx.send(Ok(vec![ + Sr25519Keyring::Alice.public().into(), + Sr25519Keyring::Bob.public().into(), + Sr25519Keyring::Charlie.public().into(), + ])).unwrap(); + } + ); + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::NodeFeatures(session_index, tx), + )) => { + assert_eq!(1, session_index); + assert_eq!(rp, relay_parent); + + tx.send(Ok(node_features.clone())).unwrap(); + } + ); + } } diff --git a/polkadot/primitives/Cargo.toml b/polkadot/primitives/Cargo.toml index a8cd6cb5f4e0..dd269caa2d60 100644 --- a/polkadot/primitives/Cargo.toml +++ b/polkadot/primitives/Cargo.toml @@ -16,6 +16,7 @@ codec = { features = ["bit-vec", "derive"], workspace = true } scale-info = { features = ["bit-vec", "derive", "serde"], workspace = true } log = { workspace = true } serde = { features = ["alloc", "derive"], workspace = true } +thiserror = { workspace = true, optional = true } sp-application-crypto = { features = ["serde"], workspace = true } sp-inherents = { workspace = true } @@ -59,6 +60,7 @@ std = [ "sp-runtime/std", "sp-staking/std", "sp-std/std", + "thiserror", ] runtime-benchmarks = [ "polkadot-parachain-primitives/runtime-benchmarks", diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index 21aab41902be..94b7b200e68f 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -465,19 +465,32 @@ impl CandidateCommitments { /// CandidateReceipt construction errors. #[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(thiserror::Error))] pub enum CandidateReceiptError { /// The specified core index is invalid. + #[cfg_attr(feature = "std", error("The specified core index is invalid"))] InvalidCoreIndex, /// The core index in commitments doesn't match the one in descriptor + #[cfg_attr( + feature = "std", + error("The core index in commitments doesn't match the one in descriptor") + )] CoreIndexMismatch, /// The core selector or claim queue offset is invalid. + #[cfg_attr(feature = "std", error("The core selector or claim queue offset is invalid"))] InvalidSelectedCore, /// The parachain is not assigned to any core at specified claim queue offset. + #[cfg_attr( + feature = "std", + error("The parachain is not assigned to any core at specified claim queue offset") + )] NoAssignment, /// No core was selected. The `SelectCore` commitment is mandatory for /// v2 receipts if parachains has multiple cores assigned. + #[cfg_attr(feature = "std", error("Core selector not present"))] NoCoreSelected, /// Unknown version. + #[cfg_attr(feature = "std", error("Unknown internal version"))] UnknownVersion(InternalVersion), } diff --git a/prdoc/pr_5908.prdoc b/prdoc/pr_5908.prdoc new file mode 100644 index 000000000000..8f05819451a0 --- /dev/null +++ b/prdoc/pr_5908.prdoc @@ -0,0 +1,14 @@ +title: "collation-generation: use v2 receipts" + +doc: + - audience: Node Dev + description: | + Implementation of [RFC 103](/~https://github.com/polkadot-fellows/RFCs/pull/103) for the collation-generation subsystem. + Also removes the usage of AsyncBackingParams. + +crates: + - name: polkadot-node-collation-generation + bump: major + validate: false + - name: polkadot-primitives + bump: minor From 7f80f45217793bde96d0a2a068eae09514d2b671 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Mon, 4 Nov 2024 10:27:02 +0100 Subject: [PATCH 5/6] [eth-rpc] Fixes (#6317) Various fixes for the release of eth-rpc & ah-westend-runtime - Bump asset-hub westend spec version - Fix the status of the Receipt to properly report failed transactions - Fix value conversion between native and eth decimal representation --------- Co-authored-by: GitHub Action --- .../assets/asset-hub-westend/src/lib.rs | 3 +- prdoc/pr_6317.prdoc | 12 +++++ substrate/bin/node/runtime/src/lib.rs | 1 + substrate/client/service/src/lib.rs | 4 +- substrate/frame/revive/rpc/Cargo.toml | 1 + .../revive/rpc/examples/rust/transfer.rs | 15 +++--- .../frame/revive/rpc/revive_chain.metadata | Bin 342591 -> 655430 bytes substrate/frame/revive/rpc/src/cli.rs | 6 +-- substrate/frame/revive/rpc/src/client.rs | 41 ++++++++--------- substrate/frame/revive/rpc/src/tests.rs | 43 ++++++++++++------ substrate/frame/revive/src/evm/runtime.rs | 14 ++++-- substrate/frame/revive/src/exec.rs | 6 ++- substrate/frame/revive/src/lib.rs | 16 +++++-- 13 files changed, 102 insertions(+), 60 deletions(-) create mode 100644 prdoc/pr_6317.prdoc diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 0ed24db97df8..bbd686b5cf50 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -124,7 +124,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westmint"), impl_name: create_runtime_str!("westmint"), authoring_version: 1, - spec_version: 1_016_002, + spec_version: 1_016_004, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, @@ -968,6 +968,7 @@ impl pallet_revive::Config for Runtime { type Debug = (); type Xcm = pallet_xcm::Pallet; type ChainId = ConstU64<420_420_421>; + type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. } impl TryFrom for pallet_revive::Call { diff --git a/prdoc/pr_6317.prdoc b/prdoc/pr_6317.prdoc new file mode 100644 index 000000000000..4034ab3f3012 --- /dev/null +++ b/prdoc/pr_6317.prdoc @@ -0,0 +1,12 @@ +title: eth-rpc fixes +doc: +- audience: Runtime Dev + description: | + Various fixes for the release of eth-rpc & ah-westend-runtime: + - Bump asset-hub westend spec version + - Fix the status of the Receipt to properly report failed transactions + - Fix value conversion between native and eth decimal representation + +crates: +- name: asset-hub-westend-runtime + bump: patch diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 7712d8ba9540..d407aafb452b 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1427,6 +1427,7 @@ impl pallet_revive::Config for Runtime { type Debug = (); type Xcm = (); type ChainId = ConstU64<420_420_420>; + type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. } impl pallet_sudo::Config for Runtime { diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 54e847791cff..3df9020b0418 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -98,7 +98,9 @@ pub use sc_transaction_pool::TransactionPoolOptions; pub use sc_transaction_pool_api::{error::IntoPoolError, InPoolTransaction, TransactionPool}; #[doc(hidden)] pub use std::{ops::Deref, result::Result, sync::Arc}; -pub use task_manager::{SpawnTaskHandle, Task, TaskManager, TaskRegistry, DEFAULT_GROUP_NAME}; +pub use task_manager::{ + SpawnEssentialTaskHandle, SpawnTaskHandle, Task, TaskManager, TaskRegistry, DEFAULT_GROUP_NAME, +}; use tokio::runtime::Handle; const DEFAULT_PROTOCOL_ID: &str = "sup"; diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index e6d8c38c04f0..56db91f920fa 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -77,3 +77,4 @@ example = ["hex", "hex-literal", "rlp", "secp256k1", "subxt-signer"] hex-literal = { workspace = true } pallet-revive-fixtures = { workspace = true } substrate-cli-test-utils = { workspace = true } +subxt-signer = { workspace = true, features = ["unstable-eth"] } diff --git a/substrate/frame/revive/rpc/examples/rust/transfer.rs b/substrate/frame/revive/rpc/examples/rust/transfer.rs index 185ad808e787..b99d48a2f78e 100644 --- a/substrate/frame/revive/rpc/examples/rust/transfer.rs +++ b/substrate/frame/revive/rpc/examples/rust/transfer.rs @@ -26,14 +26,14 @@ async fn main() -> anyhow::Result<()> { let alith = Account::default(); let client = HttpClientBuilder::default().build("http://localhost:8545")?; - let baltathar = Account::from(subxt_signer::eth::dev::baltathar()); - let value = 1_000_000_000_000_000_000u128.into(); // 1 ETH + let ethan = Account::from(subxt_signer::eth::dev::ethan()); + let value = 1_000_000_000_000_000_000_000u128.into(); let print_balance = || async { let balance = client.get_balance(alith.address(), BlockTag::Latest.into()).await?; println!("Alith {:?} balance: {balance:?}", alith.address()); - let balance = client.get_balance(baltathar.address(), BlockTag::Latest.into()).await?; - println!("Baltathar {:?} balance: {balance:?}", baltathar.address()); + let balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?; + println!("ethan {:?} balance: {balance:?}", ethan.address()); anyhow::Result::<()>::Ok(()) }; @@ -41,14 +41,15 @@ async fn main() -> anyhow::Result<()> { println!("\n\n=== Transferring ===\n\n"); let hash = - send_transaction(&alith, &client, value, Bytes::default(), Some(baltathar.address())) - .await?; + send_transaction(&alith, &client, value, Bytes::default(), Some(ethan.address())).await?; println!("Transaction hash: {hash:?}"); - let ReceiptInfo { block_number, gas_used, .. } = wait_for_receipt(&client, hash).await?; + let ReceiptInfo { block_number, gas_used, status, .. } = + wait_for_receipt(&client, hash).await?; println!("Receipt: "); println!("- Block number: {block_number}"); println!("- Gas used: {gas_used}"); + println!("- Success: {status:?}"); print_balance().await?; Ok(()) diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index eef6dd2a0aeaa5b55ef9519aafb2301bd8cd51dc..305d079f9bd86786d1cd00fadac152eb856b26ee 100644 GIT binary patch literal 655430 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�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%0A1e~t}_Qk8dIj$=EL>@d+qeaky*Y_87kl!ZIWR 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%0Aj(4&mO+3j*eJ>XBDj%#06)nLaL4DbBAH?7r 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 literal 342591 zcmeFa4Txk}c|Uwk^_|_?wN`6Quhu5L$=%rdPmOQQP0wuarg!XJx@W2<-G94hdS<$J zXX6Z2x2tYE`(CNh-|G$h z%3`D854(ev=96cHu!JS;4-DttXFqx72K^}}Y#~G*|1VBVh(dm5yYF_qTCf)kyiWLS zciYcTUi7;SPZrI?sylGyq?nYGU3yWJs)3Nx4Zj-KbqWbyF0;@EHB*|^xX^ehY%&q;(jqT zW)_L2mMF>M@bsBEApqzTLQEHCHHe43p5GtX%~sHJ2aVl@b+2F5zt6YbAh4_rSr!FZ z>iYeT+b&CML&%EX^ZM?f<#)}K>5kh4sQmsOw-Dt*85nTeYwhd~f@$?{?beyn3;YCo zO>5t-4O$(qA`8Q(XN4?dYy9n6(E5;hBq~DOHzQ^+<@>~Je!kalby|bgEw9FaE-s&W z>gf?|!WwgVF<+$d``B}gFty`#y?(1vYPNQ~U{I>^KloA>+x||lAm^X=8uRMu3NY?D zAr{a}RaE!sr5v>vad1q9ikfnj&Yc+z8=46Q$ zy#bJJ>iN z_XwnxcvXHze~Dvi-eMB`dZd%LiMd4r$i|(pgHOtD=nfR9K*Vl=R$+|V;Lg~N9umN} zcAJjhRj=*woGkGp`G)SRpk{(Man*5#{r02f6XTOZoEX zP$OU2qPpQ<@f-Mme0Iej4(K3!U&F30sikUTl$ldWYO#Va4SM}!|^-Xo?q2d#E%uxE+?k|Dr{>Y1LBC<}XS*dDZk z){Z6qM}~L_FDQ?oV=s^8LDTgj-C@wisHrX0otC<$+m9W#evO+(|w8 zocIed&)t9(=tFLa7jyFU_*OuOGA~D_GACx$G%i4__z>ZGCnp~>xb6~Z39ur5m-;xh zqfx8a&ORg76FU%&^1*vH3U=nKs7CM%hZx?oApqz1iP9DC7Q_Zi?B(P@gSXF*QgC@r z%!}o#18~NTe#={H?IZq; z(ppapzRi6*mGgenGxAB!TCbmN;6dHK?FRNxi7ig~qIX;C6qcNFm3ATI%*NTXp{fC? zW_Qun*@v!WIgB<>G5R| zg3<1*oWiV@@k^YrdY8Jk-#x|h3M63az}|IxJqV~xr@Gk#!Zp3c;b7PALk>pQ&|{k5 zR>^+kY-87J+^lJ`VawtcXp*=O>+J$W+zBt%4SRB|9`Cxn?e}enh_;fJG_!NACuP1Y zOo0xd9z?F5Eb}kb-~FPz*l!JXJKmtxu*5*lM}Mjoc`+N4Ki#$^?#Q@A z>b6z82gLN!on3bbVY?|FgjD7C`@EiiI)un3f>dId&!4A-i_Zx?YEH7LbcAG(bzSycu-S(aVFt%#uoQH|8 z0s5U`2fL-W@;sk+F(GC)yEnW3Z6!}&drB8r&&JLG5P{NzvF!WAg2Dmy;j-Uuiu=VZ z0JC%l$F#}N%8RLUZNGuTFCGwLke`LJ>4sZ2vR$$;y-5UO8xsVQ*_Uu|`L`5v)z&S@ zcYgQW-d}lrpD_DQ91dB#D)Td|qzPZ~p!i@4LQGxsSKU3LKtg<&Rqwu1MrR=W-2yr? zzlv?6oLgAw-g4WmCUZO5dUUhA(lrc#3@cl#)`|7!-S*I197HeQ@9hzQtI!FtDuoym zqN00bQfJ}yu-9%iKy0b`2{FgUi>~JngNyhKfM%}Z@)I5caK0WjqHbWg@%kQwZ78Q3 zy8H9Ht#&hd@qn1+kJnm3hb$OC^Z96|zz=^LxIRNG44V{0Ma^AJ{UISd0y8JXzxHyn)blA!2-Z$ zo1+~r;zi4AT5_ZBwcWiMbB@{;Az!UK>=fn36?%T26ktZli^I+qAp&XYW2Qiilm5!+(o5ac0uS}Q_cn_B8(a|oE@Bryr;)$@#@v;=N<_N+Fz;H=Q%;sTZy(Xf$& zT|1z?r>|{oW_9c;_}Qu-4E7nXLE!?0v?jsu*8fLyosw{a_Ft3w_1|a9mJ0eOR3DUBG~C+wJeb(*a5V*^3!&zyu*SA#-+Y zGMqb88heid@cXUrF~HTiVzg;)(}wsToOJ9LaEhCK_x4dn%Uh2r zc*TYzYC!#Tv5P<@0+}Hi`;9+!6)Fli?NNq*!0?>d@P<0 zSN%FlfA6$*NBSG;ejKf>9i_iN$e15aFMO*`X=B%Afh7w8vUhhfqqhO~;;izn6K>F^ z!)V&zqXW$`$#`V}#;D|@N374Xq?YJGnYv)8X?SdYBT>A&19pF)Sog|g(yv>BAF4G7 z;EjWsAt-`CHD=E#kF@4Y5dAKIDUwzSd}C)ciGHsZjDH3_q&-;)DUhv?R$NWJ;CbtA zt69iAU9t=d`_OHZai}C`^kgn=lZ#Lm2CZKDp~$}|7wyf0|*LEmc({c07>X@z9jmaNF$}i3kA$=z&I2AKQ&#pNs>0pqY)EwAX5A zZAL!uu)J2RKu&{cZ>I()Wd7DHWOkS<2t7ay`3Ha~r(g+ya205q7V%;P6Zrkt<@Ep6 zNhq(NjG5ZML*LYAjPf4(I?qf7v>1TRMM zL)ek7H@NNhZ+=!*wWfSgJy>Z1difR@W)xlo6Px*%ER#>q9`wDkVCtw8Sh@7qFk+pV zIq2KMO>gfDdhjzdH?Sdd>l`Cw{A$<)HjTnO_Lmft;Gk#yUBau34_eJH%hEZ&xd&Pxi$LPRSEWsV1Hc39 z%=}-reFz(5j^;=eOMFgFhkzuz=4TLoQxu<#n+iTRVk-C?n+iTRc5h={%F6V)Jz8DR zf(JOh4n#AZ+XLggnU^!6Z3`Bs850|%9+G(^{u#nHOjH35M(O>-!0osI`qT>$AtAv6 z=SwOe00>@sfkOj;&Gr(QLf;#9&=*^|Ks)t*-!CfXT7!n)>cUh`R>$y93=NnpnDNfR zF9!2`7v@Is1)%sOooVq!K&&LbTr4m_c``)sa39J=c+1JuI<@9^TQ|vRyDX>JJU)8^ zCx23-gU0+E+3&pu!UAr2Sl9H1*Vyes#cN|_l~r%26+mdFcP9kS5R^ZdCa^##c-z=h z4=6FW*d*>3fPu2vKQCaDfH*qr@6i^2b!4g$ipa^y=lxcLsMXY(M+QLn6qRjGPY>`n zKJ`3=8^2?oPd2^iubikZ4puSd0A@FeHUov-r}>0fUIXkQp>+?{-V);L*Z>&LL1W-5 zotWTX|At?9g=gQ!uM_<1yZCh<|N0(&-Os;%fL|a}X!oN+Q?vEafSFNlUS5C;5+>4m-mA4!vJ-?VHpebBQS zG&=n-G~T{J;;k|t6Cc+tN%zw&c>r2F!Bh9z*gFR^hb1dp_(#hKY4O0a+SIk~h=c&L zjF4%YI-Aoy4LpWoeqSg~X-h_Wh!fUz1>SJB_FscCKoDOKyQhh2U-j_@xzFlR9|Rm| zf(9(1qnn&j3Q~vJzx$!P#yTNrn7ymUI_kAus3;K7$+?v_Ka!F%@^Bo8@(|LJ%5vXR zy_1|qvUjwkHmDFu*)ANcpug~gd(JL!jhrNLl)HMf1&8?li26W&Gi}NpcIgyqEjl6o z$2%wIO9Kv3G~D&}5ck@&F&*Vf0P_yM(I=`32@kt$b1Y@zI0Sab-HSOM2-gl2G=Ge+ zN+Et)nA7A%ht(x$3LG{R(_n(|J2-5s1Sqq^&kvTgU?ojOP!HfNQp_%feffwqq zz{HA`;z&-8o^}CcI71NJfd=FS0p6?0RrxH?Sh8`#qBVAY^9D!bV76e-AuoMouBPu}@NDuJEGd-(<7nZ}CGp?X>>g z^u2&Fie0fFXwhj>9l|5B3nvsEBC>Vqqv&o*{(yFm$7MR+_24Ft^-!^4BujmXC+N<# zo5t51P7PLpe$Na*=RkE#@!%k~!+|qv001|769ThpkX$iG=<`ZXN=hS8tA@aEL#6-` zs>C5gK(}db5Vo3o$~Nxaqy@59t3GgVdNl*aI)Q~`fL8EQm%Jl}1Q9G-NK8;#DdfJk zL#G>$hasuNyJhIbf|R5M;PIy z_6|4(2c@&BfL+zO9US;@HyCsu81V5i2&m3M2sUSCz+%pULVPq{Hc+9nLg#k$2rTQh zaQKO`DBaCc?k`-vi=~8{%PWctAumdp;t}aWCMz;d+4b|-xcd9lZYj1($T4K0#Vk7> zDMFDu_zvOUuy_p#Xk_L5B=#CWvFl}p4HcU=Vx?ILDb&fjv!Ep_MYIs6gZRBe(s@}m zJfdbSVrx<^Xj_Z1jlsGDen3G{#7*oF+Z1CMrHAB4r~Oouh-__OmGs!qLd1D^!KS1I zDkj2$jG2(4KV~e7wId1}l1mD1bd@8iqM~mqQO5(n4R9yuGiU~;KE#xr>uGpBuM#f~ z&mJF`x_k_9n3avdB^0CAB>BUTWH55ldlbZscq&i>h}i@(!Y74v`$NhRi;_7paA_Ed z1a=uQD{xx@gvJsY&cZg6pg0GFxr8Gt;tot*lZFBXyG;EU+Ac@T;RBdVNjO(~M{F2O z_$0H3CV4lIi~-e+hbMGEcm>IGdoN1X529T((LGJr5;zPOF_rKq*gH^e`(5_Bv4xg0 ziO7_T1tJh=3%bD`+AvWeKO1a_96=FE0UKb*T`Q9VJ!pO7iji{+>S_5WhUTu~bw=OP zvH2T)itYuo*0I1UX)W_8me_n9bR^}YancXMlR1iCyv_Qea&PjVG5sUj9`Flg{)VNe z=WOU6&a>x;=_N#l;&|b*r1(TpmLYHJk6|`!Ak5|gDRrvIIC-;Bc!IPsk~pym>|lXi z4V#)I-da{=ZH2{zP#)G=8;hvoM;q9G8knSLJZjRWLC`BBnyjl20UA)m+ezA9_UeGl zI@hn7D9*&{;U*99IVn)<$tD;55Ogb=K^PJmu8CFj@MMHrua*jRQHi#!E&=riyFlrk zU10cE5r*x9n6(;7crdX-;v}JO9A{x?AsKIP1FD-hfTqB16RV;l1wN!Bur^{dAHl6f58@usch3t-A*C)JWi2qvtTHI-xvqj~T|CmJ{@@ zXY25pXL7-?dddl;txFu^b!fI*aEL0uwr-Irc0F9UaSXf}L6vBTw@@zBX{e=nE zIF2cHt?Of>Xas^1C}6lSo=+W;BfXPp1o2{+!iZA$Jph2xAP(eG9N-06gcR4?^f7{r zcYUP4lOR8E9+Qh9sid?jA_XL880H3sL~M*W8t7q%yFmzsVrX%0buF>-D`tLozxF?{ zo|P}?iAT$3F`5iA3=-y^%v=$td=N+wteu!Eg=}hQ-u-MR^G_Uhw#X@G;IcrFIqkVP z`$70EA}s=nMFCMpMiS_H;>Z;d1ro%hn@6nElD@#UL)YS<5JrZWILrzL%zGv=$u9fb z@HFxXPyyd{;@9B1^^=Yk0g>25MyIbGS3RenQzTYq}6P4$;)SFjF3nlgq`S+LLRG(%KiNXFd@QCsq^NIp z2YpVLg5FHgP@yQ26V3452J-01kr}>3c9Hpg0ghpKXE)5>AZ*|m9=x8EW_2Q-QHBf{ z%}h*VIBXTt;HwJ=U~j^wyrC={8$MJCj>KY}0M6TaG%~@poRx&C1g0Z6dvFvudFgFp zW9d!<+D@?M-dTgg0Gi}gBo`v(>l&PSkan1W#dDw@f~T1Vqcw`R3yRT(vc_Y)Bg~^> zv_>tIIWs?%QX?J~3mE7HF7%<7C*CRMFj<-o&LABz@dsiC`EW~=QWcN`dy=xv+#cd0 zl*|7SfrM5vE%4+nxNUf|-({jHLg>6(%v?mJm@u)YBvv)0QdoqK3F?oEbv^bAq?nS$ zk<+jKP*gAo7OBwbkB|fBL4!o{rCO7i639lZqV|S(Oq6Nf7oE*`Ht% zKI*0*x{g#IVH3bqH^8aKdFeXhnnC!dVs*0%^{9#5pO~`s9D_a1S4+h1K&ld-DSoFR0%8Kv+oS3~lgiiwfv9+lf=?Rs{T`|UZ51$DP!pfc+0as3G0Vz}Dn$Y={$mHP3 zgP*vpipSOmZ#6<&sEUx=-;47ZORV#0!pCs{z#cc+N0FRA@sA~ilEged2=p*?0K8Y( z(rWN4G%rh8zQo?7_v0^QBd=+vnJyu}VGb zZV_FD*gs-P)3D67dQ^f*H$=UgSi|#3r~&aP$Z2pQq~O3LW=2WPW~8GBMCE+jbNdQB zh}fA0-D_Y4>VYL*m#~(?><$x^A)u5Wi~JBn8&H`63TguBIDfuCS&*Cxl*Ef05;da; zLoT%1=olrF#vsQ-VpVsLi5nyR9Ec!q7qg%NY<=MgK2#{EPWXNpToeGd>erm{@f{wx+=m4$c(hHLz~|*G=D<{e zs~%);n>l*v1-uhk#KXPNjSB&UM8t=soMc!cEJD$ZTAM3TVUuGd8$1njp%~Sx!+sBe z#$ammS3SjCmYLoo-0L@T8pKzhvPVP(4UpprWNaY{F!AzT$k!&cRb1xX^cN((B%a0T zPYDL5n_hx?lnhtZ;cbtKg{#W0v*l?<8FPHbX(xNlLP(~m>`D9)gd3!k z@C+05dW!ihnEB|d$Ha`{^X4pO(qEJhQmq&kmN+Hlx^_pDktABN`8gcoezUTQYVzelVxHYHn&X)zBO7y?n? zw#^p2A)|oYCy1QT`O=bOA&kCFM2jXA_AGLm=41ayNl+S_CtaeIv2zBeM4J*)5;l@Q zqfC8zJnpL!%m=WT#;H%4#V=2NN8%Ss6r=~6L}qbvq?2@#ArJ%u^F^ziL*vb zIZ~dZ=Z{ILG%pjWO<6FHjp;t!vc%;bpdv%w7hw#q8A_tkR=@A=Au>M}>x+10eu~)n z#{n1)0iL6X@hN_M8qxSqNF7Rk`so{3xq9&=QUlV_b}@76)%#Winwdu5rkvMISEx};HYCCA|Hhh>_c&L0%Z-^ZjUmBat>Y-uYdNe zeuU*z6h#b3T(ZPdQoeSM6kD%Jc%+Y6^2AV9r%{e3i8OcljI^$jrnCrG z7|omN89p-t-*U7Z05*#SJS*i(QO~Fa8C&*6u$@(}i=eJJb2!`=lL>|~35F4}3Djw% zvKX3ah-Z(y_Pkp9T(t5<05bQC1WzsKK=(9jm4zu?g^JFDX4)%KzB0Oav&Y;>tzXP- zz>|!iC$D+LPht02xd#heOJLM%BM9{xH-7m{fQ2Hc}8sQ3J`1Q5GSJFQ9shC0r?|=@(NUI-Ks9ZsVCi$E+_~=<_3KfD7AmpPp>@Pmc_BI|cm+$rNaTPl&)4+TuxGvd<>a?9;syiNUxquzCt3|^I%ulx$>=9G8dtA=}jfx zD_RXXf}t@VJqEcP64BU`VlS(Qa?}*8i+188X<^|X(!#<=vN{SS1u%VNHX#$5Fz};b z?mR-M-3wtaZc$Q91H3JElrYJUhW*ZVsI&*`R{ z<6}CEwMpd#{*8=D;Mk^XXYwNPEcqAb1QwFd_WO&|1dIE%*zX-u*seV_xnGOJzvHyS z(&5`5z9r`@P947eaa(fwqkY`3kNaI-;7fB7!z{B%Ym?JQNTd7&&^5~Yqr(@MFZaox zOnsfMr_ZpR#0#e^eNJ!?VYP3So}jus@|^l_4-LXh}Mh*4RB#8K)od<`zLyDG>qrh~%? z7GplX#{vdCzi(wphJQ`SW=PooTEaAe*Xh!Y{|Xw3|NeD!SL3qE|3@n^d^22ioV6U8 z-F_>J-F_>p?=-vpEn9-WqpSc~k<`i#@KGGm&<+r4!`c!67FP`|0pUw+3wTY=kSQR1 z3}e8*%fj+#uJ33XZDCf&0bSr|$)x>SM)jS;*iD4LCi+IW5t+QbuvTn7VP+SsS)Y1paq8jzH#{>EYsOdJ2cESM`iVA&bHxFi8+Q;apgTlL!H&)b6^>H^Fg zejjmJ$SOnuI>cBYx4#C!4!s|Ouv}=}@tT{s7Rvf0C6n`W%DJG~*GIakJHmS1Jmn%> z+uVJW&VkG$HIOqp3B+UFn@Pw6CV^}aJ&6MW{6kqt7VfTW6GUn~qzMfo=#V%X-kZsTGA#b(qx#JS`wq*+vOpu?om3dtJ5;nd`>#k zdJxKbf=T180()jTY3r-YC?DJzHg*wO%=tl-{YsIq1hJkfM$N~rqvA6iIi*vjwcEJ# z@(a5j&ZWA$LATqwuMF(>27cRX@4XiR?}!kMI$gW0OG~Tz+XEaz%J;-9uvFx*s_R77kQj|n z7mk>x0DHaViM87>qo{l+DsHO~k+2JcafQsIpck&IGuXwrtjm5o%Td<(QkM^qLX#LX zdDBlQN>AV-DvlE=O4#dmycKNB9gEKEx_e3_!wsA)CBZ8Zj9b4m_DVi29;?P9X5Auz@io=-LPg;&HV1GqEsknwTXwov=2T z^zWu|3v1mhw_KH8!+H|C$BB;AeDXxE=0XrMKuw3U<&Y+PzDXO39|Ck zRRjs+Fj(SeF#O@MOSLWp{wfT5`qDDoy{3zb)WTZ`1Ke1sFC+}|_ylZ1mpu}{kj2bG zoF+>o$Rf({%F}ftN=SkoaZ2CTfhi_#sK|+OguQV$(1e(LvE`wpk0q*605G`(9C02uC zMDTxh%n@|$tZu2JXr?SYhI1@a&q{PK84e?Wx2%WnaySK#nX1_|_+(Mw7@>b~e*HJ1 z=FAHfgdtAgJ|twDV^fHSnGHM4as6+ouBsC89-{ErOpAYf-4N+BdrR!LeFh_7rv#A^fyBjZ}#dNNYR3OrcdoHz1jnwG@(EP(}3 z@cXi=IrNIE0A{|6W&Y64ZFxJxuCrjqMj$5!cH5wOofgG3yhzALPD1gJiaVkf5{!QGE3F}k)&sxvxFvmyca04z7lQa*F#nU17Sd|$1 z$Rd2`rCpXZk@6pVgDaVs{~A7ZB3Cf=f`GYq_D0Xvt zYzW93%Jen{?Y)Yk!9>YfId&@R!@hT(Bt^b~p@+XlwkNwrG7rPo06&o90(Lyh4b&?W zavem;?QFGnhU~Xj!qP)I78oLe6_;#BJ!>fyTs{!=&wDpo=I*!@;IVDvvStZA5|7RXZ;Ap6)2&LI_8TW zi1cdl4Ki^DIwVAFCdyjY+a6NA7|3z79b5ttG^vTSHHbuXJ74O`5%bC$g+E=(d}nVA zs8Qz=mk$+XHHKq~c%k^_avU2}LCP7P&lOdP@~xhch3f{*t^$6hP{)B{<^#EXZZ0M@ z^Uj_^yeaJfrobK!vqr`b32%xJaYM+JCY*KxQArd%bBThF!Ru!DFH|#u7~H3VuI5f( zc&o)jkVj9@O|6AlO#?XcCP*AuV*}cq(q{@KBe)rgLx2WCS>Pck{C0|wJp&U|EOZ({ zgf)ngPiL@ZR*#f9R3rfkGMyP85`4=I~=9g`^WYQ`xvf@aCCel-yZ=H zF?;cO>toSo#%kclr>8pF;9kde^uT@{=^0gZW%7Wsco|*BqvWzKA{^90+%g(n^a>Oa zCOybV`qhJwUKEhic**(h%D~NlaF&hedzi_rDk&5>y`!^LKUW>5@TUqu)axXn~Ao~K~vrGSVJ|icBVtHkL~#()Mrli%sNxp z#SD05!w5h}JyCDBUWwsaM3c9v&r^mnSCFoZ5?lipNhheUn2ok_BtNMX>~*&Mc04kY z3X4&Gpb2-JZqe=1be)PxZFz_FI{6WBZ7Gi!Nmc||ObarMnt(HJX2mH<`|M58dfhaQ zx@%BY90avQmkSCvyry9h`;f~3)qGIbJS_!bg)AVAzRA4r=V6qKC_}blYo^;iNtOFx zq>OGQRA{azYB&fPF;TA+IG_{njUbA9GPL>_DlX&Cb2gEcV^{D_>puw-^#BTThJKn= zDL6@H7gQFZW_@Fi`=s@&@VJw8fa+?| zEKugG--6Z1gTSO%Il-1jtTO?q>)L1 z4anqXPGNMQ^sU-vR*Dqz);m)hKHL_h&WxiSSv;C_4bF-ll`!9B{bmgN)+9?QiF8D` z2fcM+e|5)Py*r*@W=-kc)`!Q8yG4c^@E5!hx}6yu5XWU694sua|IZ% zLa=0ksYr%q=+l57*^CKt+0%!wg~%o~SFp)ctd2g~p;a!6@2HPWAV#$#&FbhA$X@OV zzRNE*S`4%oSa!js4Mzp-eb#k8*|BcZn#Ym*ynnP$E#)sn2SUvl7kKPD4x$~q%8a_Vki-gMLu)Z!02c3eI(_Kik@UMzc(aj{F)xhKT1Me zQYwv2%Sgr~u!U3SHAPS$InV2SW(3(2Vk`+^MwG3tpnXc7+kdHVXBS&ygkwo}8_`KN zIt`SvgK0ZyZ;$5Wom55(`TikSc6T@-5YD13h3pFlC}asFa+;$v3>CEfVN|rfVAd3g zl?gp9IZ|9LJr1hzO=!mh%EQ)c2LMIk)QFOL3|8?@>$L;GqQ^q5AuZvO%YA=X%Y`T; zr$*QxJeHGloR)6b|8wFG4m>$1iP)Cs+2{3%up@N(sLw=anTk<)EgUhU5DkTEu*YyA6j?0abg%yRA1+^nnX) zBY-dDU{F((fx?c~;LH%&Uhm^Ny2GGp7Q;obP6JoY&W9b%Z&d6RU~Cn)Dhv=bOVwd< zQOKUxKa~s13jt!<21=JpmaJDuq2{jG=3wSZTnTp(Fz|5of|;xKbR7tr(fK}Vy&jE3 zI}jg%hc^BzjuOE!!Yxgc2T_jEaZny&99&RV4XW6Pmq1Q3jMCEEA6l#6Ip121Cp1cQr zoJ!0Ve(BU~F>i#xoX!<0+TqvT#zoHR_v8|V)f#u-Yrof~QFaqL@9(wgP)ttvDGv}Q zvWP;ag0_z~y-Xx;LmP>aBLaKcF^JxM*2|G~f^JIc(1ilsQ%3lw`d!X?d?;0(>sW)q z<2N$c=s@t=qg>0NB(WZs)6Y`xH zF-@EB=wlcyW7@o$;6dhrvD&=u@PC~)Kb9GzkQ=$T$G{ z#q`E)A{y3%+}6}`+emoLbK>^~0Tq#<{3s-Mz>UJr4f=fqgTTgOyR9}DzRj5H?Otv5 z+Yz})AWtxr*)9ju!L}I@0fepC@HTjvFYDae=-La6M%k zCl03JWSn^FVB-Yr0|zMG3GMAJED;YM(GtP>fKk$+TO6nyW)OI`MjQePFb5m}1#J#k zB_3xcrM1?B_ks{V;RHCe5#XMiA12{-BK;+F;t>bKeG`1Alj3gRWAH-pZDJ#~HNG86 zdnKKOh2lUJ=Wx0^Ob>^#P+(6Fuu#CB4Jifr4h`>_oO~(N_o)U9KP8`5OoJq&0O3H$ z&NB=HMyX7hc=xer94M9CBd8p4vG2MU7LYcbg%GZRA=MnzdYr=woXn2$c(?U$D%jtU zYO*_{CNOT|QhIH;XFy%3xGJP|kUc%E`QOtCmaOj{X@bT=@Fv@k9{p|FkpA!pHY8k? zV0_DnvB1S@1H|C$1p4~+gShM;S)Zk^h=}oLBQ`2nu$qR;I_-oQ^oi-~tVa7BYkx%3@ z{P76TIg9yZf-)9*pMvDLc16cmlx=z9!1B4-A7OxRR-$*6r326?%j%jW)iW=%$dS1&k zM#)u;*asmG?t@Tw)U($rAs@&3*k_!>Js$|-rK`N0YwLYn(X@l=3X$9c zZgm`C;)oL4wDp^WbQ%w54aL$hfC&0FbE4?{BVcl!bJW6#W8Mc!r>s(qIoH#n)U}e6 zA4_!}q$9@Q&QZR_gC+MT@@vqVy9rrbM}61TFm*eW?KKHhj;ZvT$zA6fpft5sgWwok z(H)1(fzfkM-2=D6Gf9WRBxR)q%i(T>YKb)+DkT!NN|5$WnT2pV%tt~Jdj{c!m?pZZ z{y?>N{hbB>MAV0v5aQW{M`JNJ;?h`zp9VgS#mp&=l54B9*rA%uvP9{OF8?m!Dq{YD z!JCGzFTwrIpNEuQh^4GS(ycH<-+)b5D6!h5e z`IZ(c1(0ZfF&2zDv5NdeSh5kMC&b#ZGrC@Ti!-`X<7EfjMf*hQW?G;?hJJk{7B(~R zFpM!y4R+}8!tC+k7#bPhnEDf*l5yjYS?oQ`=t?d49!0VpGovdtne5dcGNUWiS60Zv zF*3Ri(d%(Dx{_VrMMhV$qr1-NN_Hosw5>3v^)1ZkN=+t~wKX!E*gGh|i3GEIm(i7+ z&%Mm(O7?u*jILxaV}i5qDx)hk!vCU-uH?+_O-5I;PuxfNyO+_GT0%CF9q%W6b2GZ` za&o`B8C}VRzXcgx$pPhWC8H}fhb$ibR%dh_VoJx%=sHBt*;3>^&geS!Jl@ibuFJWw z2q^;4knoF|pNO_{+pQ0Ab(KU!8dqb=S1)c4F2ve-%}F^&A4ke?Q7%J0K)bjOh1g-V zLfWo+MM+H4X1t&ztDGDevr&?+(~wfNmiQx!}}T(+7vh@yxo!hSAZ*ib6HDoe;! z$&hInEe=+gGUkW75w}}Zdf}K0IK~8}_1d@zB9#^QYf_0C-0pmEY~|*$bxT-m$Zbg( zgU8Cki>X_`uNJ~vHa?Ye4CB9tx@BrNf4jP6YBJfYKcsG%>MM(H9YeP~M6buyEt6f} zg>ISb=&p6kWOu)1-7+iRP)bC0&PZuAk7IfJNCge9-To05Sy6orXukCn^eH66oqn18} zbW``diU0-lP(c)t-CN!-=_!hl(kpJEng>0U(jvXmlo?ZV}1AyH^|dH@z-2S7>syM6J!`7kQmnqMmdcby`{|VugxC^notv zf4ha4^(_ty(svfBc5l|aP^QcEFL$DUAB@M^#9?*}5 z%56kC8E6drF-T+BlWpX!Yg_&3w?+ZI_FmXzCdM4TFuSPq~M!+$>-VG9}oroi$dXk*s zA&^jSX&edX%*MLy+P3GpIXSiN^*bPW)*!?wPJ*D_$5kh2WS5^fi90$_+-@xTDl7~; zI0OdIP=KPT;60g%y3nnCWL=|c3yN*9mkU#$$>Z*?s z_-YnKc~`0`k;PWyYr=%jb$BEqAaMfl(ax^(^Cf4;vCE6N*n%lJR(;Yw^_*R<`h)Np zx>pa+B_2)WpOTBJgj-Z$QVC4Npox~)NOvdF??U*%RY49e%!TAqSGy0>_6G6y3x1#f zt$&a1-{F_$`O+QJ2(`h8Gw*n)JpmaA3qVO!#haI@J8*MI3$_2BYBh$(K%r~B-crH z2?J93xe`;A@GUwcisa8Lxjn%f5l zM6PqIQAVFNN($Qv46Q`d@LC;@3O%%P0{>A5CDF+hHsTrtRix$8)(QQ=6iUeJdeen0 z5-7$NxC}!N;~{NAls348^S$UyiUVUqKlrAs^tGg)=k8W|n9dHd(&3x3(v(JgcPl+i zXNOqnUgV$ad$(|DV*+d0nOdG%;i@upSgFbZB~-Ri8NUCK9PSZ3{qzjb7EAzAU}F$I z64v+5>u07t-)aG61K0XcJz75Wkrt|7IOPqJPr7=jbiWo;5c3pWXq96Segt853oK*1 z1BBi)ST~HouD^ zLbsuMbG@PvD#Q?>#~Rs@m>#E6Dbi=y8eKVxn!I#{23}O_0j}<-g$t`Y<@0{iyNatb zs5tC&1Ap*cLHbrfsFXUJZ4a?&E(`?*By`CX^~lm+YBR9T-G~AQ zJf?%FY_-WtDh&+oL<2U{r}Zj9!8}#lW0J;*#w+$MIafKV>kX-*iWF{Dgo!K?x-zlp z2RA;Nx)nwmz^#2lsyi2rMHg7L+RO~D1CVrCA9@1dQ8JuX*&5&?%Yol(HG+@A=0c^) zmT~3fbNM$Gp|A&>6s%r{mm;d$Rib(ST-}VsVkmb+JJw*?kEZ;AdYi6d3lLPWxmH_x ze(B;yZR67V%K5AH8_opX9n=^ClpO_yFqtmB*a~fm60o!PdBN;+A4#Nir|QUFPbo}` zw%W2ji_!PnY?8JOQm>kgrkUu>#H`KcI#gzp)MkeV#3WA3S!6fBsFp-l>vNRh!4qOs z3(v91?;xO?o*g;sw<*IW)yFHyP`TCehje)vHkdoo=8o{w6DY6}cJybuC9T!WuSECU z5LGMDx%2uT2X3f)`XFpu14d_lVU&-$>AUV^;J^C75Wa9(+;`KtZ;CETGaaz4M7^;+ zNFT7_0T*1lREILsHLc>j4wj|E4qRg2jG8LI9PD+|h;Mg0wEtS8)4WanPrrYuO#v=4c^nmV3RK?loo`Obu*rd~_ z$AWckrcZgUI!a`&s?(j%6DWy=7#JABkyoku=CN7=1Tjny$w>H<()d-%v@)a+4Mi3z z%EV#SxP1e&q>o{^>`~)-h%I$mw7Cb$8Ks-bNEBgExMzv+pxxa+MJL#7ZR#zyE$rfV zZhKxLrh*OCDVQcj1bk+(+gt`SDM(I!Fq?%!VFN$exI)%|95MU%c`&Flf|S3EO=$9lw(yA#qgHT3pi?cyVJPnx-ylkcqxl%xbj z;tSILz;Nz;_L-R*LVT38iCOXSv=mk(j4dfb1R+fR4P`Q2@qNmUDyg(QN`6XRp(VuM z>~~Mp)D>hhA-NBIh)?7SZLPJSV&K55!Qk(~`iUErDYkA@3M|@d{E~!^0vM$U^q*|( zw%Q0z<7$dRn+K3lOGgQU{>g38L#)Rv0j1gaw= zh3G40*tpJ#9V9KC0vSR%ouS$*$Lg(gVI(osmw6zFp4wjLZx6N8 zqq`;Z8qt6)MOrqciv8W8I-BH$vGP!wM|n+!uv||yfY$sHSKQ>>Qm`o!nb=jOH=3dz zQKogn_g8(oDsk!yi-Z=;E!%j4g@Q(_`U3_IK$DJpr_~vD9PzMZ_LaICI@%QtTCITa zmE$mveV~piYq+jA2IMTW1v2X#+>A@O@o#7M0^KSD zEdOLM++yLsAKmNCmOzi2DGKV8af9#+tw9&S1ZMBzFM=NZX;Y~ty8n*7OHNZEMu_?t zZ?WC>an+Tu;671rV+RUIO+ax~8|;n}PiSR&`xQPB%cq7UP40OE2aKtCo+u1cP?Qj) zDKW``B^B+$30W@@r0a~nFlvKLDX}j)O@AL90Jgox{KYlDmn@FS2f$ia%cHbR2)&Dn z&=kg4(#mA}f}FZih13fU;`eJ>5KIw!tFBveqflC;?$v2oFo$9~ZQSjt z9nO)VP0WD2hB4kCK65Ku$bUpT`iGBWKPT)TnZsLOT zZ6B;i&JbIvX{;EZDt|7- zKVt?ba^mx;@$2W&s~#9MbQjl{tC)4d4NM}g#?wve%XH(foW*T7sMe_GW@<8uf5Ad=CGoPzKU$yv2`#LFl;N7T*; z%EbI2%=hW~rG9It)vY^oI887uxINrMzT@G3=1Kl{2Au!tS)|wg>MBkfqejTwxd932 z^!NpDi_FfQEtp8W?yXke?~<(+$WEO)bXZx(vt*IBA~yA$)eQRv-s&x2f!n!mKC2Ot zh2Nsau*3Ayy-XAzmE*c$>W+^=)kuwTyD-Dh7I2|Fn!FMMAZ;|gx+5-2P%83BfQgvd z3>9{fBL*rGQ6IdqTNIQ5>Vf~XrpLU%r3R-o@um?8&1}8jk5_0-S~vxmsVqeBrJS54 zn*}KI^Tb}B1BKkBs7NG8Q*4-O_-!Ol>Y3SKSYH2G zNuR3Taa*5FQ6p^RAdsUM5J{4uXc+8HV+ivR?J7)Sd~b0``=P>c#)(r^=Y?^t#>#Hi zQM;e&L5(?TBwMiQM@ef=)zwtNt7#0fvWdb2MOPXMf|Yb(!C-R6>xkng8wKm?t_>_j z<0mQg;U8EnpLVW<4ue~X7T$NnhgotWL8>_)`YQy*7o~T{?eyCEyc2rx zX(gt>^b-zhMj91kW$Z81&Jb{oo<>-Mg$4BEKVg9OJ0O8%rz8Qb3tI>LSTMLaHp)2Q zV!1SGz?K0^JW8`3yj69WCEI(ou#0AdUTMPkw&_g?@sC6@UI)$#?pTCB6x(-=tJ_M@^zr!q=GqK zALT}Po+wNb1oOI6Jm9jEt=%nWRz>KIFdiM9jmY+iL|s zK|IwW{)Jd2p*z6jmF{;=X+7p~Ugn2$Li{9W70&y&38R0OgJ}@|Blv%EzsL#v zhhzGk#r@W`i@xEqartCsvl~e(B5!??4vevI!9YY~ z0HUPJMeQ7`&E}y{z`n}%3~g=wZ(^yx&WT^=3LkC^vA$sdbs0{t0sa9BlG3d$Aj?Bs zb54#kl%5Q{9nyvEMQj#nV3xHkXF+f0APnL@Ae_P5t>$3&f1m_HGHjkR4}_efZb>a~ zvfTgh1l*TBI^*-p;6!x@GHkxA?f5m9TLrMYy6s0M%PPOt+NAE;=E=sZ1TPFdU9gYC&YpWW@_1r%~d1BuC=+dxp& zn%kRS$9h+q6?+*i&O-p~dTr&R#$@G$Wt)b1>;3X)(t2K=m;hycU;>o&#Kdtnr8+Va z8*WC{QW;$b51V^vLf}8p@}JRM9-fHb5Hvf&;b)n{Kayzm=)_2~N6{nf@sCavR(pQ? zrrY#^VaDSWq<09LU?gYi!u4cg!Pll-B9ID@n zr_7-OLt8C+X~r@@7xXy+<eRo;@bq4z(P}?DH{T z#EJP_^gNE#i$J&B(uBZ&OaoUY#L8IITg`w4Jl^WK7|Bc zQ(hwnJ&4VT{3kAX9rD$3+Bsetg(>f%Us*e&vmX_75O=U@Ue;+o2@29SYc+fD<(Nkjj9 zZ~`_8!|hfKtnfmJ>6gG25_)OWh0=xu<&r{5*N$=aiQht!39RgtO2Qt51}weMSy<6< z>=n4x$pYMSyRC)^L`IlK=xcnr2Xh%je`UL5b2xa`L^^@98Y|W9`p_(pB&A7-KEk;ALI0YAI>A&%V*~`kdt+++;9*b1xP3c;;6xdvUm$7EOIKqUiBCVEJYeA9!5}t$xzQn(W%%1=%~U9)MRRjzSZ&- zB0b$>{);f~vDBR8FX|e&cj(?d^U5rd%Dg zhuBQS48jD^@|y^F6P%}eIIdoU71pa!EC_=u{D{3?WHzj=L6~OmQ;ki~%9q(1=&O_v z-;0>ou=<9UP0qnA3)bXahDQ-FAeE66SzaHRf!m-?a8H{MPX;1%14INu#e_8#)N27tVhAnFBflkX)@QapDBQm#)^Q2wi zatLx?XKHmAUWiS@PhMM|{CXkW#m%B{;8s^1Cz)o4P?6yG>+uaDxzuAQTPi zNGdY&887f2Ia&XHqHqldkv1)%0BrjsC-WZ3TK@p8l*N|B6-L3rZO^?~GcO>(&%J;L z=STKT4+b;O05dS!C#IT7h6fSsti~3qC zfTv9q7Tax#|NAEr9X>rF{y7^4``qCEdA;<1i7ox}<0gWuDcNO9%rRfpe?&`_k4v*e zzyrQLhR0~TERcwQp(T8NLVW4SOZbXj!gpdz_{zj&(vMz(;|nqdGT*4KP7%@L4SS`z z$kFV1QUJd;k)He4U@{~faCHB~*Cz5!752J2+=5>h!6;BKw9~Ar-C)bm`PJq^AYDfR z+Y97-!?6w(EKB_Bg#47oGSu50E$NF$$f%d6Md=FXrdr~g6SAqlBWiyO5!kVY^J1Az zejBh$hCU}td}~6!u0O)j9LaS^ba6poZigy2T#gMat-1Ij;WWeleRHBPo0zMn0ErLt zIzp6kGZCEP2d#m|FZ8gX6_?ZtnC-tcahUXt#7NdWXGmP|`#K<*=J*}LzOPJ(e?Kzz zeP3hW4`bN({Uc-FuSZVKueAjt1N(kRll}gL_>a+Z8YiwM)qZ*%xZe%n1Ff<31{NK6 zu#1=jD&hH)3Aw(gVzF16tG<78*wcx!>h7j5#w#DeW%6CPI=C+tG1=zgP!Q%lh#D$S znAobWdSP6vj(AGevnHWyTYwKSXRlvCKboTQ4bmjekme4~1qKG*&Q(GB&ci04O1(tX zl5yff;MnqDqT>Eog&{(Wa59*&2&pK;%GE-`=U99IGuKNZ8${#|X4?qN79iDB)I>ZY zN&FAx8{-D%i~=JkhNcb`vR^mi7mt9$@AsWK7)KhEW~9J~@Es+YjNgc3>{Bl9O0FP6 zky!}oGNZ*2pk9HH)PQl1SFM+07C;Uf208Bli%tf_RVt|lxahzJfJOy;5PnhmCB^_f zIu?3OlwOoqDNqA*rhz|@J@FfCSE|@s2vpW##2hz=Z*(?`S+6p1L93`!7;k)`8ebgo z(`TM}LdlWZaJ1(Skz>QDFs7RX*ls}7(!q!SIc|byA>H+dje)a{6Nki$-9e}Q9#qxX z^_!0AbjJLk-VX#q`gOm$L@gh=4stfreL!mfj^=Q7!ocX1d2i-p+%ms7OMOF=tTG5J!9px(AW!Iz{vnJ<>Mn5Nq|;I4_cx5(D5_d(qy4=fNYzkRpu8 z+uVXusBNYIZp0TrZ($o&YKK;LiMUei<(jn3Sb%aY^ z#6m;5ybV>z!*)_Qu^k~5l$8D+3`78^rb*a1%IH?aDFb1E5o~{9D!ns=SqVwc*e3sV z8&UR10;dg2O@#@Q4NvUQJL$|TIy~%Z)KvMB&5p3S#DL zb{@|C%&ZDDGk1Ba8$V51`VV zu7R-!ut&DFMWC^Zl71(hs@h=47I2$!1(Soem?Gb}Xi-w=$rnBgRi0Ph_jZsRfgRPN z4$iHsa}M-M9r3xW1FoiO#030XCk|V8+G>(+la{C>fao?pa<9Q36<(|OiwU{9$c@No z72T~UC*dv}xXB4WF!@E*R_z$Mc43H7owEG{(fzo*0%slP4OLnk@tbVC45a|Kjb>^S zNqk~SiZB9n38NzuX!<=iLJ<{UoYnMR15C02$UOp7WZ zz@IR}pn4G#20_25!o}ew!Uv=l326~5|HSDjS+kgc?Jp=^mYZF!Izi-zK&*)-S)*&z zag9p0g6x2BDk9eL5NxrBfcl%9c8G{RCbMwGHQ;3hZnwS4;}Y}{ctah;X*HiuFv206 zd+f!JwwhTZ*cL0fYG~L-?va7#CldvVY1TUL42Ll4WzBHJnoUMj>SaxlF+2m?msLHq+UcwBLBC&GMkVp}c{s6mwsgkf1QeK^xq;tdp_E#77|nJLl( z$cm;b3eI*8cJKW6M1EJ>k7CjXtjY>M5Hmsn_c#do>E}-`b{VB%(#b!6h7RVhk8IHq z78`?q9NB{Z=g56XOLcMrd6CAVEUkjE+0pG=Cz zR7E8t(h8HZ)-+0?B?*DPpmfLz0x}#ta+EziT_u?bZm40KN{)j0kSGe-C{q*_v!}y! zVpBgX{;gd`=B(E|2}hw?GpmX?HqirhxSAj}aLIjJX%)%jB?t%XY zrzPfvZ&^uSiNhL|W}$ZBP|++Ys0C&#QZ#LH*VLH2AVDMXiCRBCVE{CJND=fgj6XUu@u)xq@>*rI!4 z71>L@F{TrH@v^LQ7i9fFq9ODm<0V7?TwcM$34o~#`USxqlg5M~Z(~c*S{)KVp0Yi3 z`T%~cNrpEIXmDSvA|X&X)MRw^l6uC}qJTF{f1CWNsssQ~%n_e7DpaJh5vJ6Y2kw$C z3~-J12Z}muFsRZNPEqBbnE<-U&G57!_z9bWI6_?lh;S@%a$+Y23#Yg{F7?N9p!$s0 z$-=WMqmCexA*F(90_qcoU*sAE97wFd+He%;E9@V!-VBw(^xyg-3HVI|#IkZ;T!S7E=Wgy-*!7re*k$6*J-*!$$^emkG8Srj0$g z^SUS?RHT2>MgMYBcdnXHIkg83m@B!2zA9Y=Pc@)PV}$*w_kad438DhwpD`=Z#Bd^U zzQdH8aA&pj&LqWMlg7f#8)ZPO(y4fSCQR6an=P`5MFfYbCORdDIL)g?DOIdu1(STa zpbD}(D#(y1qKG9f^e~`9@u`X8qP(KtC=}2HQd!0g&KTeffEyctm1)F#RCZ5>ET2~Y zm^5QyU2($9G^M4T{Wd<_9Eh07~oeB%5tO}e}>kmyK<5zew^yxER=xzxH%38y{m8WMd^T) zvH1aVjL2=2$Qd-6$3f(-qRMh=I>&{KLh&wHUGV;?8&a_$-mnx&1A8`SBs$R1dB85~ z%~vJ@;++)lO^z}g#zkUPY$S5+#48NN;*sJ>^p`T=7!qkHp$a54mPp7nKk`v&q{p?u zT;N#mRK2RWgQf$(@;d6y;n9o$oxq(-Y`=HPtwf8ZyrH+<5zgv5opwC2fNhpBHPVcXx>w4$S z({M$`b*ZOfcym13>_yC3n+^P!2_>M(ro8cC0;f|7ZYbU9T@e zaA7i{l9P}<*-K|umuTax>Y2r#LOFgVHdu&iT|3^2lFSG32e1#t*Ti#7eRBPxj{;x% z#xmH(D8Ab7Ba-NT)V|i>psj!y4K2~!Y^Gy!fz_y8cP;!3!y9Q0cA32OO8AB@ngCR3 z2B__ab8A#%rw!wt*T)qjA!JvfHLEHBum!vhQxdcR`Vi_E8ioYKYzxy32Llr?y@1*0VGd0(1$@!6)#qDwIU@Nf8XV85E<^#5dXh`UcbH zl>qw}PE)+yI>qE&lN?tg13G6`qEh#>cf)LTOmK1*ROlTjw4S(l2Ph9t1AHF9IZ(S4 zgs~u$vx3q|%Rw{k88EmXGBZGj(J}OGilX(b#5xFPlzO8#*yvV@)5p2{Hf#>05gNiB z2NsD{hcNs-Oy;XcEP?-!5FsA5@ZUahca^KkB!tpzleFiy@5ZCiEixmJWvM2o-WHNE{8meYRD(8 z!ko5vYJDRf*M;O@*gDBhg?O920HGuF&!b3Zf^C==kCKFxcO1`#p8hdpIpw)GDh$3V z2Pb2meKtgb;Iixi6oKz+4`!gpPg!CZw(orGNoRff?9e1pm~ zX$t6kmDL_%%@pj;g~wF#7c`{-G$2CBE=rUb6>>~=8OsQ483EwRWry)?7*Al? zbjn;Ug!cmS+&2`}Bxu0^6&WeVgPLPP(BDx2L(cipV6?H2QJ#O+#`)Z9!efLwQ*|Gq zHNbepVhl*5q=3(^rQGQ9(DDm=G`0 z{$55pI&a+1ROe8My8ZpA3eB_U8Fq*w z6Kif_95l{@8C7tbAn9Q_Hi+Rq!-D?I`IXeG;{5OxK3>fWdN5A(CW<}49_w>^2KxMrKqqHSi!xrqyJV8`m=Q;?t@vY6B%uYKl5tByUQ zxdaf9#k~WCRteUiP5<;Do6c^p_^j=^{Z?l7JuG8TV*B~)D(2WXCL4r4o zjK(yB!egxac1H|L5u=+S$|Dkoo>*~=!Xr-eh2-hlxl!uwj5!Ul;q`+6rX2#sqmi9< zh9GO97=@OzLsw#zfL)o>I zov5A8(@tvpPAgAAJZEMsC11YVO4qLxgvd59qZu?G$VZNe8vCUv#&A^%Vfa6d9by}l zARg?%YNe!v%Vp!|a?j#3k1kj(zulZ0j}Q4#bqmC)bRys zTA%_MKY<8Yy|KTai_h7+)2+EezBR`RRHvCT3uv^JnTR5k?~)~eKS;7CD#Rao13 zDl7?d5?*47CXgdo-bXJm-CLvb$GKo@IBRT}9U?|M%3L7vvy}_0=wa5;apq$~SQ)>D zV47U?9Y`wp-+t!3Pv$z_`y`|oyvP6KMn^}ZKhBMg;doLQrwbWFWK}NulUzLW{D%;X z<`#GY5mz71MgN!2KO8*22hV47(U};}XM^XM=}&TFoMiO*59R$Z@%Z@V7P4AV?P^iF z^X8(D$8kYJO6398xp2Wr8TbwsY1Hq?#jF0{KK!AA_7uY5orq4`A+e>#9O^Yx>a5`- z=Y}9rd>Ml9Pdh>LrwSVW2fa1^EEoM*Zrqk_S*1!eqIfG$CVT_N5-flAI^0Q?ATAPP z@j3()cn2%cki-S<(e^}C_P?zmB8-Kf{g3xVOR{0Msy*;|< z22zV4;}D~FL~9q2O@@DKVyqd#Xee(Kx2u^IF&a(WUFV;Ovk%%g=1?IIMVYbWul^R!z8{te?S; zT($4_MTo|$lek2a(ItHsQ?p=z?cf3Qh;rEd;Q+fQJFxq&hrsT$@gcFhuqrB8gVBV! z=+ATUOQth2C{cgoPlY^y8N7;iJzS4b;Gc|Olm7>O|EL6zmdFs<>g z1Fi8dJgiU+kV>O=r;eK+QCVI} zUk~@i#7^mtYIQf&1k9JgUsAQ*evAI!$D=PF zc>J#zOurVu^eZy*cTgzlYn%&z{#`fLyJlGL6UD)WZ-U>9KWq8X*W8VD*CUGDPxb_; zFYFI7xY4Tov437`$fz5nfl8LJj(a9-B`;5ah-ojq#oe-gc4fP4pO7^FeTFp8bucZI z4hPTr2Z7so^p8l3SFUx=)`iB63%*2Z*q?@;6HT()fR2L z%?ijL0=S-Rp(F{2)m0FrdU^CB^iOKjaP%=io~0j<;4lNsbU(n%F`gC^7;ioNp*a{h z#3a{4T$tSEOub|sv>4GD8{f&r-wtjB8YH(b1(FP=L*iYy9#pyb&Ve!QpAFOgCBU?Q z7EJrEcryAIVjBMZ*8`9HLmT(U!MHz^aVK#%e#~+4=f8D3mNayEZk)=gCDQ_^&pc7P z?PRo6Or%6-n$ExH;-~tQNXIQ8#T0pdxuwn$z0gF(SSu(Nv4I8%H?)ToLj<^G| zo|#<`uz~f>-GGTtURq~QBWK9Dh+_7(D9xDDaZ)uE2m@oh80STChYScuhiAX~OA@^w z4Ti)S<1HBw;o%U0u1CM&y!Ks@{uhX4ZG%JFedPlmKrkg3EyA5O7TmOVrzH&#fOYFd5ThzBjh0Cef`!2E^=*q`i@ZbS?4QX{@c0*RU;Wp&csTDEBC*{xD z8T)Wk%{`b#o(FTDmiZdVvvrmKeJcGOLtswael8Z79K{@lA}~bx@U|P}*@7g`{57!` z3Qa294qwA1XT%7Mss;)gSv@$#U?qWD(m2RoLEwSm7_SyBQ@a|2=g5-_Fg6TB);iA6 z&_?qeFN8U9OR&{?iRG;-VYs|iP>+{^g zT_m|O4(c|V5k_1ud7aTO9iYODpT@9mk2w7u0YgA2iU~+p`>|wEq03&R|Cj*UAnO+A zm(D;X5=93PZ@CQ?d~Go}6t|0Oi<)oaPZ9@73vlKLU&p`$PqF6_0<{mqZEbJu#NjU2 zy~}lfCAjW4yK&uVQY-V=)8!!cG2{%gHh3dBR&IP+?dK@B;FN-gN8(pAVmXz9g*9Ao zl!TPJ9Zw%3MdQl^bTsKt6ZA4NV_NhZM9R1WJ8ML{3}9DOG>X1vmkLx6wcVz zGl^E>#a*l02=Zm*6w!PTJ%qzB9Cr)Fgg~Z>D0Iwp3`_5_t*~bgO^@J=sF4YQdUtyt zHg|;eV7W*@c@7RuB=XYa591dG@r$>DT#%|nZh@PSJ`5`ageB+%Op0SJo;X9XMDf>h zs71Ju(jL?nUPvifC3f?mrj7$$xI}`fEax^*AP+)xPVGfMqg#bfuc1cas(iqT?0Qnc zlI|phm*QET<0TUusP}=r(`PUqPY3qC+~jh6f!3Ks+x8=*26D^Q;REx?8j zF?1~wT}i}Ge5Mjl_~nAlMX48ukK`sWK0RZ|&nv8HqB5^O3+lKa`E0QanL#bM$@lP` z+Y-K9Ym>QT$v6d#t-^9Q>(vc-W-G+vTsk4jIeW$ZXU1Qi~8%Y_Q5%tQf z2Nz>G9H8N*P}fQD42^6qnwcksFS_hbps*L>1yQU9eMN=2y7>_%mNp2Lt?;6x@-rNd zurylcW>pjSb{M}0T@|X(t8N@rTW&ijgb=a186eYATk@I1AHk!9Wyfy)VMuQ?*_JGq z%Al=FRWABuL_%1$rxK0Jya`p*`cnYV+{$5f#m?!XcWI*DVBE-VzJ z8_A~>PA%B8-Uy`Lh6{dGJNXW{;kG`+=5M3<8YpZxa1wA>^En4huqguZ@Zgh}#9_ck zH?3t^fwX&|Xye=fjIZQ%;ii$m|E$%r5Mb+fhWmnW%j898=IJ4LPQ3YMuNmbZ>$1`+ zjI~b-Kes+o*OU+AQ~lH59|E zFEwHHfykdC!s_22WuXdJWKM+I%!ejVQr1Yz9HyCv)IRtdyay0c#M&b2(=>^K084PyCKWQ~RD^3;t8M%7kg3?a{Vu@JF1eGZa(7KJ}6o@B{ zO7g7Z^UhvsR_8mNzn?lPr`KJQMx*&%l6Kd-b*%QZV<27UoVLvT2AXejXnOC0XI&)a z+s9w0i2>#=Xp8s<`T0#We}X?;-<5J<49^$GEq^ABRQnPNvme?#^KFo7zZ&SiD65Q` z)f8v!X#uW8);*SIkWmpOk}NcR?PU=+?sO{rj!z$(XYkvh;ikhgCK zBGDdN?Px$22*{p9f`i?#y@UvD+m#zlhMl-C$}OpgnaXkWOh#YCJ@J0F#oc{4Usy&+ zDqARNLM$3SY*aUs=Xj}fesfz$yJR)+z~HPzY9i%f%COiJ_Jm+9_~4YdWk8 zd%@7TpIQj4tkkB+9+HrH;EeeADbTou%nQ#fI#BTN=I1YQ|5r6619!x2&B2NI-w_J- zCP&Rdp2D@xZeuBR52u@X%Y@B!+tF1Cp|s6Rn^1HHN7S*ajsUu%y( zWn}g4V&G3OF@;et3Azg~gcx`|zM^HqItT}(U^r(hh&;i6=~u=K=+eYuvGx7@R&5VW zLW)nc_Aj`EM`?>C7q?k^Fy3gft$|fsz8!ZQScX_X?KhjNC2Zi_M#>oE%C-Ew&L#&Bx1Ti_%aRtF!BhG^M^l#6d+^Ek&c zB_+2o9)7Wt#TF%<0KM!gb!%Vq3r6=dPES^V|HA$yrNm^-ik%W=Z<7+yQ929NThJw~ zvX{J=*SLKwz}0iYD-B7W#U{v;@Bg#M3QN9UyvQe+H5k-cPOlcEY@yuwZfmCI+_Ngkw*(nfse^H*?JjYfO=>nJVZ<>Wj3Q=I9P zIhQ$7r+{sm&(I6w2xyZaTAM|5Cq#5ZDI}{ zu2Gh>i12+38Px=ZODUib8-mTwy-O;ZQoCdDPaI8ol7H9a)tUe8%36Tt4pD5tpk=`!0!ve&#ZMIS+4hu;CE!XcKm*imSXq?enz%y$Ir;I{12Ch{h_gr*^v|e zpSifn%<<)v$Y)l7w8{TursjX;qW|(E6@s_h_OmzpwB^q&5}7(nJw(ykRnKs;gWC2l;M9fOrte3Jb+MxI+x z{CWfBB3$-8in82BHTAs``@Y!2w?&sYvJH!>fQaq|CIpq^?8VXOV({YmZE2u07EhtR zJ0jqbVd`dSTMeqqSZh^#;Z#&OkJb-$1raJ=ppJ0^QO8zV14U5ccx{;iJIbDB&+;8n z;i@DLOR(L?^ zUouA=RT%&t3@EP;afQaCb8ZetUj__5EC}w4W`yKe-IDR{#HiP3eGjG&9=$u73AIYV zolJc{j;B*BLT#il1nJ(9Y~byCqS>nzB#@V{wd#9lEQRVS3-G`T!e4~W1Jxx5h7zCa zU^MqfsMXv=-DS$vX!O|irT0eDa`|Mu=zaVPhEKziUKDW>GW)Gcv-3bFUF8?MM-N2j zuDOtMW4K5h>Ba{70P#1Xi!5kkk?A%}^qW!i-=6tp$m{szWjIDrhbF@8&SvJyo5rj! ziQ+`iFYDmsW}{wq9V zw2A9BMaO%iV^FWK@(KLs%8k)@v|9dT9G{j0A-fy{Dx4aL7dXDw#*mJ@Q5;35x=qme z;%HvKLjj;DDj;XV|A{f~9dWgyGSS~?`p0golH8B4PG8c0<|4XBZ zOAm_S#TQiM>Mp*dnQHNAVm?ZiyniHKv-};w5!!y4*Z&cqVh!zGx4)2c3u4VvAZkMhIRP}BoO3BF=LmBAdI>mR8&bf@MGwO9BYX@jSfpmD z;M69rc**#ouHs66%;_$hcrM(1$YHUkT@V>lL8Ql^FQD6e2cn*a)K=YrRf~=3YTGDc zHc%|-KD-l4hCup$VoCSGhXu%ePS}HYua?e2!MiNGVb;L1XF(lzk8moQv95c{(bv z@SRoi>-_!w(Tx7Cv@sX;rv1O=h7wsU7oF`@j_$XkRUOl*n^w^?y3koWE+1*pPp$Vh znkm>8bgmc*?H+V{(ZMFVm5mF>42#2Mo=bxeln)IG!7ae)vu@9Wl5qbcTGji}zSZs* z<9DJKxx~Z!{5bm{{L<5iU~b>hzb&~q199zi%;)XnlIGqxBelH zr!MP1DSj0KYiMt(-N7|~H<|*Ezhz?egh-d|H-IbZL*q-It(c(mDq?Ln;N?rP0(edVOz|)H( zH*c-iq%=V98~Q=CtQiUGm}2hsQm|iv6pjBq=tbHt{2FECLqzJ_L3bUgYx#rdAt&~K zczD&Nor-zY;LuZe^8+Q$zy$Io5LuaMgX*RoS=sY?@=TjqN z1!NYWRO(IUevLjqoAv8o?<4MBr?~skk?5lxV+^TYO)&t+3`$ zzIJ#8`iP*}UxyJv!y`25Glc$|>0l3**6v#me(G?mccs90kVm~cO5I5w_3kS?fS~z> zp&aU8>giAKMo`=7KsQHvaMQdGJ;dhj-t*uqbDW3Y^pqrn&(vEUe$~rQ9(-gj^6;zU zbdHDfyg|2k*Bb_p_;VxUtAU_st;uV3>)JUd!45t}Yeqsdkk-kpceKCDyEt!%dNbss zE(ab?^oz&?aP?OpiB;-|>4g+`HB886@717@tLMb<`<6z!w7}ISqBQTuMFdqyvL-_! zqG%MQw~*L`01wn_W)&Pix3m>*rSBFp{gkw926Cd1)QjXwHmSh}=m>Bl&rX|=pC^%; zg|gp>#@;Qn!Dg$@Mzf2Y4&ShX1+yZpF?hAnK>P0LYEns9T$(v%Wi46chC9Q4Phqm8 zrFJCWT&>iRro}aEmo{&41r6kOXfyDQ7L#dRex>^)uc8L}gr3N~%`#whs!C!Qz zimX`IcMO@nHY)l(qa!OS@lRaX*=z1cBe_p;06{xRSHsMJ7Anp%>zc15bJ|iBEk}1Z zw`%q1WbTm|aR%FEfq1=gR-a44TslsD8{h%eEBYIoz zH9SDATV$Twc+z-DntP{hA6O%zX(>+0to2(}Z)P-_yRHAyOmMX9s%klMLxf}+y=Yx* z{Hff7g3F9}LRhg&U@Y42m9q8x(u_zTEkViSw2Vf{jz^n1LbZT#9sHxWE7s;M_1wai zExXswL$c%GAe5sqbQk9++ivaHJupbXsa?gfSl1Ms z4cO64A)H0(m9!ekGm1jz88>vL)$~;C-rSi0l_ad_EU17WjUBxlvsFnD&nuzc9gn6j z)n0g_v<-BuMDNTA#mGx5OV9)$AR&%F^V5i@KjThWX2E#K*~FkIgi~#xN0?9%PLfu( zLlGbL?Og#@;(%{8qIc!KLD1*`It?Mvbf^oM-D+svAbTshWO)^rYfYbO5sl{_jyaHG ziL0&S&zdQH(BG5}Zrg5*X=?F>$)an|RD+%i5QnM6{1p1=rHnF^W z9X-U~lUtT0YCK=CQo93<7Lw|Kz3z5kg`g^=EZ{gOQGrB%X>kfrYjFs!CD}51ut<_( zZJQlayg&Cd`&AFGz^nwaOUwQ3L4}2Yfid0A4()qKyW!uPyAZdH50*savsWF^6mx8D z@Bn0;21Oi%X1X7f1w%cXq!GQ3U~{M@BXx{mAA8JbXv4XFWK?*^whshcMlz-L?1?6G zFXHqONX~?8O`9gIM3%R`ktic6N;qy21v^?hdLqDNEt-{7M#fMd2^mAJ#=1VDt!z^N zDPCQXG@_$PU9OIduG%cdrq>aa*{V!EQLEiz3~(hkYYbU=Mf~G9LUZSh3aohJ@zg3h z4llFgCqg|Sie#T?)Li!2IfzH){qW!U|_|ulSa@jvz$jr$;O|x!!5{WmX z`MdErQ%~)WXeSd#`SFpCILeR1hKM-IkN4Q~bUJ^RO(RkTOy>C?GcN|b2KzQPir&_! zO4j*2YGJzE=#SCHdB4Yu;{@O6INPm5Y`s`1?Mdf#02mpqJ4msIY=!2v@=z*q=_5(F z_0J18s`W+_q5U`o|8PPc`aj2+qo4k@g_%A%62Ba@9%eIc*q&#+1O6*F7S@0sC|q%~;$RS9 zWt4k7rf?6s#STv>NTK~y$b=wNOTiGEfo*pT=8ojoYnv)ofPLVGLM1^5po>-yVV!w^ zDwB>6yUcMeA$uVv?BGtX&ClyWXL2_K%kb=Nr8(FNTkxB6rURuNN!d_6-18k&>Ol+3w zYoZ>79tmntxRzMOYtj&{m^{LA71@)sQWg4*)|%9#AUCyF4ekbD6vC<35n^9Xq%a3T zXM@N=s;I}zPbfF|W8i2!!D^G*Q*w&y6TsVu@^6e{Vv`PPyt4b^w0uUGW@|%^ zGOfG#ebbb*>&}_vXgI~6^uNA=5%x z$QijtWw3K5@~=8_0M!(6paI%a-*y7NT|7*0)uSlX=(RIqg3E_yfs-)ex1c2Fa$gLG z!WISjR1>?=YC^M5aU;2ik{r;wQK}HP^=T%!#FIPq)=%jMMppIHPRh)zjBHv#{n=5@4(bHQ!9L#1ro2dHn&1z0u2ob zO=#mSrJK00n=RA~g0q9quq?=7wQW%8Cd%d%{?56~wURtbdPaX^WN#k{QR>;d>iQ^Hqi>=e%59r}5h|-j8QP6;`W2h}O zQLa-)Vf8S#KqEVLGHCb*-QE^;33W^$(ST$vpXTs8N*;^Qbf1SgN=OpU#k~!*X_ly!q4(hR7%ekx6F8B24d{!J zwJ@NrnhY~oGu0HT=%TFiiaEBY=C1rSG47{U{Rs^q_xGTk@V(-VI?A}OYn`n5`Sa4X zbbkH`^np2#MXPD8h&9dr&hMb7spAFcWN>Br{268F`SU^uIsoJ>R|W;{OAucK1Kcb& zqAp&!#eT)0WBOE-8X64FC%!|kQMdWcT*Ve}Objet+l>JQyNtcHDj$D}@0~TGp)VZ!?w$wm{ zTgWkB)6X+aPMGw)C!oPR{6Ar>=2U-%`^Vb5{qA2p`+d8A0z_Br4}?Jm1VHy1l@~-?AKaC^?2P`Tu!K+kzKRySdn-gmv;l>ka29)c0nA=YR@jk zPK@A)%4PCJ5ZBxLMUI`?M8$@<$9g}+Zy^jsMxZyWGQvkxsEYmpu<~&>l7SzqAvYtJOvC^TCsRCUX%Lc z^Yg26CoCHm%KgwZ@}9|WE;(?}%#Ok~idII}U^|2r!1;T0-XwbM0C(mpc>j7k_dQMS z`|z2hfQyEX%W|C#a+PwkgG8(EB1xL{OjUwGyB6^%K%In?tlB*(*bBAY-yewEk6aUU=-*Jh#nD2KU$M`P3YQ&?zE{MktKXCtF! zaSyC-wKi}B*bmw*)RFQH4KSO*T%5eNkDqsP(N|8ye;Y$py)i%E*jq2{RoAzwWw@bW zh1}e3!2`K2x&%uqyP@Xiv#-q0J7V~D$ohaEwGjUI$y$9u=PBf^P$;ah(E@tFXq{cR zh@E@l?;PhM+9nKEgQ{n1fDYuRoxNQ2fA5LEcAV?*Qw1wPVJp8p)-1|Je|}H=kH@)+ z%yhv@a#7w~s*POq!+YYd9Ook3Ou_tbkCx6uW@3fRYw!XK&&x%pa`C?$=R!KB3|4f1 zbov^s8xZMo(V1NQ<>Oq3O%%-j-iV=%dtxKXMb+H#;-;S}SOLNWS8Ln1O66J;Kj)%< z%f)|xoG=I`3s&*lsB)sNL|H^-B>ubOTtqrou!ejzv!WI_JB~jdiT~j^R}sz@tmAj0 z)7RA+mW%$+$nj#NO%%*N9l=S0+7D3qKrBa&mqNIig89!x6RT8;bJ1Un#D8;~;FoEF znLiL&;spv>jT|q>Tx1*r+HQD``5~P7ZWVT6kmbKQ?laekg4sVDjbFae#MECKiU0aI zLC#r%d1s?p$Pe%hXn`>-;NFwR%NH}#1uJ(a9syCG5MNa;y6+_72#!&9 z>NabzuKyWLucFYO;tT4Do;+T8Y4~CP{(wodQ<(Gx#*#j@xLA?j&#l$tm(<9*aoa%WsjeX z|MNIk)@$Zqecz10MaC9f=#H_L4nGe9OuAShN8=^&MO&{`-;VaLlm(*3AefpOI7HBdms2#Ut{oQif{Dvq#_(xQt%Nc;yTKv+~^l|kT? z@7>0?XuCd)K{JwgfsQSjo5>k#^V8~-lh!}fOd0lCsndx9%dS|*>(UMgzr}IeF4T`{ z=Hk6l9c4(W@P$br0>|DxJyY>-AdJT

!;O|cR%g$0~`AjLTRWUeiIhvq?yvOx0zoTp(6EYLsK z!C+^F)3=0UK{OCyTJ%6d>$-=F@~9K{V2p#?1E>MSB66u;-uwmrwh(oQ`LxHZmiBjW z{%~7bVShRSi)taq;(-JV$AtNX!@@U$|M7DP{PG(w0JoUF|k;8Vv-&zZ8O88)-@7<@f-OgGdyX^pwX&8wl-A9R^+I$ zD*Uh8ncx{+Q25n3W6UYe4`n}3#hj++w|K{Yc@2pkun0?JEI;s9EH`gBQ)C_9KA|xcg03qpT6F1Mz$}WPJ7~VmD8|fT;bPu4n zrd(RIJT*E*hqPE23uG6_J;Cm4yZFq)v}x%_1W|^-C5S;e5H;mBDyG<|D21zmX^Icy zc3c5yrHT6W7;Y-_21`)1QEc&?(;Uh(Pzs%LGqK6j^~lQ&>%E*Z9eECI3*QG45v?TT z2pg7Ry!$*~bUNJyA=gZ|5MOgU6Ica0UYWiIHXy73vz;j>-#VMWzDM}7OWcOW{+~7a z0V^#s-25Mi1QZcnLhjh8iY{V+MOys%0C_YE!%sLBWXXUN;vN!OqY23EVyIxb$Dy)uelv)LF^f1L zlb^D$JB2*t3Wk3nv=>K*oR+@O zCubB^_yEqYiB^-l7UJJPhrJsW5*y)o0gu9V{YA=uAaB1oK69@9dcR3x#uQ<-PP!4+ z1@10WoVb05$`p5{1PT9AltPa=!A)U4j%S0W-e4@RZt<~7xe+?uNJR16k7wNoS>4ib*_i2Leud|9LamxNIou2BBvs1|mPXf{x zva)(8S+_%N%%6y3T$I`mPEfO9!FLIu3y^kFbw-B%ba#22lCp{1dx0RWk5WPUi1KRw zJd?EuQqkW1AwD5li+?dPHe=>qC_%S7WBR8jgrLA%)g@jdaXx8z@k=!h< z)U``31kBf~j7HX*AR6aPu~aVWN)H*^3O77LW~AwPWBy(mYX}^>#2xDqm?$OOfvN+l zE2Gar7orq^(?)T>%#S{3xv_Bp% zb=YOWp)SO!Rn@lU;HM|%4Cs2iJX5RwAfV)Kcy}O(Bi})wc}S)bp6Go%4B1kzBNY^^ z)M1&BVjL1h3832X%BRUM+#qrtY$WDHqHqc<4cLig?&(IcHTlk;0cDz^z|O4#g+vf~ zTA}@x$(7X_jrK6=G*-k#Te$xf60% z4WW~{0P66KMfXg;8o~xAm3iN(oo^64i}BNoS1&JL{sY>ofn;V8)(Jc0eeXi7E}CU^1C_}X3xwMP)R4WV@l2_i4J zJLZW>nl@<4q?r0qkc9TgbUzZA`U|^bv~)rL7Yn(>e}gR3ag(mQC z8>9$@ph!XaGV<5)$Wba}PZn1l_eNc#njS6SbXy3R?9pW>-(V8)hGYjOvJ;V1LBAI1 z?PHm{Nyu9*?^TpY@`TD?b(plDf%^YaoZ z55s8Q+y-zB{2pvI>KwLMcgf2EBsXwz_w#>*op6$=Cdy#716C27a6bQcp) z3PZ~?f_jB|U1^~ia;@}WJna!zTbfB0P+t=eo3+mug$^r?$(HsB&+}2z3_A@cD}+L^ z5+K}GWcMrCw{da8I;|p|(d9(Ob2oPl1fkpAHHsypTE-uS(}9ybD286GB5u-8yGlZ- z92=~!W27_Oo8pFTP5*ELkCZW^gDA+p^Xrh3bwC46k=ObW9+hl3{EcFB4Y=oJ$2{k> z<^#dSd*IFiw=x|LcE<1)qh^2??r1!8giSq3WTIBS3`2N?b*IjA{=VmnTQ!hGn^TrR za?tbq;T%%I5cwVbm9*3#G#k&J$KEc&QK1Ul$pz?Zt&MG|!G$AtMpol&XB84uu^gw7 zI0vCe@GnY50cKyTjA6-u{0O&EiUxX@U0(dhWHu0dmEmN!rW{lpX};v#L$OjTDkGpt za39t-t0Jv<54Bu(2*e0jr+B?p-6O=24v*K!1mS-d;WxT3zHBvGvzI!OT#` zavb$=Q7)mjdL;ITqdTHMLZZmJ+qNDNFYN#eLLbUBI{hU(Cc>@rpvVb$^>m6PDs?-y zW~qJ?3DKfdR4e5(o(p$5LELoSauv6$bw70Dp?JFJ<|&>biS*Y{0yNT)6Z1uB_Pe7B zphhkZ>IhWb*Kw!(Vlr_^q;<3tc_!mdL!(HDrwmOv$c;=E1n>R1q9mIKOK|< zZtpq*li{ht(U=TB0k)-fnJy;7R}>$4`mC1}R$O$TCgM*wP*LMhk)}s}z3(vdV(#7; z0qRW@ETa7We5qb#s1`I%9~CH8zEm_bZ5KDlDjVSLC4ZH%al=T^ZsR_sHtxS38Jjg5 zH{=H7a)bfRqbRVPTaMySPpT*;?R#H|FEM19FE5w9sTZcqQWZ0ht&EBfNjO!=CV8Z!pnqw&9}-G|>N}Kk`6B;>nogr)@w`Y+ zSENWDg`+Zv9t;88gLc&^uP(wfmWu0V|f(1^|B1Y&MF@ir=Jrkw>@;b*Sbe(|~26jM3aG-+Z z+n0ALuIdoqlFxNCl&IDLP|0IQg0PAKmf0%sq`HIZaE5z+Jt@Y>4U)N{j*83V3pQJO*u3Zwn?CLZqU|^TU ztyj|ogTCDjqr?if{?@oda(;%rN~h$W_qs#+3W14&OQ^5Y0<*wHuX{5kG`@#oGk0PT zxj>puu>&C|s%uFMiKZnuiagbMJ^uXN(0)qj)P(XSLITtmMSPvhjYgN-gm%tL~TDvWCqfi3O z0sN8y_$l|KseRZhh&vuc#v_=RkMa0dMp>lzV2rwWLQ!h&Y-rw5|%f2l$O719E`vDFX^gP!SW3`?aXT{W5}@8<(nJW4QM&zk@a)v;G|K zDM#PE0HHul3Ke0cybr|-4AP#TH?UR8i?%Qqw!dVXK-~~rK}2Nt#g5!eDYl^$ChjAm zT5tTY$Z{?SZXXmKoS$z88T6h~fJ1KFw>}a*nnfbFvEi`Dy)A`6e>rIMz2VTvt<2%k zoK_4ShVG9{zve0xI%+1gUI;A}2Ja`%tCYUE&(eX#>VNC!dI{Y`NM zE~Lt!Dv(Gk6R;g4AMKd0{14=;Qt5c$?RTd&4jH7`S8om5^YxaQC$+Y8RxUm{^O+S4 zuiQr%Ff7KBnc6~+8OOpO?O>$-6vM4NmN&Xj8=pq+vdaub zoXmU4_y&rv_C<^_WF;REA>OvYm@R690x_o}a&inNViCCi4l6a|zdl?*(~%rF19Z~! zek4|H$BS9rktY@Qh(2ftgJh(x`19>20{{BungPuG>)@oaRO$)auyB+a*32@QhsHVr z8d&$2Kp2kPW{x#cZ$og6x#)>_H8ZH?A}9=vAzZ2#QpO8(@e~54l2eW~qf z&JjR}7o_RJjj4*hMXgrwm(|SE~%qNXH@6+2p}D{?yIdrC6b; z2V-QgdKY`v*hZTc#?3kBY;_l5JpikP0zu5?U+zl_vP?s=1hvxjdqP>-;DHg_q#nH3 zs=m(m93~r{Bjef})NTRLB%OD-^6-d(}WI8qi6b7JDQmu>0i~ zHQ;&$Qyln?J>~CM2cxia#4YQ%eu~_^xy+P9g5;80-1@Cb1=R|-QR?LwuHvbSaK#?{ zE*A1!6{XPtS4@`!;m2C<-d3XucW*0+`mgwHodcyoiiW$heeLU$3XI-7Q(9N|{<*6z z@%BrvcE84Sx4Vl88tHlI*~3VMi>XHjqlzA?GUm~6;FrLG6*yfvaJeF4Uh;XoMr%yh zmpq$Rcam^S_o4|^A)9vd1x$AUqlQUUCJ(+i5;nbZC^qfZ?-(I+I6_AK-Axzr+WN!1rC^P-!? zr>}cbLjQm-IW49FH!$jBFvPN1gPyuBk|h?(j(5x+?<&Dl{tX3*R6ypEo)idoRYhq< z7{-(ViwShYPoQoHt9tcPBkh%F!FhG$6Ie{z)T?obv@aa~#K)$80!tZOy**A@sI+xE z0!$smwFwogQ&;cj{-u-j0m0 z@2qEUKE@41r)i?oU`V`k>b1UTlimKCjV;_06gR*5UWjnfQMc3?-1lZph%2zCaP+R} z=6UT1M#^!8ITWFLl2zIcaee$ao*2vSK?Z==A!$~-M)q(HzKl| zLV_`m>xjPBH*Sr*tuF1f!SuK$5Ha|eOY={~#T9GkW+811+((_Nwx>u7d14rY;d^h8}hjyf;&JtP6E&B6?j8nx`KNK+GJ~&Js2G-y8{7Mb+Y-n&27|J z^T^zf(Z9R!sq=jKmxr2P2y3nYlDx$(GTCXjLbsFX)gf_0wwpvnyXFoyvaxPr&_y*H zgcp9IpC)mL3m;D*Tu;TayoKt$XYQ*{(p=FS)WmPeERxD|NULpQn)10mOQNkdIBr*N zFzXela`!T&#QzYN>8Usf;k>Lh3G-U7@v^8)*Q78l>8249VPPoYNUB2~Y{UYWm(5&Z zmEGP?Ru$)lznK$5+|0fRf;_CWxDhT|nyGQj_ewbVx_P$x-KzmoWjuN8(p10j*&YG6 zofrH5XI@&|){H!ye-j*Kqt(>VzP2#5X4{2si;6u`x%SPKxg@d#PAZo>V2rQLZRL4% z8enfg`Ly}gNBaiCVg#IwD8tZ&|tAe}DGT6JQ9;UuGO@Q#r6%42E$|XX9 z0;I9oE3sA%rA5J3kIOG)UqS+zF3L@|g``Qr7n`V|RY}Dn1U3c6fB>Ury~PR^Sln*M z(tKtozqI%dHr7Qw1_TB-T01Q5;@fzmm#>(9T8$Ov5?olKR78UIgwF4iQn2h7a3E4K zqp}TA5PODrBJOaEix66tsISyrXHT{XgEUVEXrbw>Nqdqx<*HXzU%R4#%5O+|*AXX8bQO(UmpVGLN9Vw9kXIcc?ex|@fFx@x_AmVrtTq7|)r zJNs7R3(_psB8rPTXKC9ds=xsalP4ikJTEn0Hqi!Fc!br!gXpp&Wr!v8O{cpaJ2Xaa zH-Z>M#Ad%%=a6)fXNtBAVe&V~ymD@*Qt{hVf#q1gqZHU>RtiOspB3B={k2MIvnC~x z5r_k1@lkn1&b6J2sar_&*=lMj-?Xvr8c>}zmV?cuR#g{&3nA18OA%6XM6epD8YhMM z@puUpl63z7pClp%cx@5V%^82 z57d3!9IR{N@W4&^F>6Im>Rj5I`UGJXkeJZXO^SNNv#ntP-V9iIJ1Djd;hWmgOM< zcPBQak!(bjQ@dPQEKC8E(%~*R=mP{7?F1G4sFKk4+}A)$;JS5%MD5$YM0b$5=#R9g zopSPcuw%I$J1PiQ!#O{h<~)%?SpZ;7at~AfWJsZP+j)j!@7i*r15j8FmMqTfpTCay zvgB^5Wdc-?u-(V`Hwae>bV8!!4KrzaqD$PBhd*#}C3d7Oq%P*+RVKzF6#nj}H_08U zQ}nwMn+|j`#8H`zRJuR0vHFK%M1dhrOnj#m%b1#^w4UBbl-YA4NQN{zP*Wms{|3`A z(X%^&des~B$XYrH6oyJU^pMm^a=djLS<<9|qydC8%SS?sqv+&=O{nQ5xMUrpB>DZi zNE&HX9OV!01YJECMxB=HsK2DeECZA=btX8@USnYeequMSTOMQVsEhaG;9?1MA0Tnk znX+w%7((gp0CGDae&K$Wz~7kqhNjYqU@FwcZI5CfB{^o`K$->v#sGaS=p=*~^rjRt@n*fgC zEVL&H^hsip4xQeBt2R}yQ&|9tt_K!~zQ+@0QD@5>oSymoHAW9}307uHfS<&RdI-3W{yKI75=?Z{V7Ri%Wg2-4~xA4^R{7 z7rEh;>aLj_yx!jZjunEqmcA$1H|pp8)1T=_<0M4oFjP$$mDZGKr)nawn`06ZF;YuK zzQaG;1-owa@B8_oDWE%365u1Df1kzK_6piLD2<%a>#KoDhd;pIntpB|&4lYwDkEhm zCrW2FKrps>GZ|vV>&L5Oq^pKB2UdFPK`iA4nNZKK9KR6Rq>v6$;Q87e(o=?Ny<3Vs zMHBkp%dm)Xc_&275>o=Gpd;Ghth~+|t2GPT8w*{N=;Fpca44qdsE}u+$&0-X{-88? zLJH#@{m!cW?qq{@nXhMTZDzDubI+DjGSN*-LyT zB4lqL)Q-zn)-I4cKoJ1CS_uPkOsj~P5p?t3w82T%U6^gd6&0wIP}hRCt;b=%-p{j~ zsT7Tc0a@-~JExu&nXQ=aQMjneBWGAx7Pca-ykpgPk%47qGO=~J6=VJyz|deZRxzz* zeua#}hND&sfQ4lt&=}qoF2tleJ7SJ>CPrv5Fz6~Ja`*}Lsm?;MVw=R?aOa@%F&k1^ zGFIxSQ)BLdb4I=Jq|)3>60k0ku(E3~V1l188i1WA;|YqB0*{JjFi}tfdsPCcBm89# z(HMwbP&Vg~M(Yg)2f<3pEMjM93`7OqO627d4D z74%)RxkwI)9!5~z5wN^n0Z`Dg$^6*_q71GY8`aZeGrs8?2;->$OF-M*TBr%s_i^|L zaWRL5*E2bA8~y>qwyH=UC7%5xpZB%7ITt}8YwfxiN$SZ@Q*b_WM2$55W@K#6iiaVl zgiYVORS(;;>S3etG^-)ek6qgxkJ z6?WaTj6|4Evh~71iCitBU<69CC>*38zWLR{zF1tw7M4^ahD}z`5W93Ii<|DOB-u(~ zB0r}4GV6j>O{qc*FvQLR&=Er_(LKtBB^M|Xi0jOmEYe&Ok_3O0mg*?K;s+vekHTIr zWearTXRMYEP3I2=x< z*#Sygljw`Xqvi1w$PyP&3ZuTL!+SCe(mX0g8tR2MoB&yoFoQIFB$rRIM_=Qm%$Bs8 z%?EC*#)CmoD}H>;HLapIQZ4GV872l&!PBr6?xOnL-;Ts{OKQw`TAxHG7GAa|Ac5&g z5q4S9*G&asRWGY#mhL6Ku&#_2xAZc$}G#8l}p3*C$vOpJJ=$NQ&_M;I$(&Da8ybG(oIA={-ue(2EX1W zxTqY&Y%vyf$2G8>U&GRN^N)k~$^j*Uuf^MVy3_X52TV4KTcn!7&uLUo(Q18yVU+zz)jfs3gf)bwBDqpTk8E9%lvGEH;D-HFRYKgDPJjfNi0gJN_yKmD z9*Euh^nqS}@C0nej$aQm`6fQ(Q%8CDg17y+qtAZwJ$w?D|1gY0fNPbl;zLw*7=uw0 z?`afyVrYh9HA4=5UIEw|Vuf+dMwA7GSg zi$ib+rsjtEPo0(fBi00W2;o6J22>@KuY^$319>bu_@Qd)i{wjP4%K9k00Zo0iJ}T% zMG63$rSx0p+RRAl1$WW5ZlIb8?<6{6KV7fw-jwwh;UhBBX61HXkqr!o7$G4GqHPIs zAvoBn#zU0|X_qoywDL5?VBHBTY$9r98jVoBq(4vs>2lNitJ)(=-;kVPchSA(?8HPf zREYh;N^oc;P704^iuMjKr#1$s^Jb#lBj5x?N54Mj`}DpR#Z9<5h!&J4kYS)EFZ~7- z08eGBD7rSiSigzjHMB06fBnD|%4uu#vq{oQ8<`4VW&|qy;`^A7EY1j<12m2W%54Qv zy-KO1EkiI;8l%6K`sg}i^rx^WRTKuw78AU3jrX63=a_NgIm$<(lS?mMtyFj)7<5Ol zJA;5k+%N!3K0RaxXqKd;ya1aaeFz>X0Z$(oG=bSn{*=-{vXFpB_|(_nD;UIJ&-8n6 z?ouQO*dAdKI~y93B)gU-HL%JJ9M##%4MFhnVdg{cV3gQwELkdR@E zA92@7oy$$u9s;K96mB=DI74Rx?rv9`O;k{(V-cmHX>5`}0qcx{KnWO*G<=-m2~1;< zKD$CLI80n(4`{XIG$i_I4?j>_m7t;~#X+}g=C>3BIZkmGG4|7E&J=yd_ksEN+or!c zoDEq-E(JQhqXXDB%uYa(zB$w36-f@kjP3pm$BgY|pZfV^|1|0YgK=~kO#;pWd3&W$ z-*Mo>y4zaXD*E2^XkyVaP)kCkPN1rN*P;98a0QqtWIh9(F6h7GyTd{8uS<@ zCZ+wr`z?;8@-i&)MTo(y7G2qErupku(JZ97a`GX9f{!qg*bQ$_<-X( z{=}uRnePUtuc_3%(!evMFftSx3+JZ_&L*Tyo2&E3L@mQ0qG<6&fOm`Y8<_{ z2mxa|qxk8y*RdGm(?om^vG>3{Bh; z>0uc;NFV6=WXw%oqvTx<_H0U4BK^K(b~7hoA%_@7{Go36PSwx-5*V~8zdF59XfYST zV2tIRJt!lr<>P$cxIAtfz6Lv*Swp86eb;KwS6C?BjfglbW4?wChfAwC43@4UZ)5|K z&Q@jf77Ax8mJKw7z4P3Cv1MQo1yE6mLXE}udn8#nSXV__k+`%yT$0EV?mCi02ms3`K0QF#jt2$)A3tl3yZaocd>*8W zuX)$G&kxEQR#Gw`ZJ4!CI*_i@e{ey} zp%d};6R7i!z-+BqQ^@0$|5e$Sx+f@LGa)whGbiF^=WG8P=EaJ=IKK4CHDn?vEz!4Np;b<4H^B$+K82{ku!;ZIO zm3t_U0E-gw>!$e8b3A@-Bef6l8C+Xz&OXJz9PjkUpL`3i$gb@r`8StfNdvMLIi2(y zuWv0nL~x8c6IFG=N?I}OdhL0fh}|41X#!Z|5c(4k5f9x5Lo3cP98(#NV-PAl43f<9Y`>{dh1Hf(gP>r+nuy8 zs15}x*~!7*z0@QET_3RQ*HII_p914a z(0bx}A8IExOSl#L_@;VmU`2uquw&sY{*hrln{H0HjwF!6JrlYz9!CnraSq{rwd_gt0B+XE8s&3a!bUlkUJ%5Zn7%% zl*QecZ%CB;CD5_^hGexp$WK_+@J^>dw>z9cj!OWUI;FU>+=OnZJ;-r(|IFul(V#a8 zK@U!l9vTua(hJu`6gOJ(nVx_bFm)4-R!LRbJtevLoQiPiq);B8O_j&<-ip3l{z&xR z>HA`miD?jj4a-q+Ui>d5YttnXr-Z@?j|dy8SsVo$D7Fz2cezFLL%K(?eOa2NBY|W_ z07x{U+_XnZU{GU z8MZOhC?b#CEmt>HW`N63Wkg#z$Xdq;2#I(W`74y$bxt#*m9~@Uvv%&H0`u2J4|??h zA@{G$gHF$LeW!FA-J52)^+@cJ6`JivU_3*pl-wj8-)i$(1W5P!-oEAP#J)aQ&LkYN5$#hO)v{vaWW2D9pJnBWOVI;U zuPicH2tZ)-GUR!xQp}x5+!@NYkyoGx2gt_H+gL+8YFK_#2YMU^F^EJRh(W%hAzd%P zW9Jr~NT|BoWd=RC6#F7e(DzE{2kV3+LIrjO)SwGbW!W@jX!#N6}X@ z?@e~RHYsm?ZDg!qsfW8anqBh_U-o&2gG%L3(vMxe`FA66IVGa!<=K<9x?y3oaEGNI zJ`}G3aZJH+R4>5x0IgC1C#@|?PKVr9+WwO0wPLz)v&kGJ>rkBVq4P%m?fp9`S=H<= z{S~;*CEfwKfunWO35SVw*F?WiXkUc9SZ2wQ8*LfzJjsNq zZ3a=$I%vew#~h3Co=0*iyeYai1Ltf8;d&vY@i}brnnA&L@|2;(_IMG6wJP;1H-c!- z^=kQyD?-fulfafqCI!8!R&GW#{@_Nk-iL~mR2QU(p0;SWBnn$jo(G?=ZTu0cEwCmp zZ4I16R^=ss0NacqNbN`+2U>}@wkzo+g2?q9_y=ug&(z*@GGjY zf`BzpXUgGn?#gAHmR98UL9ktz&%rV@r=bTR%!k`No|{#W)6v`gP|fYXY7%8%fOHw{@7UtptE)qCp&_~?d>q=rrlnC@bpfC7-3}+-oq2a%{iW1 z#(K^V8+hzAI>Ac$v+cje0Yk9aU zbMQm7wA)V^hymL*3{F@Yqqi4)F3S^|BHK}W+XtB*M)~ibtb?csm>ihVINLC8_axH#hayJvWsKt@9}zGB@=itMh4;vb(p3U`(TTNS-P~;nNG$ z_eL4N$)xkBSdryO&>FNChyxGvr_tXmFnX_}tKJ_N2u^t|}PNeGK z_X6E4WSdkWJ5gAo9-22Ttqz`;qwKdGE%$X1ZYX_L=Xwyw`^Fct9a> zUy1RbR}b#iwshB{&QTjK*_C?&w|y1ppV^+l+pnAM477gJx1;x)A|);j=_2Ycjcq>gd$#NOF_S zK5#Y

S)tB8gBST5a=mVRhHMMu}PMX{hXQ1t7T^7@RLXVQzLCi{V0Vn8a^z0*S^ zun$OmVpX4XU5+z$SPj!xq#DG~1j8pPh*o)CS^;#`foP413l| zi03p0tXwIT5w%r8s9V_#>rz$wvh-qpk{)9Oz`X0o@~)4K{Q2-`v1F&HZO4>82z6hR z)HT)A(nfrGrndtnluoHCyPK5Slm>nI0Hf{Pi1xYBOwR3o3EYW%?4(wq*kTQodZyVE z!)SV7C=KRL(Myjmc{a&k)1Vq`4xB)(?3VZYy;Hls;kJ}kNBo*l_q4Hz8%DhamD_vh zjpQ@0oo=$I3Hh18nhv8{dqca!*w#F_dSdnX+>b2Dn%TH!?()!dyK%)+e7uo>IC`cn z*rso0HBmxsoc%JZyP4gNH8WZQYui!P;E=rn1Cy-deyGdXP58TgeXi{qYm|W!SJSNa z)P$`VGHiPDo`y!lW8`DgO1Y``*?&0WG z`D~GuK347ig(4XWb>f851McKfhzI1N^c1D{h@B^0Qz-*7gu!=$fxXX<$`;|Sd=O6&K=9vY-x%6HC&Oexh+)UJ?@2bb!N z8}RjNNLBiLhj!ovkP!9$*hfhY zmvL#@@v_fQDs0J}bD`H5Dsj0J@9k}B0ewslV#C*w)^ zyK(kJylP5Z8$AeOb-MM77+fOYO(j4ROW%Mdx>-5PJBEl9zjMu`z%6z~A@iW~yqW_w z|3_VQjC3s5;oFtZMO+Vg18yFth4xf}aheT`d5c=R){)FSx-UDEnHGe_U85d{8|m7y zDiKMduLC#qw1{Cy%h_WKnf3zjepN|#(XcZFEP#PZhXlj%)W3Ku+>`by83X0cH|Gl6 zzi@|C)ku>Z?u-O;5oiM*+$%wmSZ?Z&7i?*;M&qoyw3G9M`;8EE?}b`)l2^~%(q~n< zwDMg39ygB1;PKw6v%VmrUHEiLk{~LDYM{*)lIu|x@kedSPoU(utPIfKT4yv7+W3I5 zr9_C(&d_C5XM{HMP5uM#P-c-M6tRgFt6+q3Fm5Db_Hsrtd)=t7NIq75ug!d>8xF>C zmLQou`g-66_UGAf%I#tq^`J6YNiQOMa^^rF7#(H|%P4I3Jgfq4wA> zpOkWf`klG3&^+ZWSu#)C}uZbqK@j z1Ha^IIeifF;3X=KzDgKL+hK4)WtnI%pgZp1dP^ZBIVm^x)MxN1GTCAXQu7-NZ$gnA zcffaxDHnFA*Og`*zN*b{zByHG`e4+-NrpJz(o5Hq=BKap)Lf!B zsjAKCyH#ym)HD2>zYbMx-apk*)g}!KAiKIFs@hPFO0Uf@H_NLz14`2jz1hQ+rdd_; zELnU$A#@-Yo@GXwfBhdvV$H?_&6R}Gg|_V0LcSwrM`{_TRGUtr-fRmSkB@sa4k z)6c>LX7!mcj$ktfNjl=g)HaGXNw_wAgX9SRL2tu4YaP90GzibGAzxoiiWO7}!I0HG zAL5r3HtQA8NPEckB^(BI=ag+1(T$3vsJHS;Jk!DVvOo8EH9*K_8P3e7`(FCvSy-O z8n@+wiY$5zM#!SuEYA9dj@Lj(*BfL^XOf@6vW!;FT8cnQ6)X&bN& zz9Ez9LCAWyY$|W?_a(p>iI9+$J20nA!gzA}z;O+?A!3@8g>&EXfqfOe!_!qR<)^g< zvx&qh1z2fcD>VIQun!0IBzcwL8+JWvfh2PHZbro;^EVjEV%SBXpvFi0gW>V_i>l41 z0;LAke&$qrRIOl;_=`27Hi=#|T5%17pBH+NbwL!&1;H;z-#C%@ovCNcTL5SUH?Aj* zyT4sR^iX!OZE}jP9G?doY9}$WO1BBez%ij>^yv_O;;liOpo|k|8kD4pb~ymxM7+QX zbpuE&q(HpGZMD%?L!kkm&$!n*N0@a*M-D|&r)Jj9b1S?b;n6(`ngx`Hxpu2ob_9#M z+aC5oTE(rC>>xaeF%8OgBy@@?6L4}=>U$-03ErsSav%-$4LET~8rneIlP(ZGr08%| z;*)0bS@*4AROlg0-;w+_2jG;{3^Rv)RZd9h+z{QT@~{2~uV&6nD1(w456{qhbW?gz z)OOo0Hjk|hEf++g!ME!Tc3~-x#Z&TxR@napMuL;ld_(~ z0fC$VkYBc)LYEN~`4Ils0LM?mZi7AorHxukE9vk^3QtJ|p|pn!N(g&DWP!Fb1lsMY z?uSU7Y0GINZ7=QaBLmw&f?(xzEEIM`&m6}DJ1eQ zKw|Wc-sF33KtM1w*18WtAxhkmPywdfR(AH9`&ueF9TXwkDNc(2@zlw9+oPgiQo|wS z8^XumosDB`E;wF|?6Oej;m~tXu~bl)4z@)=By#mCpK&ZBY#ML3Ab^-9|ma^Zsdj>}=`@_sAgl$&aO1V;XwkE^YEa z93ZFvtUdO^VYus#2zEs&g5t3;r&ny4F8C;G2rg0X-WVBo3=<$Td>-lqTTim|b-9Xw z>Lw^AQ`0NAQJWZqX;Y6_^7ynSD=K(5lE2HAXw`zU=ThKkQs3CbDkb`OvJmJx@%T*o z-f}q}N8MTOMNCyaiUh<#^>L_W1=G1ug&cIh!#Ma=(sJxuZG*Cej76}J4;S!}DEj+u zyU9OMZ9chOy4he!+kY5|a?wn@lzwRAN6|lY`?$-C@x+qL5F><_L5> z4zY%RFQj0Q=z3oWk#_MW2$7uwldlda^ zrhM}cM>^^^|8PVKIRCKQatb^K3(y*!By@QBPn+pJo{ZoHpn|JP2C!w$uti@5qD=2# z1g#{F>huc^nM&K#aBz)(+-=&hieHpVDNQHx{9CurUsS~~y@)O@&UO-E%!?L+mqwr? ztxH-YME^cAb_p^ZdnyJBQlsFWJzv?JPEB#6pfvw6lDmdMqyHR^@EY#4DL6Nq;u9<QKU{55ngAvCA8v*kw$ zdHcs1;FV7Sn~P7|V<-sBJYiE5!VQT8u4|?TDnE3J{dnxxI~Yy>dt`LFvA15Q9KKVT zTLE3GUUO16Qk70$D1YdYM;?AO7sV&zixvH2em;FAicVjv2d^~rkMNZ^nz*o8Zj^G- z$jSIzh5rnH1|f@2M$tXzq6j=>g#TQWf1Wy-DZQ?I{gOYv^coC>bsi{S@@+|9M|5oQS-esQ$Ht@KjdrywdTD5sJZ$OvrN&Rd5Jkn!vBct4M6o1<4&5y;S)$&X6C2Eg+ zdAYn=s$()33(z06#Pr4=-xII;SufRUw_0La2f7sS>-vV0tI2p8V;J#^qDwy!PGm-3 z*k;g10;VLSFWr<%LAo+uT2y$IS#Xnx#o}S=5OcBg!O-tnsCyTEBxF-C3B%Zm9WiZ# zi5wVTqL%R?rCqfKv-=KBy=EJh5NEQ5AU#dF1e9#pchxkf9yTrfDvfq})S>_SlTwr( z+oCOm7Va<=3p!0vKxzUFDyS4ZY#Z~EvQ{mdD$<$m;Wy9nJ+%9@&=!x{90R`9qK&P? zF5Y5ZcFolc-hViT4kQ^aVpb=t61Kbk!{TL#z_Os_a`8DPcjG59Q`1cDX29qWypXNJ zA)AS;Qn0`lD*sl^8K3xb)iW8sVE)THm7NVh%&d#@xsJwoWnx{Pwa_(0GQcuh1)fOT z8N)rl9uG9^2-W}?381a--h}vXbWgzecq~182o=D}e!;wMG;9pm(Aoq>Y*gSX67U$L zV20R)##GvCRHUxrCo1(?>I(9vUHE~Zgh-m7bmVC?ZnV(SXf7z-{qULM4Db*IfYnkr z-uEU7Zuh>4vuL##V5!TX4ln3eGZ!?z^z#E<(9YVwuO8%%B730EHO$l|N}`vc7gTRC z+fyv%s5^~YuZfT04NK5C5B8n6#vPLLGwfA5C8*!)4(ThMoe6?Oh{i*4+Lu5s(BT^s ze6HwfX!L1IAmPe){#|?{%@bx-y@S)JS+DKq`rOD9Q{P9!b6C^KYhVc3tSE!CPlfij zaXY1N#M)wJ8tL}NVQy6Gpz$Dm=#*5cqm}~cnzvh!t%0VcmCrZ9{3>P8C9p(#v~SDY zEo}&fFG<@{?#bHc!(KtKOXWA<20kPR{EUl~AwevQiqQ-%>VFA!4Ok=wGw2|NBw%XB z`=nEnXIwC=3F|s%pE$u4F8|&F%RMvSTGFEy%YLuoW>n^hLRg~t#T{7Wv?TBd+` zyVyQU(ZN~LwaIb?*j7KM{rq@OH!8^8c1L32Q=ito>)P~&V?>7o3ZM86o4hpIZtveIg>65lA;>Ahsa!LFUQ zrj*<<3}`h~&jf{_VXIs+`Cub?4P)rA?sKuRW!I~`heyCf(9b(}PovSI=X*H4=X$(S z!{wRJ!wz%SvwWw1L3GJ%rQOoacBNUz`}Py1>New{Al<-AZj93V zl8drb$P%su2JL)r0PC#N24jRdvbs}EY%pXiS2hIK7@C)yA$n#|3#>G391G!6ixSFs zfi9kkyQY#;jy0sS{vNt(qH}4pNz;X04O=ADe37Hr-$w-tcZh<^s^C{1je*m$B>FdH zG~NnR`{i%F4LU6CC)Hgls0|g!I65pQ+`ZZa+I0O>9_MR~rXsz^V#4q-UWWYd5`&ug zkJWZIstC}4jMx$)Bw$H=P-$NzpVYX<1LFDU#c!`bpVX!mN)XgK$E`d2R!UB+R7&rd z5$PA)M>kj21!#4UG^dK}AOH@cm!NCx-|MN7m#o)-bD1fJ1j!}0xb<6=%HDcu8)34?a1~Eo{3teZ;JaAJb5)c^16*-LS=M^@ zwi;Eqds|7=f5mU>oLbl2o$YI1pHyJ<=9&H5emVbfR+o7DrB}OOW7aNq7ZWtn^U|}i zx}Lo8t-DH*%0#^Ai{dQ-C*vQ=RaGWD8V>vtIIsezD+e|)Pbx7{AMW~+XVdCV5{~I! zG@&YF({8?i=?-AjFsaJq!52rurdJNdrrr7-Bg|(R^>;gY;$24VWnnqTZfx2?wKt9`?VILdSRXb)zD4i z)7L#Ip(I(ZLCK+?SmbIqa6#O}#byn9>bgjlSSZ~MNpvVx^htqmoBvkH4y_2o$o&vU zp&NdpvMU8Qd#RE3itp+8qebFYn{Wlw1xF;Iz7R-Lq3lX~Z+3M|eiw88kdDnFFyi_O@3FV3_Ey!!6 z{x=A!$~-M)s@2O_f1-67>Tt|R(h-?%mMwyq__pNfkvq(56hyzOaFNu01q zw&586{k%eff1p4*ELyZvz==@%tmmZ&)^dW8uV*e%KS{)J& zVFo=*!??o+hpWD6kNW|r;YOt#CrO)QR3gottqj;o(V?0{L9q5Ommp$K`ktcbdx}mF>;+f;Q7#Cgs9; z0(@;2>*(DS_k~2*hQzf6@1qFx+{2v$fg2x{Oq5`d)Nts?NW?-n3`hINUaG>>KWRbZnWI zJ|d^OKuPOaU@Ly8sCk)EhSyZC{r<{a;#q=ADi=Fo559(LD}P3RpS-%|PO;9f8gQcU z5Ec@$8Eh=8`}OWk10X!OLg75T+&Kmgkoaa#8{yyN=1%g#<`?P^mq3QF)9Mw_2&K#N`}3 z51E*Vjuri6Q`MzfWty=B6XTSMNWDjNqVRH{Gb99$GB_VDG?DWtjF)jyi8(_&5&uN= z3m4jDK9~2qiRuK;mrLu_rG1f90rCf73b!c+tpNri2+&jnZ`g%+O_S&#!gmEM0FIJz z;a1TKh7jKiG>Ee#oOHfsPy8`%uUWZDck+GWGN8_r3bJ-baXazjaJ7o%!hWXrfDo<5srHwB(`EvT zjspmvfz)y?0?-Aj-~t*ZPhuo@R3c3lHpE9ck;<8FsRDN=aedR_P8&zYYqc_*7(~Qo zzJGTi={(PrZ0W+}cTmGj;aTB5Ri=zZy*0iyjLL14bLe*D4=)UKP%W*kffnJ213L9y zc_hxcmDs9V$QT8mTPsjam=h=GCe$9#IE=TdI{#Z354=YUF(sQGP^%iZR-zACX>oi- z6&;~y`s;4dJR$B}ep*fLHSU*GGyoCdjMCP2A?XejwU)s);8~a7vIh4t`2%$yH%880 z^i#qqR=(skG}5PaB_w-TTSgH$?<>RQV|ll{B*YR4#}^e_IMdJFewEyJPcfJ0Q+E0k*;#TqLUN zbRH+o@nl&o45YA+tQDu7ODQm#QjehjT`ZPr6{xg+XL9B2L_Dv8rQaSfZpF4pg*#O_ zRbJ*u$XdP=1SNd91r2%!L0$H(RtTN5^03zBaA0rBbT20^`Xy~k9epnjbxbu*k5h<1+T(l*Z)@S&QlhekrwlsHfvKzF1WcFhECAW8zo#YnPDf%s$O%pj; z!cm!xTzYe6qftCIdPjEbQoLKKEJA3~loAx|N=UxpL=ekStRcoRM4qwsZyXwy`L||J zzk3-5g|3nldW+mik_4u0_zr#QNSeZM9ry@naTDONU4@#ShjX0sP9^*Os4P;dNHzsX zFMYc@aNMGWa(;bP(-o#jrh!#rIotIG2>igVUk4t2?0%O9?-uS7gq%Q$CD46g|2s`=D1LOng(xr5}=EX?>oa#m*l1 z)BR`Qc8=U{s(Lv@GTA7$*gg?C8&^VR>=e4RKG*P((+`&Bum<+PaZE#z9R;eKwGU6% z6Ne~DXj=l(&>-%s62VLKSKKJON)q_5y(MO^AJ64 znX#x)?UZz&g7Jy~-u_nDs9$O_JRx)IP|EsNJ|-8vPOLz+c^zfa@X1# z_y}=;68)!%6Q+x+*5u&z_SW$w11Z0jO;>VLub-RxAKHw^<>+B?HC#;(Dv|4&xSFV1 z_F@v6P#MZN-TClmqhQyH|9v6cG6!^Pdd>JB(|ES?Ik{3EIjGxL9h0tp0e`0d>}7c- zP5{Xbt+eb;p73-93}cx)lP*@=eks<4B=--aJ2^cnx3fBk4#D=u6sTmnAlw^yxE8m< z(bL^IF656=sZ`%`;jfej_ezSit6%Py;fUO-9})#7cU%$)+z!N)isSgjp7C~*UuzGg z%iUk==n7&po=!V|3!9nJo zWL|Me1>!tXLDIB&2kh4y;ZnQ|?F1_g1G4NvFxa~6G5wOKC6~-mPEnOd_8|=|!{s&e z4nkTI(VDzl40{e@j9&s8Xj+xw>wkreQbDM`%>{4)okF0obT=?z$ahx69O+Dq&|qND zRZisS6GKXwN-?}*ZrF3yXm2w-BWZxW9yn|n4_e~tLKNZXn!^edY*I|e%jBQ|+PN~G zp*Sh`W1{0C!beGGUkk(YH6-9dbS)OX zBoRIBfsvq|u%CkSzUbQILA}1Sd(9>fTV1TeNdbmjDQQrAqaZe&OAL8L1Ct#}zZ3VG zael`fNm?{wz6e{)B$6haSgfYAY$E3#U*xZ${yJ4a0y@FAv<{9@=$$Gs|1finrJ|eL z+hq1SU@CoPTsp84r1lbwKYqNla6vG7;@F%V0ay**gi)jhX{54~A^&Zw4Wnvdn;l~e zKWPvXxaJ}ZI1=R0XoS?y?OomwiYtX=<4|FcCPX9#*lmSWu}2&!;&o?9VU{%PswQMx zQQgI z69}$s*sox=jyjS_S_a~;h#^u{m5oW}>q|&P9zD6W*-7V^-%XrfirVa8js?q|nVbnW zm84J;0DyjH1z&rN%Po#4NZU=dR7Hp~eHUolc8k#BbLTDnaeK;R{~Sy0#`YG6UO0m- zZlE4Z6K#ACH{#(5bHo`hT2Hxtv&mLeCzPonK%It%M`Ca#MwF95Ycva z&W4l>5Su_oDMfwGXZm;I6feen4n~)D`C4S>Y_mpNwB&AQ=j1)X-^%Qq?FeI2?~K;& zmfP7mTO?4_43*J#cFyl-cFuN0zMY+O4whaUiGu6w#C=dNxl$>-U^!N;+ui2kD0Av2 z?9bcTIdONLPI&9?pXoWgot;xI$HnR8+HS?|?3{8}v%OnwGGB4D)s)ZMxpb8(+NbE- z>vQS0%Ffw#qi$#COr`u!v-$1poUoDbdVqnDEX6I~!${)h)oNX}i!!JXXk7~b$^SpbGDh`<)q5(?3`7`7}R@XY&q-vc6QEn7&nSi02|prkU)k( zWBE0k`dWIll=$wCU5-7uIF_u4+u1p- zhg=gI-_Fjd`>thn&a2;c+u1ppLM^}59I-!;**V*-{^c?*UY;p$XXn&J7k>k@xwgs9 zd9}2%ot<+#J7>ym(1xemB>XPC|8Hk@&Nf?SJ3A+fATZM5N;A);vvamVuG!AcIrW2( zowMEYq@$>}vvaDK#}9UP&UP!gsn^eTc21W`MGw5qvlG9#**ULj0k^Yr$~EfW!0en? zwcOj;Ij=lBXFEi1XXgyJvvY1|=iJWDd1GhyeKr@PNCC*Rxr$t)EP~dQn6qy{m7)1H z~Xo$Drp%b5vqoSbivEHr|bJxp;rA zSSaEz_AcJYnqRrJP-J-oNvD}xUYoC$P;eoqaeT(0tU6M+A|L&tRp2Fo467XA6PtH3 zH*pd6jggTVbgbhdU9;qU-xmefBac3^CZd1}hh=^qEysYYw}_BnC0v%Xx;Hn2oUbuD zK?SOL4{PqMq!Fq{Lv_u&uO2sMR0A0g%)>Dr1yc(33W)ppV4I%@+}DWoZurvvbdW^~ z8h+Z%xNsmgFoRr1%^l6;sl#8HfdLt}7OCaItD0L+5C*(!uEPUZ5_pj7!@*p1oz1|E zMJ%mVS)1C0Xu2TG1G>m0nuETc2l7)a)Rtwz(cK=S35=O>ptvZBi5ZlEKDq3+J_rYu ziY6Xsxm#_EM^g?-1Zx#Z#cmzj?Z?s)rtg45(&SenC+ZSzuaJwmAD_3ij^g%a|OO+wxCaD zjn~B1l3*IO6-#iXi&;~eJE@3gWVu?XGD$G7bRN;IK&9ZBmv7F5C7HBk`8*{LeBm5( zEwYzj$yA&mfOJ(~fwh9s&H@vHJm$%ZN z<<8!0n5aau+n}O_)aSs0duRnNmdcTtknT8ZItXjtCW~$Tifr>X zuM9XkXtCuxx~I(;Ysctj!4A;LrErfBYu!AOWEjihxw3%FO!(l?mZB};-(@s~fMdz% zfY^@0*CRc!ELyFC0;HsMznN2Wtlw#FTDe1}SVeRZ*it$DjkZt1+B4$)#$t5d4xdA{Rq!$t{}1&Fb=;>gJTwNf>nUn>_;0ln=O3d>+)MHKLqobd}_LbSZ{ z#4QZcERheT8@obnxW*P%s6SbVhHW$C*KU3#f2rZe-B++enLcA=iNwFz7hLs=1Du_= zwL%3QO$&&#^G3g9*yaGIVa3oz-D>48r8-Gg>p-q)wd|p{wbg2aW-#(Cu9g-7djW#t z&H0RUEoy+PBtKi@{Aw+Ub?DG87Zlt z?910zEiJQJhRQzp1ATyfNEK4a0FG0my0KOyQEo7QB#XPqA*_<)X(MtYXjSFeMN?km`0 zeYDJ)%IZKoks~4kQiIzf&Np=iLC2%Po+N293nyNTo3x$XgQ&}`bwRKm-DdY%eglQH z3&UnN4}jV*IsW;41;;)rO!b?-*T*cj5tun!E(0REWZl;no-~8?g#$+Sn=y!I>*2)R z(;8%~%Z_?Hp3Yop4d^mBb%FW`P*pjut%ScYd-BBoQ!_^)QI?^vMZw@JLJ?6}&!Q6C zU~XZKeF4}a{?zfA{S+7>`9KU?pkO#M5?@>y8JT0n3myD8s!T6zXt9pm*~qXPKS$dU zo+zvTc3c<)8 zK_L(@Rg_gEL(#G--2VA+F<-W+BO~Wy6!I-DjEo#ziWe@#g^5d5 zV7yXX05_HjE(;}4f!*$pR(#!G)^sZ(q`@(QG)O%}5)Ms9U_a1ddL|FNdN44(Qb?_l zT3-mIV+6!hq_@rCon{(k!-zXsC{7s2R^E%kC(sv5tHNkq@V!h;oCs|JphklK0LV;2 z$x+{>P{0aSoZjS#5~Fam=sS2-CTS_ql@ubC+1f7&d(CO(4Jq%W14Od7wy-HwMbn*t zb4Vz-Wb4VJ0NClR?hKVoI2{iua}zY|5qBUw7RoF^j+hTPfG^c-Hz6hDN^J!ReLg{y zVPz~DimPk2YRCrtk`@2#zC+y()`?&hgIhEW5OhjiEm}jQc*HVV9dPY$!DMGxqEEP7_^OcqG9BLnzH=^K>vS)W3lX}!A7CV#Vc5iMrUS1p- zNg-sO0tO&1#N>4#Q9eVJ;fhTeAt@ZR7;A-=mr6BW1A_*babCG>nDdjDB8cELFonQ8 z0{*Taf(BDSj=LIomD<|cGO9X6b1k8HQ5LF)u%=mM6Yp%hIy;sRn1f zY+7Xq8{rWYCt9{A70UURpeI4vadL8nBS=Bsg^ln$Ru9&L8J1d(KaYZ0;O#sa1)M@@ z0Ss=ns&%`-H{E!3fHa;U)8xljbuJJE_2qUP(tj||id$)=gw$AgCQJ@G9*tW#H2wfc zlD(*P*W5%wW?=2uz`CO-J5jVfw*i$b`u3F`G$dMN0k17oaZcXBbs&LSGGoi#9oTa%TfqT7tEha zVpT4{Jgsn2YOA)5MXtrru=+zj0dFp59x@mhmT7a-NXeK?trpMvD-%_87b5vah_! zpl;FeA?7UA&@olFryF>iz@88?F22!ULD)VX z$FQISPjr>kD-BeiC}wBO*y;wa(&h(Okc$_T;EW<;2~dxrX)Zk zv|-H5L7ux?6EGNq79GSVi>Pm3g)q%Ok_pnd+fP7e76fL$=p!`hMsiq5*vU6&LWKZGBVt3UmpPS6N zvwxSF0i%aHPLqI_AeB+dWtR#gLNYk?F%fSv^h0B!VjrlA`nh1Q*J^84MFyVaMom=$ z?1HcaK2gRVCcQv{P3nPS-A~25XRdsWBbCcw+L(H(YIBZ|SZ<9U!JsPD9PJE5jDVKx zjspsoZ1h`h!3($C0)a+u@W`m#QHN>q(M^ZP?EN+JZ&+GDhM>O{ma4tO2v+J|bzR^c zC_u|4Tn?7OKY&qUlh#BorXY`cLYQBKAxTt8knp5zt+ecSjf#Wb+}XWs-4CDv;i3R$ zjTm<%=e6i+lTohk9=m7f^R;Rzc`;)KVJ$ru(|uvFcz$1}xzDOE)2^@MG+9;d41(?l zkCGCx?*+B!2W94P;ps<>g6psxTP$pQZw|d-NSJ1{*OOY8{cLm?u;65^x%tH1rcdl< zmHnM&v;nTbjN&|Bo+mGzhvX2KdrWujRR?h;4}E79)Hh$vS1ugrG>83{b7+uCrgKkn zYz`$;@)Pm8zt+jj>FB6N_>Id>&srDAjK!J+am5~wQn*|=owpjY(rtL+WH^2D#Khd# zsWai!iK7!I=BDnRn2j6>ktv9EjnrR`*Yjo3`oT)0aBLa10ILHyF(GCo7qRyE262nI zENvtW7g5a?K)6f2D8M;@CF@^<5fG@r*2P|hNo+rN2i7+(>~jVo3Z+o&SI@(4O+JBL zPC^jN2W2W(810O)7J3yqYis+lO>p{P=>0IPh`LGUx=y<65~R$<-T6|&m%@f}9S)3I@Plh>lQzPocVlyfq#hUq|+ zm-PgMGzVbm?ZTg-Gl}vQ6~Hky5XmYgzFq{!Q?Wb7HHL_sYPc1hhUQ7FzC561q5&+- zS~WN>fjF->4130TfaRCep$!0e=}oFxwjgDgm@5(Vh9hZ7yM+>4M;ap|Iva2c(EBp( zR+ms|n(*Y#${5oIhVIe|$dnfU^*pA!h&2EK0zH@j{^2rX9>BS>2I@XoEj|C?SppFc zhZy?L@kWPOeNT=IDrfEgWD@yhbj@ow*>3A)nzvV@NeA2lU@EQPc6$wn{e^g=@>GWz z5FNdYp#^>wGlRfSrY<;&P$e^ps1bYTEeIFF(w;Mq9rW z!IcBUB5g}t}|m4eN@=6HI@mY_TlVf;1J@@q#`HLo>O=^4IDB*pva%Y z%_OjP9x^=d*7bI-lG*HR&SajPwRiUJx=Js=EQ;(2sjEs^Y!bW#{tg@oWOW{hs$|We z`;)ujP^TFJRmo4b9{I5UUC`M4#?HOZ9Am%gqlC`6BWWI!UgQB6MyuQr2`KKk#&#ES z1+DVx8q~@aBgKx$m#fr^L1|vM_z#VyKMy9TY z2ueWcSJ~}4l$0X$FR)u!U)W>iLU9e-7yby3~GVR9ps zPe;Iww8jH|I|l(h#0H}w1WIWI;%sULIxx8)=l&S?DZRdkvvU}WJcN^UYPFjl-RvAQ zhr^4UNI()03uDmQfVdGV@2iw#&Twy8T_^(P0ct#aRhTO`h%S;}4-PG!PuHgQ@4n7V zp9;E}%$$K-neCwwD+q!fn!z%`eq48(0RP#FoZTdp24LDLu55b1b5|_42@Y(j&!K5| z=u%t-ltIw@!nZ;_SbMRd<_TKG3C#n_iPCMsp)J81&e;8w**l%T^tyQCc)S{z?%i{x z*(xr%fZ5&0Az9#uOmJJLDIDQB85i_88wmY3IM{;lD2!8a)kQ} zJ#v!Wf_;?e0}yKxPX&tM003vK8aE*0fTNHhs>FyC($kbD!8VjhMVtGDjCE!Uiqoaq zx%qXd4G^5%1^l2Ybw?rP#${ZY^;I_T`${I5=rq+uIMadGOUuZJzBzhI9on#F!N5#u z`9i)>s@}7JFQz)pq}6yYEx`Fg-w$3E^dGIEDqpQ4Z%ucaC0t_gdA`NlVbF6oEMA%5 zu1+&%Dsp9pYrXthaek=?-_}+5ATBJ|U~wRYs13}`Wn`rOm64H`%+o&)CUo2hvfE%s zfQfYv*@;0LEtPRIT&%zWa$#xjsr`p;MJ>>}juD*! z)6QhE_0M)@NA0*$u@RIUPP23){^%JvvRqoYa6+<&nx5T9G+%+~OOS4YLGYuoHJmxP z*aA#FriZpk(_!wJgS+J#w1Mhqsv9t^-PS;*paxZWN=QSx&=0j(SzbY$Km9Z&^X zePtEA5nqxf`d47Dh1t|CPBad3ITo*T^RPEqmV+Ic4}es_;>hNzpr%D!e7NE80>v$I zJZeYLq$R&}2bs*{^&JQ~w(DUo_36o4_2lBoGI)2e6QT+!HvQQ;p(0t~_8wLM+{Qx` zF7fr-2@#A&xT1%`K(&yqAp7I9)I`futEo|fPS>~m#%p*nZsK+q-P+ly2{twQ?G)Q| z0HX99EJ!Cx)nhzcgRaaM+y+T(wPJBye*OpQu~VxufCN!yI)!>G~xAdn_F%(>#VdB0az*SbOxgMCONKeD}vsLnVMyI80~B z7%$Cyd#pY1Ff6y|Io;_f_>uZaf?AK4)`RZMr%78Y)(cQu5M1uIv~tnTkrZdL0{*wy`= z*+I+1F_%jlqci;Xc)SSy#2I!cEY$>i<;S^=wb<I#KwPYrP65=5SlU;qd7+ z0YTGV)Lvnu#B5;&0PHdh7lz?bt8nZkzomQ84AF@5pm3Bu;)?3kGe9<9hP8MI!1Od-Dav;MBM6 z)^aTkB3PeXxNoGTwM2qzfFUsTnQa{4ff*`R<|{SpUR4cE&p8Ax1jZeHiCUe!Rt5>5Xu5iT zr}@CdB{B=}yHnCMT2BH4q5X1-0n)v&i3JJ!FyZJrg6CLzT*-pU5BCFBEwVM z41)K!?q{?CxU5_Kda^aWFj|O%a2X@?&w8T3It=7~<&vo_gsQZC>LkWJlZIKwm>ev4ml$S*Kn8YbgxZ5T`d zn-f=IZ-h^Y-I&1>SA1MI+mb%qIQ_a~685JcGlgK5e#^iptOa26?o6a9(0BX*(3mRU)p+3z1_%nM{>0 zLMb~>1GfbgFY2;Hy;G~FD=|(%=}%4yMoXoHPx_mzM!;p^^?I9+!)m0lPDJWU7YPcW6*BZ5(oxx z>xpT9$Bbwkf~zcp)+D$d-P(w9@7!f)8-)`v`QuIESM|O!Zy4QYvPxp~#>rfQdmdg~ z-Os%09$;vQ+R9USe{z!WDmBI(0f+{s>u*8F#V{C4bz7$yBvn!z&=qkzI2YqZuwXH* z3;@Ay=R(!G&tVvLN7>D~)=@w3A$m6$)k`v;fIjBba5ZQ0suMDX-aF`gh%HCOh+3v{ zJ6?=~mu3!`IsByML_KNfmy|jxwKAj21l#U=ULjHtqAfsBhVzoOaPXd2Y=m1^pdP)2 z-3x~HJHm6CpyHgH$uef>L7#1Y-I*Si@_>Ukwn;!nL1mO^uT1C&yqSU@$$Wxc`0Zk` z02>OlQmW)+@VxKk#35r}06z&gQk`KwMG@+M5*7wK@Iz3^Hl zf^RPv2HIjqmhmp$gCI8GrJD?PcMhm3SDE~R0s-73mWsIgmGDoRNF1uT?;(P6vx1vC z;X%CtB9#wLl&T_V^kkm#C?mXFG(xObN6WzG%Nd1Hsz5L4O8Gghr8-y5~3FTc;+pxMGy?t zSr<($DdM3<1|;hQQ)h2_;^0>9TDOjI-dV;4!jtzQ(r`w=xU<%`G4Ou-ck!9IhT-e`X#;rfxm^0Z!25km! z6GzTl7LufF{q!55oAZz+vU){8;G3lW3Htr(Q79%M0*bClDN z$gLDpQ-P`RaO%iBN!I0Fr1a&ECOMVK*M*bg(BVr_zgR5Q$ht#e7yskX=4+CuF5%p% zY(EIfmBSdlJ2qM;NWo6wj`JFu6!;$zO7X+D-tA5GC7EnKAp5uC%Npp3dx0%4}9O(VAO@=bF&=PtGK>| zd5flRk*Gv$kt;lHtSrGx!+N^ZBSgERXc_h4vJP>{5QvU@n!qG@LxuD5FwDNQjEC8P zW=u&9g3a<6?^Qq+l;a>F2+6F_I}mpT{(WcXg=le|Y6=htHMV9$psk}g$QaSM{}wjB zS?E#AkZIIA)C#Wf@J#w3kZ{kT#K2QF3AROFOX(rKv@C1DCP2!8Z38>gS= zeW^T~hqyKf4PoVfHc!ERgR8b*}fF|ohFHu23?y`&H3 zI=hwP3 zuQNgBZnLK&BQ7k@cbZ}M+vw{C!n<)3{CsC7bZuGxKGyQPI2Gkz9i}V5KfDUx(-G|K zYC+SjWDkci(^oHaVE$h2bMM&j=R=o~HiBKP2CtVZT$kp$u6E;I*Oe@%iaD1Z__|gD zA14EG0*xiPEiZl>S+G47T+b!w>Dkk9TgSeR;09TcLmfAE1vj>W?eLhBf?ssNZt803 zQPn`6I1}o~b2hHhxyrf~?CHYY7^xP`;$pR@%aSD;$X+n_;42`=n_EqGU`DTgnDD7* zFyTOOi0SF$BJq!V_y}!|u&>oL>-g>dE?{I3 z-m5rSj+^3snV^^WtOT$cAE@uOrBM94 zf3t<+QOEZuF0FC-VV1m2O(^y}fS3c95x-mzQ(&8dD9o3|n^oi%_i|C(Ow_OlKNEVH z)XCkMYbAH{&W}<`v(TN`ZYAPDZ%ZWHPQh93h!6 z*-=-V4cNmT!ABT`oGA_|ijO5v9z z-c2?Tw9rbk`SHw@th84Z8hR7WhBq?g5s3+%YlwBI+Zxurs7bf!y^L<-6e05L;D*d` zNq*>T&U$%s*x^;wAV`YAPh=h@Eu}0C_U^_b8pn1t?n%IZ_>)S6e!iZT-~eIKr7jj} zm8@OSOmS&n{soYJqN(J{W^zBCUM6eFILD03&Bm| zZs?q}u}up07rJq%e=|e38FE#L2E_^jA&HN{&tx7VWPVxfYssQ;Iq#v>93dLKBNtwP zJ1RuZ1~0=&VmB#%M~hdq?X42;OOh%2S&^pwh`2hUTaE29%siuisr@NU=)jA$Mjm8(3(>H(b{t*K!*{O`?9F_<4y#0xI_x-KKxbiF z;1^hkXvp78jN|q?xS1}mDJ+SH999^@Tx|wFEB>MoE_k<%>~b=g1bKwu{4oRk4ysRkOqBUwB+3BaV9z9a{+cbtCyNJr_VEz>@gztL+05 zEP#|fTd2rBbT*}Z^tB}u0I`J>e9|Wk!&)`s2`S*zb@BuZU+`&Ph=W_y3(=3ygHwJ1 zrv`>)11oPMjKZm1AfS{lNQHRH)X1Gt}kV-TPRe(X=r$Edi z^bIKP>V&C;DhbCzFIMT-e_DGwFP5fuQ<%W7uC6I zn7wXZJd4`xokU_uCH!8m_j$T7S?UKYumwe+rd;^RdKj% zJD4J@zIAYXzN>A;LOox@83Zp)A%Zl6%nPMuD~I5UHDf|oTnnLtso#PiZr^F$sUdLY=*>s;_FqX97nS5?yBJe9oJo4)o@PTxDZ8Xt#sX%3cum>L;O670xMnFK_wOOzOx zHjUm8VF@N%WYvS{Ag(62$|PS|8HLd?Rol;vKet|L96$3t8Gi|RBAD9xIPz{aLa;MC zZS2p9>wS{G>{_AVuw8;$72aHvuwNq7vJ55|iH^gA8LRiTaA3L+);$;Cqb zVmh~ai54N@(%!yUEF-Ckl!j7UD1c(+AB193u7=^(jt3HGJ02s|Ih`$xS3wRtQ4$b8 z({8|r-2~}hAyomFo9Fn_YdS|A5_8;a$nI-9%{6*-U@I$qIqJ5Q1@Fj@+TUrL_BAwh zqk|7E`>R0fLJ}=cA^69|d}>f35}SY~c$e`bnM>)P5fb6bvtrTcqB+F4B>xreSs$V; zn`v_jsh8`yQ$N44oU8}bs=(F1^RQ!50EFo~K&nX)B7G=%&3Jw08;Esim?XW2>n7@~ z=1ONsvyztqqhtupI?!^6AItoKgM{v)Qlm(}=OC*R+T)NXmrNA^77f^wVJ2T)LPUl{ z<9IL-kG-T}7NBWfgCWRj5eZ0ym-1R1i-got(Gc7kPD|{BbknO5IhX0CCLh?oq8YD- zla0v!fPQ~N<{MK8Ymz8A)E`TgO+1eq6YOwY(xjrprr;H#ZGq zFmB?0woj|u^&n7iA)G2GDQ2_q*SfzeiHwEMWVTqizsulG7Xm=?T#6SzNX24 zxJvq^MD5{r)2)QYp=pZPke9&r+uRoL|G*D4bZ`EFhVHFbOuNnTxo=C%Ct9Jd2OD<< z(bGC||#Vz}uSOSN+2Pr=(8A>GYfD@frB=S8g?8lgao;MQEDrFQ^t*|NdVS$N!H5j{jDLnqKntxY)2O}L=IEm+*ONf z@E2bNtw<6z`tif@AV!F%jYD=zJFM5~4b8y^F$5FeA?@%IWY6M4KkoVGHC5+sBq%wD zdVZJ@4C<0hJ@`-~NOi1Mw7bWkdy95A%RrSHLkZwc=R%AswoyY+P#tO@jHC0B9&=Q| zG#$9)!$Pv(Je??#m`$fRGEh?j7@+$d@_i#^3+rS_)+4fXMmYF!7@(4?Fhx9F4^XuU z6$RwcQ=qg*p=9oQZ5~dw>R;w-pg`!r*(cQ*B~>)N86blAa*omPob{!&5v4n_hYWBC z(H}CO7O-Qat;%@rj?h*}L@g$lQ`Mz6_A0h}lrwV;ecFYUnq23|v%zPA~;vS8Zqr`EDk{dfu`m)6qA~3*0?7#1n zfGu)cbYiok)px)UDZ(P}-~wWaP$<9my>w+hFHoQtrpelRR|7&B5~%^-Y6O3cA$}VP zvEdg}t5%T6jdDZX?)-8s zhRAfBHAE4Y_6KuZBBU6{KuEo%E1sJh8G%?N@6JkQbIw==CMY?%Ii`}pmW&IpM*?i* zb%7ZKo1_|aXW?RNe`J<2pe}_St0904OY0a2QR=KtPRexn!yDH3>r41rej-7`Wo+R> z6dr{Phxm0ImCUD)9^ihZyB(=96d<_o35uZ-v6ceRavh@F(i{T&R)0bF7|sn%S1eR^ zLb`gPT1W%*loE*H(<@~`d=iOh$jY2^C6qp{Kr6h-5RFz5!laRTmvyv~b#_ci%tZ52 zucrb-o>VDdUIn`cqr>pkqx&~<@GxyNB;z0Lo{-Kpb+o@Kw0aNGD>eJzoTeWTO}fto zef2-mj)H^*m1*NVwj=;vGH-DyWXodGHJf!Hl}}I&C!|C04^#^xCy~n09)pAUupGZZ za^pp#_Z*!T`j0L3_hjCpf2D|;5aK<$E20F!N;K~CY&lAXPW#N5U!;`U1Xr{qagZY# zRjw-0eU1iWc+R6M*pH=ZTJU4tPo{d4yjN7!_^VPrSwAM^K8S)(&k2>ZpgdrnwCN9^ z6b&ABJ7NLWCGue?z1M-U=m!w*gl`m4!}9~?2!ni3x3}i90JY(~DeNyXe@7ezK?2Pv6fy8__eQ-YK6F@>ODj0{^W2@ts?*R3% z6foo$rLm9*4ud#@hx%n&&X}CLr@%bKulB~Co0Ty*<_Qi=YGjH#l_RAtD7^{62l)%@ zgdt@yY23{M%mYhZLD{<*T3P6riG@rUfv^WcDJ$pd%4lUI?+=S2AFof5ma8Rw)<7D& zSj>kk$PL(XCoiya3nmLD?z)otAj*k3yzneiD631^JTMS_nQ&8n6Y1|0m`j1>QNZ)1 ztQfvxc@>!-nWlItNOB=ts$L|HQjwAb#5{O0E!xJf2Fl`XIi6WzDRNXFY>i z;0zLg7<)KNm1FEtw#o%3bAjJ9llB#2RK0*mA$47F@PP

*m3mS{$;z5M@CfVy+c0D!S;l59sV%?X`3}xB)-asRt^|Ba{T0x`6FlMCT5Y$WQIJTxG*iEj_-VTR^s`&(ODEq9Klmi7y%ZNDZ<`q|*lvZ_^IZKI%}vwXD-G(R>EOTq_2?4hp(f=OO0AQ`>8(#F zJx$S3#>v>+an4$rgqt`Og{;ae$vhL4ecQQ0~JNFXDy}4sh$7<^`Z43Z)zUI zI@#OHw>_D^s+*1T$k&m58Z3=eX&z%ELe?FcJI4wIW}O_XG!KZtfPID{9l>WB$|Q;` z!H5*z!1e}Sv3V%_J3{k_268b%G$(RAgkgwH*rT(03TXapb7Uho@0Zo4C%0_Cv&uE5;S6ffNGT$@UC$bk4H zl`tMRkS0~wgS@ZX8&I)RVo?G>N(}b1NysGa6A2R|OPsc zC+NcP{<{sNS&$sK0jH^Qv~FQi70|YT&kxh;*8h>Nk99rO z^>o)KyFSzPxvoF%`eN6YyMm8&^&lk`bO2jmqTj1Ol{r`{7B0*~Zl32>;iPj#8@iE=)`A3gmr z9a@27w^Xq!E_5T3SbjGq6JnFf*FfHG?dJeejj*7OeLN_S9q#c}^0>o24#{K2Jsy(B zo$m3~^4RGfUn7rQ?r~Tiv+nUWdE6C*7K}l8eu;lpxL)d?6|ipqtdL#jpB1zp@y`m| zkNRf?uE#$sbl3Z51@FiFv%>e|?)jj?*Xy4Zz8n0r!uJ#YS>gLh|E%!!`DcaiM*pnv z{gi)J_YS-&kEmu|E%yu{#oI>)junI zgZ^3JJK&xVDSQY0v%>c(|E%y0`DcaikbhSAUhSV1zSsC?g>TqDD}1;4XN7OXKP!B< zyXRLce53wZ;k&~>D|~nQXNB+Q{jbz~e^&U8`)7r3+CM9NC;YR*chWr%D||ElS>e0O zKP!Bv{IkM0>z@_AIsdHio%YWP-`)OM;d{M*R`~Am&kEle_k5ee_ly2n;k(yAD}49) zXNB*qe^&VZGykmc&HHDCFXx{XzPx`{_|Exfg>NC)>-e(*h!9@apb*AMgMzr2G$@Sc zlLiHHDQQqBi%Elm`G%xH;k=MED4>w<9UKbjif=fekgg^T3TY{6P)OI328Hzgq(LDq zCk+Z|C23Gdt4V`GT1y%f(u+xhLb~o7?4fcgX;4Tvk_LtJjY)$-`gf8Bh4f8HgF^a1 z(x8z3=ShP?`d=gs3h9GMgF^ad-(ZiIzne5Dq<=4IP)OgBG$^ERO&S!^|1xP%NdK#( zK_Pu8X;4VtmNY1&e?MtZNPo#U*n{Tbq(LEld(xng{?|!^Li!Jq28Hw;NrOWANYbE? z{&Lcwkp9D@K_Pu-(x8yO%Qx5~=f6oB6w+Tw8Whq;lLm$K-ARK&`m0HULi%e-gF^bA zq(LElZ_=QU{-dNpA^mmVU=N@7B@GJc`;!KR^f!_Qh4ddM4GQUFNrOWAn@NL0`rjrE z3h8ep4GQT8k_LtJgTBEYL;qdUppgD{(x8w&o-`<=A4(b&(*HhbP)PrWq(LG5ouok_ zeIjX4NdL#AK_UGozQGF*~E3hBR08Whq$NE#HHnTID5U>K z(x8xjI%!ZyKjRzh8TGG{28HzhOd1r@&n68D>F1ILh4c@T28HyGk_LtJ^GSn3`o~Fw zLi&Ft4GQW1?HlYF^@XHCA^l>~ppgDa(x8z3>!d*;{Zi7PkbXI7P)Pruq(LG5)1*Nm z{YuiHkbc!S*fZ+iBn=Ab|C=-@q+d%K6wFC28HxLBn=AbUnUI->0c!c z3hDE{!Jbk7U(%qE{&muzkbWm=P)NU#Xd&(wvk5Ff z3$e9%e87b8bC8xG_+0y52DKM0!Zsk?Xt6eHeEH+9>_cgX)wJCYRv+9@GFD&)x2m{- z5mVpXr5SCM9Vo z6aCf()kib(a}^?)c)$ZfoD0 zJiRK!9T)sOyLZEE4mXjC`Rb0nK8un0?#|2vQU83GnK0HWI5N`w`KbMRK-^H^bvrRK zg1Z8wueuYt>ZOV&QXdV36E7JV!I4L3Uj}C zWM>`hwx$&<$|xKSyXAN3YYr+f*=)+%d5=c|;sj7*7}tbC*Cva?iU><{w1EzWPQsI6 zYL&ixtBh3(f}L4kAQ$OJ^Kb}s`-Ny5HoLVJHm15Oo32j26VCKKU_#hFt~&WnF{TFB z;XOoV!C!}&S)VhR33EMO&YrxG37*4StNE@y)4_4Vts-Kn|Eh-F@rFU-p&18l!J zvFB^!-LMAZdSnA#Fc(60x4nzV1UF=jyD&|?kKtU2piFQhUa^_J)2{%i8!it;tIo}k ztQ!ZhtPwcvJ64k{svSsu3TriL`USW}D$HPOECQ$FV{|KCtsW_st4p%mgPp;+b?v~# ztX7|?1f4vx8uqMMsf?jyA`c?+h`XJ-p)DlQr*j$)(}XtJFIM-;`=` zu^5+;ff^jIHhd?DbWgTE5A_}cKk8v%{bo>`{HtSMHo!k{jNXGE_BQ~4;|}}QjltCb z9kawmPw4_&c3cUOfkl9|Jg-g@uCY%ZD>r3L!!!O#RfV(nk>V zBF!XgX$B#=Pk9x81AT|;w%Tpk^j5o#6RzKCw_P5N!?Io}U@)}7Eou?vvZ@<{=6LES zBqEm9z`$cbGM`nR>M+Npygjd|b;Hi(&oe(UE@D|;xcwV@#a;2Dj53oNc@_;S?V zykcjGu$LbFOdbBtHskNC;_vJhpu~Q$z+licoG6kj5gPz8HMZG}_?8B2nB)1v8^A7c z681jm&GP9QHO9Nsi?1X~%!-r(U>fQ#cj3oG_}oEw^5 z1~046dM4;Uy;_EYH{(rkY6L5Flj={;slRDT_t(ALmwvgg>E&A8%PTy^J-Sv>r?^Ge zM&0WV5WDYp*nJ>{-S;bYA83Kq-G%Z7(L7E`XuP~}sXUF# zG(8Yk5OnyqIzbXe@U(vsuF`1C^l%gdAhY1Y7#v6TV(G0}BkinzN^uM|pzC@Fg12KP z9dp4Wn7E=A3Yht3rb%@CW`_6h!MphSyR!lQ0nh!6d2cqrKS2k6e*Y#ERFqNO%G>#u_})VwMAmOOWS#S)_`lvnPBiF3I zOTd9P9%A}%+1eot5PT-d4^B(-w6PDdu6h`vX=S+4oIr`$bguCA$}@r9=5Eh%o6bP5s_g9_$P)otjfv62nC z;M2N}PiMQUYo+B2`9i4*NeKaK4D6VPJZa57D0c7u9Il@YI6$5H`APJc$$l#PnQZW> zZ1?e`SKmQ+5he)KbUhEZ=`u=yEaU8e-hqAqA8PLgpSjF;haqqkS0s$_stL~s+%yQ2Li$leWW2wwT$K7Akh`YT9BucgcJFPsdEKYgc>n>LL5Y3DS2O9=dq|n?@N`Zlw1B zU&#bRh+tm>VU^>Nkx94+bE{vsNyn&A)=0P4?@IKrA27PT0VMeX>Gt|fkO2>BFlc6; zsv`EXj(qLLc{XdBh}-n!VI+N^!}LrZc=aGGby#)(%jpiex$f0{X;kfNLe;)aP%y{+p?f2>aA`C^Euf%cFAEwsiA!IRnsTte4!EZ> zn8*R_VbRB*!g1xMazJqCGA;s`UrvmItF?Z}Nw0V+VF_I)t$fWGJ|?zoR0*IVitYlEp^)E(wcmU&wTc!WYV4E&N{T!mF6R}u*bDrpAM#O5*kB?}kOehd$D z3P||5sgE0i-LnuP<;(pUbIE>L(YB`7eKh?(GYr=L?z z+nNTVvo3`#7_l=!-0AVYVjG)2*cbG|z<&w7zpc}_RML>fFKslvdP~qJG8|U)!=2^} z7W_PeDH_*vz(%;vyI+-sF2o1!3=Z;QLuztoP;oe!k+wCB`l_G@36e49fi9>CN`5RS z+VtX3FmwzD9IW>a%89P4g!15>!J&Y8nU7I+13meC+L_?4F7rDH7|j{Co8WqNAZZpI zP-m|es8`(?ye1fxZooG#+IR>gjWq8`F9pR%`(qO*!`3iY(F{zu%-ZNOH<|S3u(Kfk z8KE2vA2+OM@>XhPZwqGXR=|()z%?5MD>D*|`*dQ-MBaSOh5(*#4{%gp#PvQDl`d1Y z_)+P;0WWt3qroJu%c}7lV%4UL2&^Z;WP*2fn8)phkRGb>JY(7BQA$Q%XzJ!zkZV}m zRA*0g`Ofm(Z>dFQtW#5G&p>&xOyAi5PVUAjgDm7#V=^5<_ZZHQYx=(jGhYkm5Qnr_ zL&R}jheAjvfCvo5Q9RxRCwB??WyMy^oq5&}> zsMXN~d&Ra4P5L33mK#fr^d1B|^jP}7`Z#Ks$OOjV@gg&T+5bFp!1N=}CSa!Z(bG(@H)MlVVSw)&bBn%0IT^o4-* zA-@1Llt|}rG|JpzcyDKYG&F1v3o7wub%2HaNC(9T!DK>+b}Qlr_Y$KK8Ia2h(S4C~ z*9dxg2zq80P}I1Ru0)*XGVqq6D4a$3AVRfsYD$miWd=E9u@#38!Ah_Nl&pn()kSo! zaa)&ah|Lo!vW^@k7{&Cz1tpmN9q!$_IS+uKD$Iz07=IfbYEdeVE>WMT#uZ$hAo%Ki zp*}IxmWnz;JX<0%3T`stiIZ~^BZ3zu2V<0VZB;C85-w@;<&vZ|YsxAV^Rfo{BPbKm z?_#WqKF!=Jn&PL#m)B7+fEyB0lm`CKl^Z+8RxxegQ>Tf^ifBu7Giqux<;1{W7P(BgI(`sUncZQdL#W97E1G^!^Uq zM%`?{&EbGfIk%12-F52Fp|qY!F@iwD8@N1xKMXoc_;sF z079)I6+q#@K<0uFOu~+#o!FA|&dY(i`hpKI#%c)Ll>9G+^v1atW$G0ZefU0}Z}F(_uWh`fHd%KM66F3gMV*Z|>T zj7wqZiAYV&SnvRgEH+6LoYBpEk;Wa(34xVj_M|Y^e}v?v=2^%HzzlqnNIUfMxU?^a zWzfu7NYt<%GdG8gH6{T~8yQ;HRzAqFK~nJwVc{bZqb?vgnmkpQvj)Nt>-_l$`c*Jo zC3Ry!vZPx0VkoM8vJ7C!W<07cfiZYGM}i7l2yPiyNzvqS-5R{Ajk_* z>>*7{Kpel#r6XE%LPy%f0&mEn#ynt{3}3~?c{Q}uAJ()_VAAGLp0lGA=1Qkubr1$B zyEAyq0Y`IXpk47NhnxK#Ia(MYjNtg3tcCk7N5+z?z3N~t3Xg%6#bG~kQstp2>|-y$ zOq5FooGTY`SM0u(qcwYW3u9WLgZB@z7?Mh&x&#?hC<6<#7{Yj?Q*n??HiH5QAzpyi zQ(B$D0t3M#M5c1I@v&M}E;HhSoel1a`0bNnj&&7*2F;p4e3m^Q9=?D&+j`&kI1?Ht6 zzx%pBpFqQx@OSX&U*35Ze}9X1H~0IW%%AyZXd5*6-G7Y!9y;}gYDX}m@9~WMbSQ&{ zKbst`tOH3}tc-}CK`2{JBlI=Zh!tiaia_xATnFq=qv&cz{-61PuuzvF!wM%rnmq(k za!NETLz4jtt>M(hxlQ^;eh$e-oG@ZRw`0sW5cC-ff^@^mYq1?l;e|L}3;RpUOauk* zni_MUNj~p=Q8<21G5f`i!-p?Dpc3krpW6wjy`*5}FF@tg=>uix-5m)_l#`!xdfnm_%O@4V@e=e`14TyXe5j(pbq)w_PR6Mz4!k3RY<|L)WO z;D#Oe`!9d#mA~}WpPx9+cYb?xXL;Yt{_L07&qv?;ws)Mo_m_X3f4}WlZ|?rRxBSHn z|Ni?AzVpHTx1ieaRSY>fS#Uk?sEI8lL9SZiNl1gwJ z4-P!y=v_u7OkKa1S1N~waaKTmcsbp$kW;g|0r?5uModO$4}9rCk{(7OlEWamG1%Gx z9vEPFPfgi`<-Gep{Zz+M^PYymZplZR9QzkA*d34bLTtmuk-V3u1_^dT>^xm4uD zpbRH`&%x@jTskN901C&kM7YP@Z?R6jHVSH7J!TA%c_HPBC_7kAVMSOfNKpbp9%W8p z$U+}9qI2JuODpozy*ZdP)CYYM&X~EgJS*VD4BzF#r~^W^>1cWEJgOwno37A=RCs3Y z$OPR*J%1_vz|nFU8DvNe)~IS4!J#RBv)kv1%lHNQ@NLV0@l`CNDqkSRf9 z!6r{l&mh~Fy^oH<(-c}L)yupoSaH_6UR)0j$}Am%c5raWyYB!lY_=`t3j=C(OK|!~ zhJ?cs8P(=yRQ{{KXEp&8v_6PjAOjJC&^r?3z<-7a*7(1Ar z$*Q8ujwb7l_p)@fXvG0^z%g`C!p^diRT@yHBw_+Xnd|w|{ycBqp;nxS&1`_8oFu1W zJ&1L9Vv00+zd=SUNrja=d#}HivJj4ptjEjC^W3JZ^B3X`*fx%y9G^IA$y9m+&LE}8 zh>1K$7C{#RXq3dP{dK)@dMY;)zp~pDZg=k6E~4=h@p?VB17f=u0#DM6KEVHkvWQ^S zO}XOUUqD1X{3aO1>&UjVDk)twrI9sg^Q}v=;7n_~TE!M+FR6_d1+it2F4LDS{>+_` zL*^9}DWIdJqz(lcFk5XxX)4TFR-tMUj}&Rwdx2^O?HFlj)DE776-N^Qa@tBp^w^pK~^0~ zQ4raT^aDbbZX`1?wAlnnLd|v~L^wejPZ#M_p|)}}m+IbJ(^dgJ;5GXWC|NZdY^_3V zAucR~SpiT^TUi4#K0!Jo98mzyAeeF7%9%fy$cXn;{^(`da4FnKE{l&rVoHDhpiAvS zcgn2L-`k&FmJ#U3061P-f-7v92_yxbD=}QbDFC=iH%s`N8`A>U@O@4QEWe1;K;9}^ zV`MaJwT{=8#>g5?a0Aw__Y0x=MPWF{d>WW%ayX`e{#%J6q*=wXh!K58f=z-0z){~P zgzt#)oXj1J&8@Mj$^3}R44!G3PJksA0@70-tIAcxvhhgsn@!lGZe71oQyW3JgC;TZZ)XxI(O>LWQGEF%NNz zc5Wvg*;G`~JwQ}^5i`=uM2s&;Lz#8*1UV$o?Uez(zY{jCM*j_KA7{Q{K_sI#-qROxE`N`jpwM9LPb0Zf@zZFK0OB#6fNOJp5G#` zsvsp&0Ta=d&QZ_;hxcpwLxsd)9;@Rk_fOxyKqpKSoZj&u6v|Ei*Yj=J_p-;GGpI}3 z8p>_Fm61i#goG)oi_=gNS;=#Dx<+9I?B3<6Rl(g45?~6mPVMEQwX!#k3OzMnhU8Iz znNmdu^?Ja;Mdq+tjSh33;t#76h1LHIXr%|*9r#CE=xN5O2cMfw|3 zUbPM1p9wMwP#tu~t$(idXTVJLDyRFIP|dsKJIxrhXjWOr-!^Hbystqo1LuP7$Gu3A6O8$q3_iobog;LDiWrOD{g^_aTvm)31b}j>aP?~i0t5^C zNhgCp1$#pGogunU`$A4zD4XStV=%EoUy&`hC~xpw6W5nD zt5u1i5D9dpBV21p3UG=Qr>AG4w~Qeg6d}-YLAElKNvO;fs67Z1Rgu+ES=OGliS<*A z0e;D`tsO%m$u;U|oU~jmh~+^R$o2$Kz6G2J7(@?Ik(fMN^~1JAZ*Zk28IHB4##EMO zEW>;i1pV+RhYCxMWcvi15Tse^(UlK57sC@BHW1u4rL5>8d8IfdstC+@3R6k=Nh$is zfmElq)fw!j<2>{*r2(c`jSA3gIBsFQHJtV?=>@_${zAdbX(;pEUQgmNHZHm`3@W{z`r$;kI0H|*-H zPa*D1m`Y{kDHTTsZhyhvId~C5eq32I$)DNo6Qy+%42FKoy-#S$36JzV^rE_JRMmGc zRQJ=O^NPOS^Lg)_K?322GFWl}lTwgHfVLVmE+9nC_h-C&5Bog_cYoaK)YhOGqhbb6 zE%>D1&vTMr9<9JWZhQS;IMegsL8y{20kv1s76Teyqf06Tw=>9`@MTJSNba4m$2ozz zy%;XRk{OwI?FceM(^^IntfW{fB#7&KSP+NfUG7qF_Ty&=DtaR=b4Aj7=p9F}Kw$RP z+$5NwHc@2Z9BK+ye80sRS#F^m!M=nP37YCV*uv1lX*7Uj0t+7qqzc;|nkC3N3eO;h z8xt>q6{|^#X+fBFn~n%_OiMBfP$+Vtm&&jntEmIfR7W^P?LZLXO-brPh?CNnZ&=lfMDF+lc7lbTZMl0wKUjY{)S?^#d%;j>c zCCIaDDEja_VaWJn+wHvlp7vf*rG$f=Qa|SK@7&lC;(u~~ZEuukX&DUfkc)O4({SgQ zT`I1OlJ6mSNQd`+&G4s6rLP_tnNfmI9G?f0>Nd!mlJ0}d-Urd#*&T)t^l%!%4ThVA z)-2W8i3<`d$X%&O*o?=7Tddry?4_KYc66-}RBeYI87=|)E>OALePPF9A#hmh4yN}) z3Bh9neo^fby6MoJOQvU;f!(h%?F#HJxip4zYUz-h6DjINytoeKjD)@nK>(#az`7h6 zxfdKM*b#Ba=8o8%>F|cD1+mgWfMS019H~L60Inh0qhAPXcA>R78Z+Lz3JAbIVuKbj z%uY}4QY_oV?~X0;jsu&!sqcsxE-qB)S`@sOD&LbZ%x>Tc$W);6ws+B)wG1@`91b2ZK0&DLiJCHT{ z2msA2htj}RXb0zIe~xcI6l#;jYO9aTmv@+ldL|`g1LraBVr=*w4vydd)!WnT zHv|dtJwZ@{@fUYfQ3 zb5ZKvl3CS}l+h&tF5Iy#N0Vk?EiEZifV;PWt~Cv?UNvAp+U z-_$^47Qig=5Cz|Y`(l+MJ8nVZ5r8FAKwJssLc2$(qKPK}aELidO>EFhyoa7IvgLbv z4q{!cwaPiuBnqK!!L%x%RZ2)cp6sG8geRc<>fH?Q<|)WS{~VB7Qo?c2;IuUe$0)r{ z>Fs;G*9S0Xx7~Hx-Pc(vZgK;nQ(KA!2m*xY7|tX+E}8xJm<{w}8g_SE_hS&a?8T;8 z@G=b|G4fJaR%ti?X44^^pfa{-7Lj>&$4i3Axg_C^&9h}%v}JMMe5nev#x$dJcBIIc&Buv31xThW&x_y#IhSE67eKI>;}wTj$^2m60VF4O z3MTT9Lt1U)*m4bH&|Za(cwJHcHcJ(Oo-i;~wl#qZ+YmYr&NH}yNF+f52IR42Mi&qh zl?a*>^f|rBla+&{aDtjLO4s5I1l(g&27_ zy!BSmlpM4Vg8|W7P7_x_>UDpz4(~!g2fsa-^IxUGy&nE5_|}d$Pmu1QhaaG1q#Rcx z{5b(Xyakt%t*PktPh1uNbdnll-|Z?TgDSw_tlo;^Zt#MEu>p(?@**$0Eiv#s^t5kX zPwZV7R-X9sON|NSV8jwrs#QMGeX^JkX>iohq$r}4)QDmSsaHat8PGT#I?th~n)p!` zzz@Km`8p276ozujkZC88`#EN#Thyxn=rGXCcvb;2z-)`>0_-}f8BPNLo%N-`u!Q^g z+@#pX??g~`(69cgm8IgEN&TJa({^lkh$MwjpEzv2g@vLGErGgW0H6^b~G<4LNOpk-@ zo8Y_Xy0@rZ_k*|46Q+tPQPxFlpY0R`$<3(7R`j3q-BhgObREdJbXTeQTnuw0vKNY7 z0%cxF+ZUZ{8hHuN9{g9KBkRbEcOAE>I{snj+RzLUt-VLKZN$PtN`iw9y_!fLA`rX} zWClT*-RVLa-V*X>=Kv!v$M_ZyIZUHc zEBq+YVS)~4iSE7>+TWb(ElDClVIbaloVsEPfC-ZF%j?Lb#CvEgJxzk(OX7uxTTH1& zoD2nrjzkEN!vS9*mjLEo^$WeQaip{g>q@hwr2EP6Sn(1jWF=kf42Nm_3BP~dQL`$m z#`i`67%W1z$cJ4Ux%x7EN`Ul>F}$7V(hm;46ocKaK-{3$4c35*fn}HVaYL~1YJ`*v z=1YOOE|WwE8y70PyZ>%A?!sBp1Yf}pc#~`Zx2-5X0;OQ%bXvhnsHa`S+->NBTZhc0 z&^Cj*81_Vl06MY3$I*}k{uYi(Kn3s=!WMrU_Jj4==c`CJ5#!7uO#&&axb<7GZl_gL zK4gtdwTANrNVynBMo1ugOzfsGgCS*rLbG|+USnklhY2T*f zoh&-x5+6G^4_%tZX21ij)cc_F^DlWjo>QC?+aN#pV8hqTb`Hg7qY_Ob@dz1`b2F25 zxe0e$y$`}E2|iB51+EzFTS-UYs&*ELui?2^UdMh<^^9N1iZ_9L`lcr+K~S%7HlP^jg6- z5Z63QR@jbE7rDMD3JtM>D*}J(oxzP*l+1gC64Uq>6v@hX=dDsX3h~*=b>>aQr+e_B zcbCUyEoKIE<@qHUcFaGB9WFu+AI>2_XX^iN@9aY?yURR(PtC2Wo86MVSt>j2Y-jhH z?J(7$>ZCe$XUei8-RbVwwbMx|-4&DBwyAr&>vnf-s;esZRwZ4MKm>(xQBY8bFbmGE zfw((@g8m_lprD{I0|pcnRxqFtVFU%;7e-+}-{*OL=bT$FNq1r#8PjOG@4e^zUY_6c zd){Br_Yea3ri;5-0+*i74%V|*YS~o%-_)A2(PsAF#-tN6TXj}Dq=l{wT$zq%O%UlT zT$_x0p{mkZ?OpYjSd{*ZJvZ={YES#EVR2#S|0GZFDOrRCG-$WLTM2p0VTl7ZtEXG* zf9z8zz# zxB#gPrx;FhhhIP$sLXl|rdd~To(u;|UOSSRcqif{AFi3=D7LeF(K7v8T^7W#NlZH5 z9`GG3$ZN6i^cpl7dLRl(oN!+IeTg(Ed67bV;cV?<{o@*+ze*KUS7Is(vb-f zJ;n^09LFf)14xcc^KYd zpc$Bu*RV7sA&c9lxI89|%c+~#X_3JgGKesQvo5(>;>V05v_>O9dyYK}?n~HII#63e zQ#lc8wfn&AvF{gT-Ee5VBR1?dQ?|8v?P6T7f@_=JpVywp0S>?5SwxzOs#Lq$-R zqC%BA6j@o4V*mV{$D_!Vdv233argtA<2cJ|CA>~wa}b^(YMxnPAh9FRl;gPCElI!y z7@;Q)9UrQUw*)my8|wRn++;<8ShyOUb`0283o+h}E=h9Nc`Bn5S<$aA;vUd1om7yN zur_x93g%hNi`ZAU6rbai$&H?Rt9?I?c(!Zhlh1YG_K5}<-Y#K%*R2IDb-M1{lF3H* z#$)tM`gdi_4gt+0p2pk7)wMiU1y(;)WZNns_96uEs_M%L!(b7DnB|Nx8Bh|tQ22uG zz*N{{aAXOgwGcD`>B=_4$zsWU6i!-f=ciM^h@B`=d7E(G2RY7t+=&HVU#0uJwLfhQ z9HSQQQYz~A_LJP{=kI~Szo}acbkn*{4EU25`@rcQi_C5 z$!wz_nZ=uQT35-Wn<|t+kB?4+vmSoT9q!-GHBB0XPng#&t99$sEr=2sgK9EbqXo8e>4Er0cfP=UHhOCa<^t-{7M4F5 zkBlwC9n;F~sPh?{q(a6Jfa@;l^EM)ij@2)b?a$s4R10#2Ar6ymnZ3l__P{uvH|d!7 zr6_F=5<(>UX|`LSkjEF_M4(WfE!VSu7^8aq^&ri~ewr3zF+S(O+G!2ml{^dC5f`{s zLXSk2WqraH(pl|Nl>nDj4dNh|(&bI+?gObH?(u?bI3XZi?krPzgWFzUkBsE9bu5uR zw;pVZb4f!H?f}dXsPondf)z}>_3IE4&cvbV>1nco0WVeL-)-lN9c8IYioZyQ8|sv) z)?2>F_{*!`M_}2FZ7E5_;=bHI!e!A}^B*<-?&fp{xPIK+b_x?y4wY&7YGj^(RYEl# z824FMJ26|I7q5u$m~b|Z%@%<7!a$v_>2$zUKZtak%I z!uhCk!7N;q^<_0E0b*;IcSXDN)_cW{R~4#Zw4HHOmjee81+-DGWYY6vS(8V~LZ%Wh zb!rkK0EI0km#FKrhfY%XS5rcN?Hb}m)c zPCLjN!$Y5|H#x_j+tHly!;fJFF$glcpvl-z5p@1_&iA1c+wgB zhW)aAD#^?Wk_axy;mG}*rW2)sd?KX-S8`kRw*Y=kel)iqe^SR)c}EX&A9USB=}1iD zfqWqriGA#n2;LS~Ws~5vFS5Wf7$kP!bl&L%11W?gX*rDiv_+b%ER*NXS_OGZFKpt{ z0O}yIs5#gO{elZZiP^i>Yo$Wq1%zZl*uV#USzLkQ0&tuN!j(ww3SyPFLX`QM(%2_I zS~4>;6at;XHM9Hy*OHHg38|=vM%8nX2DgtHwSuh`wbf)%q@uP03>A#Fl`ov_Uc(D@ z2D^KAsi?wEmFnT4p>NjzUPJx7mA-ePAqXm~GNGoG5csmZnlC^FPNkA3g_vn}t3`qOGmt^K-gG%x~$zou#L+ z&*_7h-X0_aI}mFy;nF%oR>!n<%p5 zKqEVvG8oKjlO=@JO*r4aPP>%^_1RhIK0%?gvjyDS7rtcykJfM{sIrsZ>TyZfDig_a zYFj&CIk;Nw*O&@WF;Lr)^afAWvu=|piE0`Gtd?flfAutcmF2N6l%@(rF)k6y-s|dM z&Q^eLQcPJ0O5-`91**k>ilw&5H5VB;QM4LBDWxyZ3mve3ZOqY9z1= z-IRaz5e+#PffwAqNScMQ6i=bh*A@KS9%Svv`=c;JPpH&}Ir$!4#g^UccUGWYhL1=& z5|Fth7=&Vu*2JN#)qeb*2`4$c3vEYNok*kHZ);h4h@f@i!RMA{XW^X_2MLBKqOM(y zq{tVy_mMX~8a;!j4<2kY1ITSRXbVli=v6Wj3S3EOHBC=Z4i3${pf;)Gn7x^{YrNdw zMm^T;vG>0zi!zYPng?aYHF&b16moFeQN4Gw0HC={RCR4H!rq$m_GuKYc^1^Obmn5mz66h&WM%Xqqt1%`Hr2CR@aH3(JlRGy_-4E_7UDs8o58y}F|- zZyv5-F&?_SkYwx-xLFWOxaX#DU^0q`v*M~xxiE>k6{C_vaY<1lR`bv$>5?4eL@*iU zxyTHNEh|>`&UmtfB#Gx@BdD)R6(nvY(tzX`Pq{;bTmrR4OHFLTCCpm(1GwECgO$m% zfjZ@5)Ay6ZB=ddu0n#n8r2ZsZ9#&EMz5mqz&h)&Ut_YLeOca?YTo+QSfnz=?za)l8 zW!KU1B|~y{L%3_?I^IGYFAQg>>L?m|55k*|%V=Xd+wkV_)-WTuwSn4mLI|_kBqs;p z^Leum4recCO?3!buNZtds0C5}Fr|nK=y@4HIRIs?$S10=lbtTvTI`juj)z@WW`Bu4 z$0?6WMwXkr2v0#p!mjEPU}^UYQ=!K3a0+Y*9`@lM;{%feljZ&e{5ypmMWQbPw-N_} z`B_EJa-lF0`945}>UzZyV<$WizuYQQAeJP_9?5v*CM{;xqWJ;Ak7O(>ZvblHsqS2V z(AQ7h!G|x)^vGAB&kB|_ITOZMZZX&9wTkwL438nm4u#;v9E-FxV#Je30F71kDZ}cT zDVWZ75$;JzV6$9UnPt6HW{&udWsCFS0r6h5_*JJU_7E}zyFu_p|8?^onh4gzi``!* z%Q?{j6f2cF$)JkMviez=R07;UbeF~EhXbu2vto@e4NX$s@$qB-_}_+V>(tVBSVUA5 z+l{SGFbmiEU|@WK?mAj!>51g{kyg#h(tBPkuLI^G%pR7GCNJ8~kcnS&5FBAxWmgE_ zLCfNvLzLV&B;AY6n)bv$z%~Wbn}8)%kdwsNY?s!|5NJAn7rWlbfOx%YomD5AprsU( zWSyR)U8L~B-*(ubzOXCN&jqP;ippUAwgNqPx}DGO_R=6gRh_GfTzdnSnNUSdc{869 zx;!eku1p+3F|VHiCv_92@EV6;+quuuUzC<=cQ&ObntB>2l`dkqNj zc@1R^HW+q2fv_;l>$G0;tOkIceR#NRJ5kF%GMxP)tDm{3#;`PK(|G+wGdee$|Kl#q zx-S~s-3%QBt+WfpYv2*D<=LYnl@=8^brkiBkFtIs{mad6e=%}l1QM?JA9fD%h#Z*l zK3>o#59eXfPYq|k$;Kv6dTs6d(f0{MeDD8!-%y23sZ^c@GmWdn4L2TL+De2G+z7|9 z9gQ3l=EKE{lV)~h*Nm(x82KR#5TZmKMT%L+EZeKSF8f+V zVi}OoR|RNZyK^^40VeJyE4JeKPR(&MDX@r;t*OG=uX7L(-Q+MM8lz%oqU_y%)3Sc+ z|6uFB7Vfoj{pKh4Ckqx66fhlpo^?l3yOhkkrZo1?(<+70Eu43GHW4!3(3P}le;d~ev}?!Jip!L)!tNKSczWm6IbGd%x<9RN{^I~< zjJtGOqThBhr0e5Awfq;07~Dxhwq1;MELox}3f(eiC`7Yu%TbIc3|VB#FHR?WwX4Ia zY$9&@g$;Y8uAmP#KpUuEu8RI1T)u%$^2?YZ(Af!%ZDmn`lDYe>CJyG*iJd5*%u1CO zoGV33E4B;U0%qYh+Ie9$Bu^EW)o3aNZuJU+WZV=V&LeP}O&cz*|1doW+xWm^fKkNubblT{JA`CTUv z*W?`%uig!xt7|vvVQSG{SPdSxi`zy1OgKv zte}AO0~laT#18W;{=T72avEi%XIOAHGR2!M6>VWC6K&yT1^{f%pL!fwn-Va4hs0JMbrDwE{m+KCO@ID#iI0D4+9@- zm?HgUgJIV~d67H6)M)CQ8exz!Z=d z@lhmZCqo2ldT)1?B&Ple<0q^|sCl)^NaxRWH!9n?FwrhNxwEqx+Plc13C_+AK8nMl zcr?#m7|w1T+J8)zOUkWC?LDI&%^sRJ_J~E0RZI>RUaI}cLET%Uuq8aV9Um5xtQ8ce z_&QzHw!t!2>G}uN;|7p%#OAqksDVl-41fr#8)~KT5!vl)^A({@rG6+ zg;*ts)+`h#H%1f!hR!ahA^BiG|Hn&wCSCdgcKO^F{`G*$R_DxwW~gj8yjy31E*M=4 z%Ay5Wkpf#*{`j`Z=exc82}R#`to7HU1C*zcw472;do4ig`c-sahV6S10@$(ci>+e< z-QHSw4yRoy_u45T(37o~sstoTdaVu24fcOLfI7vg*Rv^mb*B$L| zUAst}pXs=Yeel}Igm1 zYmG1lI1MnZ2u()DbWZ_`f#2<>2Mg}k933=h+1_M&HM5##aj$Gho<#S=nt}>;!3G&B zK>BHe4ZD?)WTL~@eKAn^!QH|dih*xpO(nR>0X`1-85D{WeXgu5Nj*q2FARNC=k^_2 zk#7>LH5D7l9ll`9oaFx!!Vpjsk@BOq!F0PGzHDglZ4GyIkbPGUHa|TV+Di33Tgcz* z&4s}?VSxZXKX7~lR3_^Jl+mfaH1a(?yxuCl4+*RUNj^EpoAVfXyIPse@B@FzzO2d> zTvXSfa$58zj72k1blb|7ZObFt{(P{d#Nh3B=y?{aBfYrQ)eSRq=Go_lv)hJRC(8*+ z60n``u*FxBd9BDpRo*RliMYZkRA%ocI7M zsr~q04sf)3kn)?^VwIJM*d&w_jp4IRKNGX=%|n9HRBU~(hld#X75_+_WAbY$OYja) zhq9B+uvp~Bq`8$42{pWdp(_qWJdu#IbTV^fn!5l+_Mb)cLrqbh+#BaQ zq3h`7Y34tN)F6LZqJ5#SB6x{Hs~{ONjs)HE4Otn&x|JWEcRSs=r#tHf=f6|idWJ@@ ztN7#0h$v+=ne@nAR>r{Ta@nr6hDh^OMHjSFJ!2V$DpTjvf>rsvLGdSg<4pC9;2+sn zhOOZfUrYV8F6A~g-&N^{FW5(`lNtb_6DQ?W96?;3b1)^LJF8E2vdj= z<2%KOf+i*5j7zzR+fhLU`T8DoE{6Me8qRIlNh&EMD5yg+`s_CHe)NI`DnB)w|6*WG z6*GVrh7yUBOs+L4KLR62=lV$ko>YbKWYnf0jpvi2`F*Vo}#cY9|cy)2XH zS}33t;iL2~t_1aT=qs#OqD*1epF5e)Jk)*KfPBt;BVQaDZ$5DL_=yNO2OSjIufRbq zO8`|usy&)dr&m=e-Ckr#_20n!)&wNy+O~-Bcu1$Cg!PPV|(alm48=cv$x_O zEM!22SMw|VZsH#veIv5jTk#LxFzXLgHoI*kd-R{3Y?idoq=ddX@ekW(`6r5h*tUw= z?>hcr`~3ej@ekWp@%tM8aAEY%YDbNOV(V2NH9-y8_AeUa;Z<6_}wE7a4E}Ia_3v zB(bB{v1hTguVU3%quZ9*oIFC}3FUY%QOMGA5arFZA(2wn>9|#fE9OUK+^;Os>(LM_ zUA663e!o%E{>nP$TUL=PXp(KdmSLagZO=TKtoqs8N3y>d+CP3~o^}V#<{yDIx-Q3C zcbe>2_TNb{Ngg5+!6CG9~qBXj^`m}-0H)7l_$XPfKH zw6nZ5Qr~gKKu=kf3gwvtP-H%0{v?iS-TBGt5Qa0)H?mrt*fIM+@!$?MxGfi{tif$R z^$Ck5q>x+wSo*sq4^VB&RR+Xc?zw5S^i>?o|ClEHu`qTvz=*2&?=U zwPrW?e=*L!V}w->{HcRv3oHVqz*Ls z_>09e(w0)?v`=Edly7DplqGNQ%Er|=&Xm7-y<)w*x<yFOU4mc5SGZZH>OGHa}pvP21n~+d(KC8GW;%aAfpNhr*LU;n~sInqAjH4A}YA z;Qs$vV8JH)|KRAmYx~Z2pQ1EK7Ju)XCO}H1rMf4G5XP`Da`?kS`M-07To+R@+9fxr zy1HGG!mBdkVp3KCTB@|Z$cf%q>WageD|O`XM>NonZhjzWz~!}k2G(oyl_Neo^f3+T zz9m~jr{76Fa|`eSpKa=z1A3Dsq&v3;V~j6xstF?aM}^NzED)g%&O3y&Ra?m;_3q zVq`0w4=iKe;MI1SoI9}N6J8-KtG!VAjv7>HAo@Us@$ed5qq)Jx+A4wuj6&7K%I11H z%_p2IiCI{c6gy!^Ur)uiM9>m^UNkF}XyA%JY(G>0tYumnbe_FqB-dIST21Ttf6Xq}v6jS06LQIQe?&OsX$fJokvMtbUeE4;3po$@H-s;zN z`<2H-L7OKw*Ta%H?}FCnMw_+&{_?HO$j}P$OK1yVb;xOr$zqhoXzAgtt(qr)mPElwCx$)7JQ}8zwpVUiTYm zNH94l)=ps9L;(pJq|?gka+m)a9h=jgqS{fng|iNXeoz6?X~TxbR(2EgGOMzx0NC9E z@!J34{AP@Dfz*doTEaSOBw`z6OKTr|yd3c9wbehhoCuqUEi1xjU4`D+)8?vL&T!0j z2Xr;MR?(zwjhjRB;cKhlu2L!#ELLC;}ks}u-QXkDO@c_ALYu(Lj`C9MF-8^#cZobxYG&{1&PFRv_ zc3asi|Le$<4Yi7%FBr8gj{@5>Fav~T69Io6Sg7LGq{_`3J_&<+-E}e;6{!UsRh88B z<@dR6`nDle;K)8ddbD=>oGQ93K=mJ7rVO5VRkq7_W2gi1^SoCTq1(sE%z!#jYtFZd zrvx@Cx%WvBvRVu}S{H=v`mPdFb(P{Zah#19-ub+T9wD()X7=wQr)Jyw(;l-RHm zG^mlRs)Cx3H`HwLQki1JzP_s}3xThyp&Io=$OhZxdBh(|zk)k$<)L?%3P7Rp_+o0c zDJ`am(=e5)x+nvjqkw}-uyFN?`n?~f`z?>{?EHzlbddoAS1RtL-fj|_wYFQTxrGTCz1k+%p3uBp(_`0A%z-fh^KgRv!Hk)E6swo4+ zNZ&CbC{^tQ3Su9KJu0aCTvTkZRi^gtjC|yfmwd=;&ruSKh@%p%JPHcz0fSi0>~~sI zuZ<$_v(6?LW>YDNZ{5gChE}t!dk>qC_oEwm<)k&Pj~Cx{uf141ZMmJ1f`3^iqAsv3 zPW0)gpU$aT_%s=E)Cl{)vNd5Q;Ok1I{W8p{R90FSKA+@66#84`PvWp;iQd_BB>=%zC8M& zj|@F-y^mmdjl*izvzTJ%YCjzv`#4rUYrtjnX|NxZXolJ+S@{vqgQhqelA$Wu>RS3e z*uh*>&j417`O-35xlD-dl5}7p`6LYCD!xPY_NpDJQ>)k)U;`$bjW`Jmtnz4Hr-AtV z9P!L5<0X2f)mh|UJf@_!xVKt!TPvrOr?nmJE}iRM5%>T?eFj##cySZ8a&dEgZMAVY zJ+mkhcep7a(2i>YGvs4oTlQNjId{cG4#~j?AArDQ4vP=i#HigSfkfHte)BuXUmgp+-P53;1$x z4Zxy&)(Z#HrgCNN^nd}kb>Dp;22~3%+CwFkFX-^G*%;YN@;HZx0-@=4mzy{F*f?eM zwSsfdL$2k5vh+mbJp8RS3O0YMd5p&R2?(}s3`olhRC3mxPjFTXvcM-03HA6W3>E^& z%F?@r5nG^D;`sm~7A^^<-!kmKGkRVa)GgPIK^(gU1gy3!auAEQV7O9!b+?E&k zN@)|0AcLvPZ=~XDg(lDR?aoe-(Zy>#h8`}YaSp4h&M;p_!{l(s&f~jdl0pHVM;a$X z^#T(#14FGDLj2q1?XobBWE~oOxckM*;1r6IxHgj><^~AE?adoOB~ZBsV5~D;?89z^ z?KE9{ZO#23sfchFIXe&-#mAtl6(og-zEm2v2PR24e#`sn{O*#CA({Yd zbD9|UqMby=3?dy^h{iF7h@Mh^vynl;o;XwC~HsB$5@)|yL#;3UMK zH+>?mlObyqx{lgjAf&1xeYrD^^XpKs>p5-Q%IxbXVTY;osGqSW<9~Xc}t`D+1&@R8|S-@w@l$g zW6+lUhj#p;f-utAL%#Mhsay@ih(vd!@w9U;=A#I2shnT2xEM(;D-hLMYf?NLD~JAD zj9cu!=tz)wf*(PCa>szR+lR&mx48Fc=xASak``E+NSUm6t#&bX3CVU|N5)Dkk(XDKuo!UH~IAa!nON#-MYQzQS`o*Et!$Lqk8>7@y}n4zOB(* zQDv8qE&LdY->srVx;@ZtqubumV{^k=W5M);Tkv0f=K7O?4b>yvfe|4LI_r%_mRP65q-K(@OKw652~0tb^2ZlC zS7^_>P!Si*>KjH%frF(MH)QDLYOeoBbRm0vL4=yxKsRAnFi&q~WC z#UCvIa_Et%Fk~63uq>&%wBlN!5qV07Z|{x|RfH3lMi3M zY!~P;OoQE?)PSP|h}c()G8cAx^@3%>YQIG&2NIT}F%Nom527UcPZ;_-=nb55IN8FN z?D*5D*7!@N%JpTHakd_>e~wwDu^_cb1fgh!ENe@>iQ>ET_4>^I+bSdj!O%4!By=No zdery1wl1*@;!ThBqMDE|OC#mCI5ye~r9vRRn3EHvTp~03jJ}kO*lc{L%!$Ju6{%7O zF%(u+WyHcI4A=N5V!_He(&)KNE1ZqXGc=zUdApL=zB9mnt45>Nxf93FoQhseNU0uP zqqXK|mME9Cv<*1>y*l2~%zcfq=UXT32_wk9;ds9aq`daO24=TT_gt`eR52cvS*iKS z>!Uu<~R)hQddZHIJ~8ctNNzCKR!~d_Dnb_NGD&Mm?HB>uRez|Cm)vJ&3 zz=0yuZLIo>4=@H8X1-o$6pA;kT|= zY$-iiJ8dSA(rg0=#yR=*Yn|&vks$>{qd|&~ud6J0esBO1)$JQR95laW zNbE7on*oV03_=2tc@aCy9-x>eU~Uoqr!sy0#`e>nYp5k!k|70tDC(95fj zF(1H}Zv$L=+Y>`zHPb{29PeGBN)+*jwX3bWTGmJ^sb#aR_qqQrKjsV3Y`&NJ{-}!o zv--8h_=}Zk{dZ$sa4?Q3E*!U>woTjT+|ZmcRC~77jtRSJ7S_&}wnNi!L@;e6n2y_5 z->yH|*!PNR9I$q|X;axdbK`X^^e|yt(q2-7Me&7gkoZfj$~VHIAO{-N(t3L3KU)eL_dSNHw&Z>ds<7+p~lz_+@wSrdyY-)Qs7 zWj7*Utcr#xC!;5cpjm+1+YKO$e~lSa*Alrd<#3P)D%+Cz0xG}i5b{YXo`gQmZL(Ho zag7io^4g1LGA~rJom$x1MHL=ISevbVgH4HE#T_YBx9TlfwJBmhlMvIpIJ;f$vin&; zHAj;KfG{Ef-MH+N7#MukOKSQUlsHxp@@aV`D#W6gApcAZP~arC0l^{7nT|NG{k*l{ zjc0l=5NE&Tf@02Cfz%!4-95QUFu285VcW4rcEr3Ox%Y{EQQ%B((`=)&vx!z?;*otm zzNa{E`TJ2-|>;A*7S+t ztww#O#lzx0j6g|($@Dw~uyNEMU&QEQ+UHBz7+)V6exXsHFJ3b=@&1DczK9PQfm8H{ zai$nm*dVdrEi{lTu(cI$@hAlAy2nOgg;I7lhLBv^?;=*}(24WNLiprd=ygKsH9jY8YJIqaODGdg<^=>*b;L zsa~(0cg+)k9m5_$`>_oIDW4De;xsU+XFe>>m(-%I#D9x97w*zX1v|Kg7C|*nA71P} zek4x8yne-ce3r0*vM3)zR8%B^!>Zp$qLl1 z7YFrH_W1^;^MZl4(*Py~R*GTg^|zJi%)L^%VINkybJ+w+=v)~@Cs-Y&YJ(cQw9TW= z^mQICg2lBA8LW=y`jKeb%$1Ul9}EGnt35E3)4&-RuQo@XNdWec3!9 zV&zYkS9vV^ew!o*}j%j@5_3z3MWorx%AObD?_Q+l8WlvIf?d8&%0l39ih z3dycN9?I{c{->{q`qESG&5>@t_DFIeI;$)yf7D zJeOfZ)?8|pZ^ElFx5zFHDg$?Ykck{t$|ns|uF-K^j1m%=Sju6sduKCa1)N%H zUULzFW^kxIR^ei6BJ+uUwdMxh83xt>cY;@Yk0Grde$g_Za010yN)t9Ld3B(1GQ>A7 zJ+kMz{bb7MV6(CbZWh8GfLkUyb(4gIcsPY-a*S5OS-Fl9Mxhp{`InAv&b&vy6hj^; zyOk@4hL%V@VGXTPFFGMKsX*MYLLI_$Gf{+xV57&ugGh8xCgrIJo5GqDl5+==Bu57N zo@a8n^_6h9AjWZcP?|WGBDRV}LS%AdPTpjTXgQ~0)YrzL)|%*DzVv7BoVl09Cb@4H zumg!;5;4~vXKGa6YIv|$+jkd8I)7h_l1 z6oXaKoWbaOw`V<^gA^bu#PYePSqA-ZL@kEbp%!#0`az}BD`o{;7A=+2&quNagw;G+ z7_of`dtBDU`(nVO3q9=h+!6_&u-C@v!wfh^;Hc zbCDfd(fc6WS+lAZOxFxG+iX*2FtZE2XiT^=}M}Rrr%M@E(JhqOdP43ALsSPtOjG8xh)f;7TWY z7QQsJ5bRuuJZt^s4ew?6<>3jF=ob}VF!Qs<*e_f2?g1775UGhA#{ol)gm58qN-7jB zRRUyQ`%AoQl~ydt!*a2MIH=)pBH*4u#8P10Mt6T&_f0IgDF@;$w^&5POp`|L8y|Yx zEo18hK^4QYFy+9M%lfP>t9?E?#0)H}vYZR;d>W!v#3CfHK09P^efur3%yEO+%#DEe z_FKlTmLOeio#H5aZi#Q)p7TZBuj#VEKV#BzV=eRTiTol?)a5)hMrqTzvJZTWI^{>SjWhc+#A* z65j%~%j1H*rYqHc_=yQc!A}I^R*%OZWoZ~^xW?&K7z?YaQ98dba{Mv}P=`P4L}IfJ zFvUZ}c=^akz+tgTf^X6SE3XH^kBf|MlZ-sJ3KLDzH|8M}GxaN?m4t<7K=6&u&*BnE XtqGG|`D)@FGI!*k7+yGXOYQ#ynerH6 diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index b95fa78bf3ee..fcb84e6b54b0 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -109,11 +109,11 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let tokio_handle = tokio_runtime.handle(); let signals = tokio_runtime.block_on(async { Signals::capture() })?; let mut task_manager = TaskManager::new(tokio_handle.clone(), prometheus_registry)?; - let spawn_handle = task_manager.spawn_handle(); + let essential_spawn_handle = task_manager.spawn_essential_handle(); let gen_rpc_module = || { let signals = tokio_runtime.block_on(async { Signals::capture() })?; - let fut = Client::from_url(&node_rpc_url, &spawn_handle).fuse(); + let fut = Client::from_url(&node_rpc_url, &essential_spawn_handle).fuse(); pin_mut!(fut); match tokio_handle.block_on(signals.try_until_signal(fut)) { @@ -125,7 +125,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { // Prometheus metrics. if let Some(PrometheusConfig { port, registry }) = prometheus_config.clone() { - spawn_handle.spawn( + task_manager.spawn_handle().spawn( "prometheus-endpoint", None, prometheus_endpoint::init_prometheus(port, registry).map(drop), diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 64f7f2a61617..ba93d0af62ac 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -35,7 +35,6 @@ use pallet_revive::{ }, EthContractResult, }; -use sc_service::SpawnTaskHandle; use sp_runtime::traits::{BlakeTwo256, Hash}; use sp_weights::Weight; use std::{ @@ -145,9 +144,6 @@ pub enum ClientError { /// The transaction fee could not be found #[error("TransactionFeePaid event not found")] TxFeeNotFound, - /// The token decimals property was not found - #[error("tokenDecimals not found in properties")] - TokenDecimalsNotFound, /// The cache is empty. #[error("Cache is empty")] CacheEmpty, @@ -165,7 +161,7 @@ impl From for ErrorObjectOwned { /// The number of recent blocks maintained by the cache. /// For each block in the cache, we also store the EVM transaction receipts. -pub const CACHE_SIZE: usize = 10; +pub const CACHE_SIZE: usize = 256; impl BlockCache { fn latest_block(&self) -> Option<&Arc> { @@ -228,7 +224,7 @@ impl ClientInner { let rpc = LegacyRpcMethods::::new(RpcClient::new(rpc_client.clone())); let (native_to_evm_ratio, chain_id, max_block_weight) = - tokio::try_join!(native_to_evm_ratio(&rpc), chain_id(&api), max_block_weight(&api))?; + tokio::try_join!(native_to_evm_ratio(&api), chain_id(&api), max_block_weight(&api))?; Ok(Self { api, rpc_client, rpc, cache, chain_id, max_block_weight, native_to_evm_ratio }) } @@ -238,6 +234,11 @@ impl ClientInner { value.saturating_mul(self.native_to_evm_ratio) } + /// Convert an evm balance to a native balance. + fn evm_to_native_decimals(&self, value: U256) -> U256 { + value / self.native_to_evm_ratio + } + /// Get the receipt infos from the extrinsics in a block. async fn receipt_infos( &self, @@ -274,7 +275,7 @@ impl ClientInner { .checked_div(gas_price.as_u128()) .unwrap_or_default(); - let success = events.find_first::().is_ok(); + let success = events.has::()?; let transaction_index = ext.index(); let transaction_hash = BlakeTwo256::hash(&Vec::from(ext.bytes()).encode()); let block_hash = block.hash(); @@ -319,16 +320,10 @@ async fn max_block_weight(api: &OnlineClient) -> Result) -> Result { - let props = rpc.system_properties().await?; - let eth_decimals = U256::from(18u32); - let native_decimals: U256 = props - .get("tokenDecimals") - .and_then(|v| v.as_number()?.as_u64()) - .ok_or(ClientError::TokenDecimalsNotFound)? - .into(); - - Ok(U256::from(10u32).pow(eth_decimals - native_decimals)) +async fn native_to_evm_ratio(api: &OnlineClient) -> Result { + let query = subxt_client::constants().revive().native_to_eth_ratio(); + let ratio = api.constants().at(&query)?; + Ok(U256::from(ratio)) } /// Extract the block timestamp. @@ -344,7 +339,10 @@ async fn extract_block_timestamp(block: &SubstrateBlock) -> Option { impl Client { /// Create a new client instance. /// The client will subscribe to new blocks and maintain a cache of [`CACHE_SIZE`] blocks. - pub async fn from_url(url: &str, spawn_handle: &SpawnTaskHandle) -> Result { + pub async fn from_url( + url: &str, + spawn_handle: &sc_service::SpawnEssentialTaskHandle, + ) -> Result { log::info!(target: LOG_TARGET, "Connecting to node at: {url} ..."); let inner: Arc = Arc::new(ClientInner::from_url(url).await?); log::info!(target: LOG_TARGET, "Connected to node at: {url}"); @@ -619,9 +617,10 @@ impl Client { block: BlockNumberOrTagOrHash, ) -> Result, ClientError> { let runtime_api = self.runtime_api(&block).await?; - let value = tx - .value - .unwrap_or_default() + + let value = self + .inner + .evm_to_native_decimals(tx.value.unwrap_or_default()) .try_into() .map_err(|_| ClientError::ConversionFailed)?; diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 5d84e06e9e04..01fcb6ae3bd2 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -50,16 +50,14 @@ async fn ws_client_with_retry(url: &str) -> WsClient { async fn test_jsonrpsee_server() -> anyhow::Result<()> { // Start the node. let _ = thread::spawn(move || { - match start_node_inline(vec![ + if let Err(e) = start_node_inline(vec![ "--dev", "--rpc-port=45789", "--no-telemetry", "--no-prometheus", + "-lerror,evm=debug,sc_rpc_server=info,runtime::revive=debug", ]) { - Ok(_) => {}, - Err(e) => { - panic!("Node exited with error: {}", e); - }, + panic!("Node exited with error: {e:?}"); } }); @@ -69,17 +67,36 @@ async fn test_jsonrpsee_server() -> anyhow::Result<()> { "--rpc-port=45788", "--node-rpc-url=ws://localhost:45789", "--no-prometheus", + "-linfo,eth-rpc=debug", ]); - let _ = thread::spawn(move || match cli::run(args) { - Ok(_) => {}, - Err(e) => { - panic!("eth-rpc exited with error: {}", e); - }, + let _ = thread::spawn(move || { + if let Err(e) = cli::run(args) { + panic!("eth-rpc exited with error: {e:?}"); + } }); let client = ws_client_with_retry("ws://localhost:45788").await; let account = Account::default(); + // Balance transfer + let ethan = Account::from(subxt_signer::eth::dev::ethan()); + let ethan_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?; + assert_eq!(U256::zero(), ethan_balance); + + let value = 1_000_000_000_000_000_000_000u128.into(); + let hash = + send_transaction(&account, &client, value, Bytes::default(), Some(ethan.address())).await?; + + let receipt = wait_for_receipt(&client, hash).await?; + assert_eq!( + Some(ethan.address()), + receipt.to, + "Receipt should have the correct contract address." + ); + + let ethan_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?; + assert_eq!(value, ethan_balance, "ethan's balance should be the same as the value sent."); + // Deploy contract let data = b"hello world".to_vec(); let value = U256::from(5_000_000_000_000u128); @@ -96,11 +113,7 @@ async fn test_jsonrpsee_server() -> anyhow::Result<()> { ); let balance = client.get_balance(contract_address, BlockTag::Latest.into()).await?; - assert_eq!( - value * 1_000_000, - balance, - "Contract balance should be the same as the value sent." - ); + assert_eq!(value, balance, "Contract balance should be the same as the value sent."); // Call contract let hash = diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 3acd67b32aab..9b360c7de712 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -313,10 +313,14 @@ pub trait EthExtra { return Err(InvalidTransaction::Call); } + let value = (value / U256::from(::NativeToEthRatio::get())) + .try_into() + .map_err(|_| InvalidTransaction::Call)?; + let call = if let Some(dest) = to { crate::Call::call:: { dest, - value: value.try_into().map_err(|_| InvalidTransaction::Call)?, + value, gas_limit, storage_deposit_limit, data: input.0, @@ -336,7 +340,7 @@ pub trait EthExtra { }; crate::Call::instantiate_with_code:: { - value: value.try_into().map_err(|_| InvalidTransaction::Call)?, + value, gas_limit, storage_deposit_limit, code: code.to_vec(), @@ -370,7 +374,7 @@ pub trait EthExtra { Default::default(), ) .into(); - log::debug!(target: LOG_TARGET, "try_into_checked_extrinsic: encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); + log::trace!(target: LOG_TARGET, "try_into_checked_extrinsic: encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); if eth_fee < actual_fee { log::debug!(target: LOG_TARGET, "fees {eth_fee:?} too low for the extrinsic {actual_fee:?}"); @@ -381,10 +385,10 @@ pub trait EthExtra { let max = actual_fee.max(eth_fee_no_tip); let diff = Percent::from_rational(max - min, min); if diff > Percent::from_percent(10) { - log::debug!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?} should be no more than 10% got {diff:?}"); + log::trace!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?} should be no more than 10% got {diff:?}"); return Err(InvalidTransaction::Call.into()) } else { - log::debug!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?}: {diff:?}"); + log::trace!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?}: {diff:?}"); } let tip = eth_fee.saturating_sub(eth_fee_no_tip); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 8629a21c4fda..943c377e504d 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1268,8 +1268,10 @@ where fn transfer(from: &T::AccountId, to: &T::AccountId, value: BalanceOf) -> ExecResult { // this avoids events to be emitted for zero balance transfers if !value.is_zero() { - T::Currency::transfer(from, to, value, Preservation::Preserve) - .map_err(|_| Error::::TransferFailed)?; + T::Currency::transfer(from, to, value, Preservation::Preserve).map_err(|err| { + log::debug!(target: LOG_TARGET, "Transfer of {value:?} from {from:?} to {to:?} failed: {err:?}"); + Error::::TransferFailed + })?; } Ok(Default::default()) } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 51e9a8fa3f90..5038ae44afad 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -303,6 +303,10 @@ pub mod pallet { /// preventing replay attacks. #[pallet::constant] type ChainId: Get; + + /// The ratio between the decimal representation of the native token and the ETH token. + #[pallet::constant] + type NativeToEthRatio: Get; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -374,7 +378,8 @@ pub mod pallet { type Xcm = (); type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; - type ChainId = ConstU64<{ 0 }>; + type ChainId = ConstU64<0>; + type NativeToEthRatio = ConstU32<1_000_000>; } } @@ -1239,6 +1244,7 @@ where T::Nonce: Into, T::Hash: frame_support::traits::IsType, { + log::debug!(target: LOG_TARGET, "bare_eth_transact: dest: {dest:?} value: {value:?} gas_limit: {gas_limit:?} storage_deposit_limit: {storage_deposit_limit:?}"); // Get the nonce to encode in the tx. let nonce: T::Nonce = >::account_nonce(&origin); @@ -1264,7 +1270,7 @@ where // Get the encoded size of the transaction. let tx = TransactionLegacyUnsigned { - value: value.into(), + value: value.into().saturating_mul(T::NativeToEthRatio::get().into()), input: input.into(), nonce: nonce.into(), chain_id: Some(T::ChainId::get().into()), @@ -1300,7 +1306,7 @@ where ) .into(); - log::debug!(target: LOG_TARGET, "bare_eth_call: len: {encoded_len:?} fee: {fee:?}"); + log::trace!(target: LOG_TARGET, "bare_eth_call: len: {encoded_len:?} fee: {fee:?}"); EthContractResult { gas_required: result.gas_required, storage_deposit: result.storage_deposit.charge_or_zero(), @@ -1339,7 +1345,7 @@ where let tx = TransactionLegacyUnsigned { gas: max_gas_fee.into(), nonce: nonce.into(), - value: value.into(), + value: value.into().saturating_mul(T::NativeToEthRatio::get().into()), input: input.clone().into(), gas_price: GAS_PRICE.into(), chain_id: Some(T::ChainId::get().into()), @@ -1373,7 +1379,7 @@ where ) .into(); - log::debug!(target: LOG_TARGET, "Call dry run Result: dispatch_info: {dispatch_info:?} len: {encoded_len:?} fee: {fee:?}"); + log::trace!(target: LOG_TARGET, "bare_eth_call: len: {encoded_len:?} fee: {fee:?}"); EthContractResult { gas_required: result.gas_required, storage_deposit: result.storage_deposit.charge_or_zero(), From 657b5503a04e97737696fa7344641019350fb521 Mon Sep 17 00:00:00 2001 From: Giuseppe Re Date: Mon, 4 Nov 2024 10:48:47 +0100 Subject: [PATCH 6/6] Refactor pallet `claims` (#6318) - [x] Removing `without_storage_info` and adding bounds on the stored types for pallet `claims` - issue /~https://github.com/paritytech/polkadot-sdk/issues/6289 - [x] Migrating to benchmarking V2 - /~https://github.com/paritytech/polkadot-sdk/issues/6202 --------- Co-authored-by: Guillaume Thiolliere --- polkadot/runtime/common/src/claims.rs | 247 ++++++++++++++++---------- prdoc/pr_6318.prdoc | 14 ++ 2 files changed, 165 insertions(+), 96 deletions(-) create mode 100644 prdoc/pr_6318.prdoc diff --git a/polkadot/runtime/common/src/claims.rs b/polkadot/runtime/common/src/claims.rs index 2b36c19efce7..b77cbfeff77c 100644 --- a/polkadot/runtime/common/src/claims.rs +++ b/polkadot/runtime/common/src/claims.rs @@ -19,7 +19,7 @@ #[cfg(not(feature = "std"))] use alloc::{format, string::String}; use alloc::{vec, vec::Vec}; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use core::fmt::Debug; use frame_support::{ ensure, @@ -82,7 +82,17 @@ impl WeightInfo for TestWeightInfo { /// The kind of statement an account needs to make for a claim to be valid. #[derive( - Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo, Serialize, Deserialize, + Encode, + Decode, + Clone, + Copy, + Eq, + PartialEq, + RuntimeDebug, + TypeInfo, + Serialize, + Deserialize, + MaxEncodedLen, )] pub enum StatementKind { /// Statement required to be made by non-SAFT holders. @@ -116,7 +126,9 @@ impl Default for StatementKind { /// An Ethereum address (i.e. 20 bytes, used to represent an Ethereum account). /// /// This gets serialized to the 0x-prefixed hex representation. -#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] +#[derive( + Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] pub struct EthereumAddress([u8; 20]); impl Serialize for EthereumAddress { @@ -150,7 +162,7 @@ impl<'de> Deserialize<'de> for EthereumAddress { } } -#[derive(Encode, Decode, Clone, TypeInfo)] +#[derive(Encode, Decode, Clone, TypeInfo, MaxEncodedLen)] pub struct EcdsaSignature(pub [u8; 65]); impl PartialEq for EcdsaSignature { @@ -172,7 +184,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::without_storage_info] pub struct Pallet(_); /// Configuration trait. @@ -1440,7 +1451,7 @@ mod tests { mod benchmarking { use super::*; use crate::claims::Call; - use frame_benchmarking::{account, benchmarks}; + use frame_benchmarking::v2::*; use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo}, traits::UnfilteredDispatchable, @@ -1485,136 +1496,168 @@ mod benchmarking { Ok(()) } - benchmarks! { - where_clause { where ::RuntimeCall: IsSubType> + From>, + #[benchmarks( + where + ::RuntimeCall: IsSubType> + From>, ::RuntimeCall: Dispatchable + GetDispatchInfo, <::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner + AsTransactionAuthorizedOrigin + Clone, <::RuntimeCall as Dispatchable>::PostInfo: Default, - } + )] + mod benchmarks { + use super::*; // Benchmark `claim` including `validate_unsigned` logic. - claim { + #[benchmark] + fn claim() -> Result<(), BenchmarkError> { let c = MAX_CLAIMS; - - for i in 0 .. c / 2 { + for _ in 0..c / 2 { create_claim::(c)?; create_claim_attest::(u32::MAX - c)?; } - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&c.encode())).unwrap(); let eth_address = eth(&secret_key); let account: T::AccountId = account("user", c, SEED); let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); let signature = sig::(&secret_key, &account.encode(), &[][..]); - super::Pallet::::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting, None)?; + super::Pallet::::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + None, + )?; assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); let source = sp_runtime::transaction_validity::TransactionSource::External; - let call_enc = Call::::claim { - dest: account.clone(), - ethereum_signature: signature.clone() - }.encode(); - }: { - let call = as Decode>::decode(&mut &*call_enc) - .expect("call is encoded above, encoding must be correct"); - super::Pallet::::validate_unsigned(source, &call).map_err(|e| -> &'static str { e.into() })?; - call.dispatch_bypass_filter(RawOrigin::None.into())?; - } - verify { + let call_enc = + Call::::claim { dest: account.clone(), ethereum_signature: signature.clone() } + .encode(); + + #[block] + { + let call = as Decode>::decode(&mut &*call_enc) + .expect("call is encoded above, encoding must be correct"); + super::Pallet::::validate_unsigned(source, &call) + .map_err(|e| -> &'static str { e.into() })?; + call.dispatch_bypass_filter(RawOrigin::None.into())?; + } + assert_eq!(Claims::::get(eth_address), None); + Ok(()) } // Benchmark `mint_claim` when there already exists `c` claims in storage. - mint_claim { + #[benchmark] + fn mint_claim() -> Result<(), BenchmarkError> { let c = MAX_CLAIMS; - - for i in 0 .. c / 2 { + for _ in 0..c / 2 { create_claim::(c)?; create_claim_attest::(u32::MAX - c)?; } - let eth_address = account("eth_address", 0, SEED); let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); let statement = StatementKind::Regular; - }: _(RawOrigin::Root, eth_address, VALUE.into(), vesting, Some(statement)) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, eth_address, VALUE.into(), vesting, Some(statement)); + assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); + Ok(()) } // Benchmark `claim_attest` including `validate_unsigned` logic. - claim_attest { + #[benchmark] + fn claim_attest() -> Result<(), BenchmarkError> { let c = MAX_CLAIMS; - - for i in 0 .. c / 2 { + for _ in 0..c / 2 { create_claim::(c)?; create_claim_attest::(u32::MAX - c)?; } - // Crate signature let attest_c = u32::MAX - c; - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); + let secret_key = + libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); let eth_address = eth(&secret_key); let account: T::AccountId = account("user", c, SEED); let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); let statement = StatementKind::Regular; let signature = sig::(&secret_key, &account.encode(), statement.to_text()); - super::Pallet::::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting, Some(statement))?; + super::Pallet::::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + Some(statement), + )?; assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); let call_enc = Call::::claim_attest { dest: account.clone(), ethereum_signature: signature.clone(), - statement: StatementKind::Regular.to_text().to_vec() - }.encode(); + statement: StatementKind::Regular.to_text().to_vec(), + } + .encode(); let source = sp_runtime::transaction_validity::TransactionSource::External; - }: { - let call = as Decode>::decode(&mut &*call_enc) - .expect("call is encoded above, encoding must be correct"); - super::Pallet::::validate_unsigned(source, &call).map_err(|e| -> &'static str { e.into() })?; - call.dispatch_bypass_filter(RawOrigin::None.into())?; - } - verify { + + #[block] + { + let call = as Decode>::decode(&mut &*call_enc) + .expect("call is encoded above, encoding must be correct"); + super::Pallet::::validate_unsigned(source, &call) + .map_err(|e| -> &'static str { e.into() })?; + call.dispatch_bypass_filter(RawOrigin::None.into())?; + } + assert_eq!(Claims::::get(eth_address), None); + Ok(()) } // Benchmark `attest` including prevalidate logic. - attest { + #[benchmark] + fn attest() -> Result<(), BenchmarkError> { let c = MAX_CLAIMS; - - for i in 0 .. c / 2 { + for _ in 0..c / 2 { create_claim::(c)?; create_claim_attest::(u32::MAX - c)?; } - let attest_c = u32::MAX - c; - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); + let secret_key = + libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); let eth_address = eth(&secret_key); let account: T::AccountId = account("user", c, SEED); let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); let statement = StatementKind::Regular; - let signature = sig::(&secret_key, &account.encode(), statement.to_text()); - super::Pallet::::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting, Some(statement))?; + super::Pallet::::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + Some(statement), + )?; Preclaims::::insert(&account, eth_address); assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); let stmt = StatementKind::Regular.to_text().to_vec(); - }: - _(RawOrigin::Signed(account), stmt) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(account), stmt); + assert_eq!(Claims::::get(eth_address), None); + Ok(()) } - move_claim { + #[benchmark] + fn move_claim() -> Result<(), BenchmarkError> { let c = MAX_CLAIMS; - - for i in 0 .. c / 2 { + for _ in 0..c / 2 { create_claim::(c)?; create_claim_attest::(u32::MAX - c)?; } - let attest_c = u32::MAX - c; - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); + let secret_key = + libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); let eth_address = eth(&secret_key); - let new_secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&(u32::MAX/2).encode())).unwrap(); + let new_secret_key = + libsecp256k1::SecretKey::parse(&keccak_256(&(u32::MAX / 2).encode())).unwrap(); let new_eth_address = eth(&new_secret_key); let account: T::AccountId = account("user", c, SEED); @@ -1622,73 +1665,85 @@ mod benchmarking { assert!(Claims::::contains_key(eth_address)); assert!(!Claims::::contains_key(new_eth_address)); - }: _(RawOrigin::Root, eth_address, new_eth_address, Some(account)) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, eth_address, new_eth_address, Some(account)); + assert!(!Claims::::contains_key(eth_address)); assert!(Claims::::contains_key(new_eth_address)); + Ok(()) } // Benchmark the time it takes to do `repeat` number of keccak256 hashes - #[extra] - keccak256 { - let i in 0 .. 10_000; + #[benchmark(extra)] + fn keccak256(i: Linear<0, 10_000>) { let bytes = (i).encode(); - }: { - for index in 0 .. i { - let _hash = keccak_256(&bytes); + + #[block] + { + for _ in 0..i { + let _hash = keccak_256(&bytes); + } } } // Benchmark the time it takes to do `repeat` number of `eth_recover` - #[extra] - eth_recover { - let i in 0 .. 1_000; + #[benchmark(extra)] + fn eth_recover(i: Linear<0, 1_000>) { // Crate signature let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&i.encode())).unwrap(); let account: T::AccountId = account("user", i, SEED); let signature = sig::(&secret_key, &account.encode(), &[][..]); let data = account.using_encoded(to_ascii_hex); let extra = StatementKind::default().to_text(); - }: { - for _ in 0 .. i { - assert!(super::Pallet::::eth_recover(&signature, &data, extra).is_some()); + + #[block] + { + for _ in 0..i { + assert!(super::Pallet::::eth_recover(&signature, &data, extra).is_some()); + } } } - prevalidate_attests { + #[benchmark] + fn prevalidate_attests() -> Result<(), BenchmarkError> { let c = MAX_CLAIMS; - - for i in 0 .. c / 2 { + for _ in 0..c / 2 { create_claim::(c)?; create_claim_attest::(u32::MAX - c)?; } - let ext = PrevalidateAttests::::new(); - let call = super::Call::attest { - statement: StatementKind::Regular.to_text().to_vec(), - }; + let call = super::Call::attest { statement: StatementKind::Regular.to_text().to_vec() }; let call: ::RuntimeCall = call.into(); let info = call.get_dispatch_info(); let attest_c = u32::MAX - c; - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); + let secret_key = + libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); let eth_address = eth(&secret_key); let account: T::AccountId = account("user", c, SEED); let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); let statement = StatementKind::Regular; - let signature = sig::(&secret_key, &account.encode(), statement.to_text()); - super::Pallet::::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting, Some(statement))?; + super::Pallet::::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + Some(statement), + )?; Preclaims::::insert(&account, eth_address); assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); - }: { - assert!(ext.test_run( - RawOrigin::Signed(account).into(), - &call, - &info, - 0, - |_| { - Ok(Default::default()) - } - ).unwrap().is_ok()); + + #[block] + { + assert!(ext + .test_run(RawOrigin::Signed(account).into(), &call, &info, 0, |_| { + Ok(Default::default()) + }) + .unwrap() + .is_ok()); + } + + Ok(()) } impl_benchmark_test_suite!( diff --git a/prdoc/pr_6318.prdoc b/prdoc/pr_6318.prdoc new file mode 100644 index 000000000000..b44a982f5992 --- /dev/null +++ b/prdoc/pr_6318.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: Refactor pallet claims + +doc: + - audience: Runtime Dev + description: | + Adds bounds on stored types for pallet claims. + Migrates benchmarking from v1 to v2 for pallet claims. + +crates: + - name: polkadot-runtime-common + bump: patch