diff --git a/Cargo.lock b/Cargo.lock index 7145f07c67..11cefb18b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,7 +311,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version 0.4.1", + "rustc_version", "zeroize", ] @@ -747,7 +747,6 @@ dependencies = [ "pallet-assets", "pallet-balances", "pallet-collective", - "pallet-contracts", "pallet-evm", "pallet-evm-precompile-assets-erc20", "pallet-evm-precompile-dispatch", @@ -807,7 +806,6 @@ dependencies = [ "pallet-collator-selection 4.0.0", "pallet-collective", "pallet-collective-proxy", - "pallet-contracts", "pallet-dapp-staking", "pallet-democracy", "pallet-dynamic-evm-base-fee", @@ -881,19 +879,6 @@ dependencies = [ "xcm-runtime-apis", ] -[[package]] -name = "astar-test-utils" -version = "0.1.0" -dependencies = [ - "frame-support", - "frame-system", - "pallet-contracts", - "pallet-contracts-uapi", - "parity-scale-codec", - "scale-info", - "sp-runtime", -] - [[package]] name = "astar-xcm-benchmarks" version = "0.1.0" @@ -1067,7 +1052,7 @@ checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" dependencies = [ "futures 0.3.32", "pharos", - "rustc_version 0.4.1", + "rustc_version", ] [[package]] @@ -2004,13 +1989,13 @@ dependencies = [ "frame-support", "frame-system", "log", - "pallet-balances", "pallet-migrations", "parity-scale-codec", "scale-info", "sp-io", "sp-runtime", "sp-std", + "sp-tracing", ] [[package]] @@ -3048,7 +3033,7 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "rustc_version 0.4.1", + "rustc_version", "subtle 2.6.1", "zeroize", ] @@ -3364,7 +3349,7 @@ dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version 0.4.1", + "rustc_version", "syn 2.0.117", ] @@ -3407,7 +3392,7 @@ dependencies = [ "convert_case 0.10.0", "proc-macro2", "quote", - "rustc_version 0.4.1", + "rustc_version", "syn 2.0.117", "unicode-xid", ] @@ -6241,12 +6226,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "indexmap-nostd" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" - [[package]] name = "inout" version = "0.1.4" @@ -6280,7 +6259,7 @@ version = "0.1.0" dependencies = [ "astar-primitives", "astar-runtime", - "astar-test-utils", + "contracts-mbm", "cumulus-pallet-parachain-system", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", @@ -6300,7 +6279,6 @@ dependencies = [ "pallet-collator-selection 4.0.0", "pallet-collective", "pallet-collective-proxy", - "pallet-contracts", "pallet-dapp-staking", "pallet-democracy", "pallet-ethereum", @@ -8332,17 +8310,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "num-format" version = "0.4.4" @@ -8947,58 +8914,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-contracts" -version = "45.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2512#ba6a0d23259cdc3108f70997a6847e7bc5b28794" -dependencies = [ - "environmental", - "frame-benchmarking", - "frame-support", - "frame-system", - "impl-trait-for-tuples", - "log", - "pallet-balances", - "pallet-contracts-proc-macro", - "pallet-contracts-uapi", - "parity-scale-codec", - "paste", - "rand 0.8.5", - "rand_pcg 0.3.1", - "scale-info", - "serde", - "smallvec", - "sp-api", - "sp-core", - "sp-io", - "sp-runtime", - "staging-xcm", - "staging-xcm-builder", - "wasm-instrument", - "wasmi 0.32.3", -] - -[[package]] -name = "pallet-contracts-proc-macro" -version = "23.0.3" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2512#ba6a0d23259cdc3108f70997a6847e7bc5b28794" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "pallet-contracts-uapi" -version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2512#ba6a0d23259cdc3108f70997a6847e7bc5b28794" -dependencies = [ - "bitflags 1.3.2", - "parity-scale-codec", - "paste", - "scale-info", -] - [[package]] name = "pallet-conviction-voting" version = "45.0.3" @@ -9542,17 +9457,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-insecure-randomness-collective-flip" -version = "33.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2512#ba6a0d23259cdc3108f70997a6847e7bc5b28794" -dependencies = [ - "parity-scale-codec", - "polkadot-sdk-frame", - "safe-mix", - "scale-info", -] - [[package]] name = "pallet-membership" version = "45.0.0" @@ -10633,7 +10537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ "futures 0.3.32", - "rustc_version 0.4.1", + "rustc_version", ] [[package]] @@ -13111,15 +13015,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - [[package]] name = "rustc_version" version = "0.4.1" @@ -13278,15 +13173,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "safe-mix" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d3d055a2582e6b00ed7a31c1524040aa391092bf636328350813f3a0605215c" -dependencies = [ - "rustc_version 0.2.3", -] - [[package]] name = "safe_arch" version = "0.7.4" @@ -14893,15 +14779,6 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.27" @@ -15136,7 +15013,6 @@ dependencies = [ "pallet-collator-selection 4.0.0", "pallet-collective", "pallet-collective-proxy", - "pallet-contracts", "pallet-dapp-staking", "pallet-democracy", "pallet-dynamic-evm-base-fee", @@ -15158,7 +15034,6 @@ dependencies = [ "pallet-evm-precompile-xcm", "pallet-identity", "pallet-inflation", - "pallet-insecure-randomness-collective-flip", "pallet-membership", "pallet-message-queue", "pallet-migrations", @@ -15251,7 +15126,6 @@ dependencies = [ "pallet-authorship", "pallet-balances", "pallet-collator-selection 4.0.0", - "pallet-contracts", "pallet-dapp-staking", "pallet-dynamic-evm-base-fee", "pallet-ethereum", @@ -15271,7 +15145,6 @@ dependencies = [ "pallet-evm-precompile-xcm", "pallet-identity", "pallet-inflation", - "pallet-insecure-randomness-collective-flip", "pallet-membership", "pallet-message-queue", "pallet-migrations", @@ -15537,7 +15410,7 @@ dependencies = [ "smallvec", "soketto", "twox-hash 2.1.2", - "wasmi 0.40.0", + "wasmi", "x25519-dalek", "zeroize", ] @@ -15596,7 +15469,7 @@ dependencies = [ "curve25519-dalek", "rand_core 0.6.4", "ring 0.17.14", - "rustc_version 0.4.1", + "rustc_version", "sha2 0.10.9", "subtle 2.6.1", ] @@ -16661,17 +16534,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "string-interner" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c6a0d765f5807e98a091107bae0a56ea3799f66a5de47b2c84c94a39c09974e" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "serde", -] - [[package]] name = "strsim" version = "0.11.1" @@ -18189,23 +18051,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasmi" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50386c99b9c32bd2ed71a55b6dd4040af2580530fae8bdb9a6576571a80d0cca" -dependencies = [ - "arrayvec 0.7.6", - "multi-stash", - "num-derive", - "num-traits", - "smallvec", - "spin 0.9.8", - "wasmi_collections 0.32.3", - "wasmi_core 0.32.3", - "wasmparser-nostd", -] - [[package]] name = "wasmi" version = "0.40.0" @@ -18216,41 +18061,18 @@ dependencies = [ "multi-stash", "smallvec", "spin 0.9.8", - "wasmi_collections 0.40.0", - "wasmi_core 0.40.0", + "wasmi_collections", + "wasmi_core", "wasmi_ir", "wasmparser 0.221.3", ] -[[package]] -name = "wasmi_collections" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c128c039340ffd50d4195c3f8ce31aac357f06804cfc494c8b9508d4b30dca4" -dependencies = [ - "ahash 0.8.12", - "hashbrown 0.14.5", - "string-interner", -] - [[package]] name = "wasmi_collections" version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e80d6b275b1c922021939d561574bf376613493ae2b61c6963b15db0e8813562" -[[package]] -name = "wasmi_core" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23b3a7f6c8c3ceeec6b83531ee61f0013c56e51cbf2b14b0f213548b23a4b41" -dependencies = [ - "downcast-rs", - "libm", - "num-traits", - "paste", -] - [[package]] name = "wasmi_core" version = "0.40.0" @@ -18267,7 +18089,7 @@ version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e431a14c186db59212a88516788bd68ed51f87aa1e08d1df742522867b5289a" dependencies = [ - "wasmi_core 0.40.0", + "wasmi_core", ] [[package]] @@ -18292,15 +18114,6 @@ dependencies = [ "serde", ] -[[package]] -name = "wasmparser-nostd" -version = "0.100.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa" -dependencies = [ - "indexmap-nostd", -] - [[package]] name = "wasmprinter" version = "0.235.0" @@ -19218,7 +19031,7 @@ dependencies = [ "js-sys", "log", "pharos", - "rustc_version 0.4.1", + "rustc_version", "send_wrapper 0.6.0", "thiserror 2.0.17", "wasm-bindgen", @@ -19333,16 +19146,13 @@ version = "0.1.0" dependencies = [ "assert_matches", "astar-primitives", - "astar-test-utils", "cumulus-pallet-xcm", "frame-support", "frame-system", "log", "pallet-assets", "pallet-balances", - "pallet-contracts", "pallet-dapp-staking", - "pallet-insecure-randomness-collective-flip", "pallet-message-queue", "pallet-proxy", "pallet-timestamp", diff --git a/Cargo.toml b/Cargo.toml index e718cbac21..e84f10269a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ members = [ "runtime/shibuya", "tests/xcm-simulator", "tests/integration", - "tests/utils", "pallets/*", "precompiles/*", "primitives", @@ -186,12 +185,9 @@ frame-metadata-hash-extension = { git = "https://github.com/paritytech/polkadot- frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2512", default-features = false } pallet-aura = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2512", default-features = false } pallet-authorship = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2512", default-features = false } -pallet-contracts = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2512", default-features = false } -pallet-contracts-uapi = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2512", default-features = false } pallet-identity = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2512", default-features = false } pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2512", default-features = false } pallet-migrations = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2512", default-features = false } -pallet-insecure-randomness-collective-flip = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2512", default-features = false } pallet-session = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2512", default-features = false, features = ["historical"] } pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2512", default-features = false } pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2512", default-features = false } @@ -316,7 +312,6 @@ pallet-treasury = { path = "./vendor/treasury", default-features = false } dapp-staking-runtime-api = { path = "./pallets/dapp-staking/rpc/runtime-api", default-features = false } astar-primitives = { path = "./primitives", default-features = false } -astar-test-utils = { path = "./tests/utils", default-features = false } pallet-evm-precompile-assets-erc20 = { path = "./precompiles/assets-erc20", default-features = false } pallet-evm-precompile-sr25519 = { path = "./precompiles/sr25519", default-features = false } diff --git a/pallets/contracts-mbm/Cargo.toml b/pallets/contracts-mbm/Cargo.toml index c2bf52a516..1283dcb1f1 100644 --- a/pallets/contracts-mbm/Cargo.toml +++ b/pallets/contracts-mbm/Cargo.toml @@ -16,7 +16,6 @@ frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } -pallet-balances = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } sp-io = { workspace = true } @@ -27,6 +26,8 @@ sp-std = { workspace = true } # `std` is required explicitly so `cargo test -p contracts-mbm` works standalone, without relying # on workspace-wide feature unification. pallet-migrations = { workspace = true, features = ["std"] } +# Surfaces the migrations' own logs under `RUST_LOG` when running the tests. +sp-tracing = { workspace = true, features = ["std"] } [features] default = ["std"] @@ -40,13 +41,11 @@ std = [ "frame-support/std", "frame-system/std", "sp-runtime/std", - "pallet-balances/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] try-runtime = [ @@ -54,5 +53,4 @@ try-runtime = [ "pallet-migrations/try-runtime", "frame-system/try-runtime", "sp-runtime/try-runtime", - "pallet-balances/try-runtime", ] diff --git a/pallets/contracts-mbm/src/benchmarks.rs b/pallets/contracts-mbm/src/benchmarks.rs index 6859e7f828..dce184e74e 100644 --- a/pallets/contracts-mbm/src/benchmarks.rs +++ b/pallets/contracts-mbm/src/benchmarks.rs @@ -18,69 +18,100 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::{Config, Pallet, ReleaseContractsDeposits}; +use crate::{ + purge::{clear_child_trie_metered, CONTRACT_INFO_OF}, + Config, Pallet, PurgeContractsChildTries, RemovePalletStepped, +}; use frame_benchmarking::v2::*; use frame_support::{ migrations::SteppedMigration, - traits::{ - fungible::{InspectHold, Mutate, MutateHold}, - Get, - }, + storage::unhashed, + traits::Get, weights::WeightMeter, + {StorageHasher, Twox64Concat}, }; -use sp_runtime::traits::Zero; -use sp_std::marker::PhantomData; - -/// Supplies the benchmark's hold reason as the `Get` bound the migration expects. -pub struct BenchHoldReason(PhantomData); -impl Get<::RuntimeHoldReason> for BenchHoldReason { - fn get() -> ::RuntimeHoldReason { - T::BenchmarkHoldReason::get() - } -} +use parity_scale_codec::Encode; +use sp_io::hashing::twox_128; +use sp_std::vec; + +/// Upper bound of the value size component. +/// +/// `pallet_contracts::Config::MaxCodeLen` was 123 KiB on every Astar runtime, and `PristineCode` +/// blobs are the largest values these migrations ever touch. +const MAX_VALUE_SIZE: u32 = 128 * 1024; -/// Destination of the swept deposit. -pub struct BenchEscrow(PhantomData); -impl Get for BenchEscrow { - fn get() -> T::AccountId { - account("escrow", 0, 0) +/// Pallet the benchmarks operate on. Only the length of the name matters for the measurement. +pub struct BenchPallet; +impl Get<&'static str> for BenchPallet { + fn get() -> &'static str { + "Contracts" } } +type Weights = crate::weights::SubstrateWeight; + #[benchmarks] mod benches { use super::*; - /// Cost of settling one account. + /// Cost of seeking to, measuring and removing one top level key holding an `x` byte value. /// - /// Runs the real `step` against storage holding exactly one hold-bearing account, so the - /// `Holds` seek, the decode and the `transfer_on_hold` are all measured. The terminal - /// `next_key` probe that ends the loop is included too, which errs on the safe side. + /// Runs the real `step` against a prefix holding exactly one key, so the seek, the length + /// probe and the terminal `next_key` that ends the loop are all measured. #[benchmark] - fn release_deposit() { - let contract: T::AccountId = account("contract", 0, 0); - let reason = T::BenchmarkHoldReason::get(); - let amount: ::Balance = 1_000_000u32.into(); + fn remove_key(x: Linear<0, MAX_VALUE_SIZE>) { + let prefix = twox_128(BenchPallet::get().as_bytes()).to_vec(); + let key = [prefix.clone(), twox_128(b"PristineCode").to_vec()].concat(); + unhashed::put_raw(&key, &vec![0u8; x as usize]); - pallet_balances::Pallet::::set_balance(&contract, amount * 2u32.into()); - pallet_balances::Pallet::::hold(&reason, &contract, amount) - .expect("account was just funded"); + let mut meter = WeightMeter::new(); + + #[block] + { + RemovePalletStepped::>::step(None, &mut meter) + .expect("migration step succeeds"); + } + + assert!(unhashed::get_raw(&key).is_none()); + } + + /// Cost of seeking to, measuring and removing one child trie key holding an `x` byte value. + #[benchmark] + fn remove_child_key(x: Linear<0, MAX_VALUE_SIZE>) { + let trie_id = vec![1u8; crate::TRIE_ID_LEN]; + let key = twox_128(b"bench-key").to_vec(); + sp_io::default_child_storage::set(&trie_id, &key, &vec![0u8; x as usize]); let mut meter = WeightMeter::new(); #[block] { - ReleaseContractsDeposits::< - T, - BenchHoldReason, - BenchHoldReason, - BenchEscrow, - crate::weights::SubstrateWeight, - >::step(None, &mut meter) - .expect("migration step succeeds"); + clear_child_trie_metered::>(&trie_id, &mut meter, 1); + } + + assert!(sp_io::default_child_storage::next_key(&trie_id, &[]).is_none()); + } + + /// Cost of handing back the consumer reference `pallet-contracts` took on one contract + /// account, including recovering that account id from its `ContractInfoOf` key. + #[benchmark] + fn release_contract_consumer() { + let contract: T::AccountId = account("contract", 0, 0); + frame_system::Pallet::::inc_providers(&contract); + frame_system::Pallet::::inc_consumers(&contract).expect("account has a provider"); + + let key = [ + PurgeContractsChildTries::>::map_prefix(CONTRACT_INFO_OF), + Twox64Concat::hash(&contract.encode()), + ] + .concat(); + + #[block] + { + PurgeContractsChildTries::>::release_consumer(&key); } - assert!(pallet_balances::Pallet::::balance_on_hold(&reason, &contract).is_zero()); + assert_eq!(frame_system::Pallet::::consumers(&contract), 0); } impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Runtime); diff --git a/pallets/contracts-mbm/src/lib.rs b/pallets/contracts-mbm/src/lib.rs index ae28d6718f..0d656f9a1f 100644 --- a/pallets/contracts-mbm/src/lib.rs +++ b/pallets/contracts-mbm/src/lib.rs @@ -21,10 +21,16 @@ //! Multi block migrations decommissioning `pallet-contracts` (ink!/Wasm smart contracts) from the //! Astar, Shiden & Shibuya runtimes. Staged over two runtime upgrades: //! -//! 1. [`ReleaseContractsDeposits`] - settles every balance the pallet still holds. Must complete -//! before the pallet index is dropped, otherwise those holds become undecodable and the funds -//! are frozen for good. -//! 2. *(next runtime)* purging the contract child tries and the pallet prefix. +//! 1. *(shipped)* `ReleaseContractsDeposits` - settled every balance the pallet still held. It had +//! to complete before the pallet index was dropped, otherwise those holds would have become +//! undecodable and the funds frozen for good. Removed here, along with the pallet: without the +//! `RuntimeHoldReason::Contracts` variant there is nothing left for it to match on. +//! 2. [`PurgeContractsChildTries`] then [`RemovePalletStepped`] - purge the contract child tries +//! and the leftover pallet prefixes, in that order. +//! +//! Both are size aware: every value is measured before it is deleted and charged through +//! benchmarked, size dependent weights, so a run of large values (`Contracts::PristineCode` blobs, +//! up to 123 KiB each) cannot produce a PoV oversized block. #![cfg_attr(not(feature = "std"), no_std)] @@ -35,8 +41,11 @@ mod mock; #[cfg(test)] mod tests; -mod release; -pub use release::ReleaseContractsDeposits; +mod purge; +pub use purge::{ + MaxKeyLen, PurgeContractsChildTries, RemovePalletStepped, TrieId, CONTRACT_INFO_OF, + DELETION_QUEUE, MAX_KEYS_PER_STEP, TRIE_ID_LEN, +}; pub mod weights; pub use weights::WeightInfo; @@ -47,8 +56,6 @@ pub(crate) const LOG_TARGET: &str = "mbm::contracts"; #[frame_support::pallet] pub mod pallet { - use frame_support::traits::Get; - /// Carries no storage and no calls. It exists only so that the migrations in this crate can /// be benchmarked through the standard `benchmark pallet` tooling, and is therefore added to /// the runtimes under `runtime-benchmarks` only. @@ -57,8 +64,5 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + pallet_balances::Config { - /// Hold reason the `release_deposit` benchmark places on its sample account. - type BenchmarkHoldReason: Get<::RuntimeHoldReason>; - } + pub trait Config: frame_system::Config {} } diff --git a/pallets/contracts-mbm/src/mock.rs b/pallets/contracts-mbm/src/mock.rs index e9ee8cec78..4866e8b9f0 100644 --- a/pallets/contracts-mbm/src/mock.rs +++ b/pallets/contracts-mbm/src/mock.rs @@ -22,11 +22,9 @@ use frame_support::{ construct_runtime, derive_impl, migrations::MultiStepMigrator, parameter_types, - traits::{ConstU64, OnFinalize, OnInitialize, VariantCount}, + traits::{OnFinalize, OnInitialize}, weights::Weight, }; -use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -34,89 +32,54 @@ type Block = frame_system::mocking::MockBlock; construct_runtime!( pub struct Runtime { System: frame_system, - Balances: pallet_balances, MultiBlockMigrations: pallet_migrations, ContractsMBM: crate, } ); -impl crate::Config for Runtime { - type BenchmarkHoldReason = StorageDeposit; -} - -/// Stands in for the runtime's `RuntimeHoldReason`. Written by hand rather than composed by -/// `construct_runtime!` so the mock does not have to pull in `pallet-contracts` - the migration is -/// generic over the reason, and the runtimes' real wiring is covered in `tests/integration`. -#[derive( - Clone, - Copy, - PartialEq, - Eq, - PartialOrd, - Ord, - Debug, - Encode, - Decode, - DecodeWithMemTracking, - MaxEncodedLen, - TypeInfo, -)] -pub enum HoldReason { - /// Stands in for `pallet_contracts::HoldReason::CodeUploadDepositReserve`. - CodeUploadDepositReserve, - /// Stands in for `pallet_contracts::HoldReason::StorageDepositReserve`. - StorageDepositReserve, - /// A hold owned by some other pallet, which must be left alone. - Unrelated, -} - -impl VariantCount for HoldReason { - const VARIANT_COUNT: u32 = 3; -} +impl crate::Config for Runtime {} #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { - type AccountData = pallet_balances::AccountData; type Block = Block; type MultiBlockMigrator = MultiBlockMigrations; } -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] -impl pallet_balances::Config for Runtime { - type AccountStore = System; - type RuntimeHoldReason = HoldReason; - type ExistentialDeposit = ConstU64<1>; +#[cfg(not(feature = "runtime-benchmarks"))] +mod migrations { + use super::{ContractsPalletName, Runtime}; + + pub type Weights = crate::weights::SubstrateWeight; + pub type Purge = crate::PurgeContractsChildTries; + pub type Remove = crate::RemovePalletStepped; } +#[cfg(not(feature = "runtime-benchmarks"))] +pub use migrations::{Purge, Remove}; #[derive_impl(pallet_migrations::config_preludes::TestDefaultConfig)] impl pallet_migrations::Config for Runtime { + /// Mirrors the runtimes: the child tries must be purged before the prefix holding their + /// `trie_id`s is wiped. #[cfg(not(feature = "runtime-benchmarks"))] - type Migrations = ( - crate::ReleaseContractsDeposits< - Runtime, - CodeDeposit, - StorageDeposit, - EscrowAccount, - crate::weights::SubstrateWeight, - >, - ); + type Migrations = (migrations::Purge, migrations::Remove); #[cfg(feature = "runtime-benchmarks")] type Migrations = pallet_migrations::mock_helpers::MockedMigrations; type MigrationStatusHandler = (); type MaxServiceWeight = MaxServiceWeight; } -pub const ESCROW: u64 = 9_999; - parameter_types! { - pub const CodeDeposit: HoldReason = HoldReason::CodeUploadDepositReserve; - pub const StorageDeposit: HoldReason = HoldReason::StorageDepositReserve; - pub const EscrowAccount: u64 = ESCROW; - /// Mutable so a test can squeeze the budget and force the migration across several blocks. - pub static MaxServiceWeight: Weight = Weight::from_parts(2_000_000_000, 1_000_000); + /// Name of the pallet these migrations retire, matching the runtimes. + pub const ContractsPalletName: &'static str = "Contracts"; + /// Mutable so a test can squeeze the budget and force a migration across several blocks. + pub static MaxServiceWeight: Weight = Weight::from_parts(50_000_000_000, 1_000_000); } pub fn new_test_ext() -> sp_io::TestExternalities { + // Makes the migrations' own logs visible with + // `RUST_LOG=mbm::contracts=debug cargo test -p contracts-mbm -- --nocapture`. + sp_tracing::try_init_simple(); + let storage = frame_system::GenesisConfig::::default() .build_storage() .unwrap(); diff --git a/pallets/contracts-mbm/src/purge.rs b/pallets/contracts-mbm/src/purge.rs new file mode 100644 index 0000000000..ac0d8547f8 --- /dev/null +++ b/pallets/contracts-mbm/src/purge.rs @@ -0,0 +1,700 @@ +// This file is part of Astar. + +// Copyright (C) Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! Purges the storage of the removed `pallet-contracts`, spread over multiple blocks. + +use frame_support::{ + migrations::{SteppedMigration, SteppedMigrationError}, + storage::unhashed, + traits::{ConstU32, Get}, + weights::{Weight, WeightMeter}, + BoundedVec, ReversibleStorageHasher, Twox64Concat, +}; +use parity_scale_codec::Decode; +use sp_io::hashing::{blake2_256, twox_128}; +use sp_std::{marker::PhantomData, vec::Vec}; + +use crate::{WeightInfo, LOG_TARGET}; + +/// Upper bound on the length of a storage key handed around as a migration cursor. +/// +/// The longest key any of these migrations sees is `twox_128(pallet) ++ twox_128(item) ++ +/// hasher(key)`, i.e. 72 bytes for `ContractInfoOf`. Measured over every live key on both +/// mainnets; 512 leaves ~7x headroom. +pub type MaxKeyLen = ConstU32<512>; + +/// Hard cap on the number of storage keys touched in a single step. +/// +/// The weight meter is the real limit - every key costs at least a read and a write, so the meter +/// always binds first. This is only a guard against an unbounded loop should that ever stop being +/// true. +pub const MAX_KEYS_PER_STEP: u32 = 10_000; + +/// `pallet-contracts` storage holding the `trie_id`s of contracts pending lazy deletion. +pub const DELETION_QUEUE: &[u8] = b"DeletionQueue"; +/// `pallet-contracts` storage holding the info (starting with the `trie_id`) of live contracts. +pub const CONTRACT_INFO_OF: &[u8] = b"ContractInfoOf"; + +/// `pallet-contracts`' `TrieId`, i.e. `BoundedVec>`. +pub type TrieId = BoundedVec>; + +/// Exact length of a `pallet-contracts` `trie_id`. +/// +/// `ContractInfo::new` derives it from `blake2_256`, so every trie id is 32 bytes even though the +/// type allows up to 128. Verified against all 305 Astar and 923 Shiden `ContractInfoOf` entries. +/// Enforcing it turns a mis-parse (e.g. an upstream field reordering) into a loud, logged skip +/// rather than a silent wrong-offset deletion. +pub const TRIE_ID_LEN: usize = 32; + +/// Length of `twox_128(pallet) ++ twox_128(item)`. +const MAP_PREFIX_LEN: usize = 32; + +/// Wraps a storage key into a migration cursor. +fn to_cursor(key: Vec) -> Result, SteppedMigrationError> { + BoundedVec::try_from(key).map_err(|_| { + log::error!( + target: LOG_TARGET, + "Encountered a storage key longer than {} bytes, cannot resume 🚨", + >::get(), + ); + SteppedMigrationError::Failed + }) +} + +/// Next storage key strictly after `from`, as long as it still lives under `prefix`. +/// +/// Seeding `from` with the bare `prefix` yields the first key of that prefix, because `next_key` +/// is strictly-greater and every real key is strictly longer than the prefix it lives under. +fn next_key_under(from: &[u8], prefix: &[u8]) -> Option> { + sp_io::storage::next_key(from).filter(|key| key.starts_with(prefix)) +} + +/// Translates "ran out of weight" into the right [`SteppedMigrationError`]. +/// +/// `SteppedMigration::transactional_step` rolls back on `Err`, so `Err` is only correct when this +/// step has not written anything yet. Conversely, a step that returns `Ok` without having removed +/// anything would spin forever, and multi block migrations block all extrinsics while they run. +fn not_enough_weight( + removed_so_far: u32, + required: Weight, +) -> Result, SteppedMigrationError> { + if removed_so_far == 0 { + Err(SteppedMigrationError::InsufficientWeight { required }) + } else { + Ok(Some(())) + } +} + +/// Percentage of `b` that `a` represents, saturating and zero-safe. +fn pct(a: u64, b: u64) -> u64 { + if b == 0 { + 0 + } else { + a.saturating_mul(100) / b + } +} + +/// Renders a meter's whole per-block budget. Only formatted if the log level is enabled. +struct Budget<'a>(&'a WeightMeter); + +impl core::fmt::Display for Budget<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let limit = self.0.limit(); + write!( + f, + "{} ref_time / {} proof", + limit.ref_time(), + limit.proof_size() + ) + } +} + +/// Renders how much of the per-block budget a step has spent. +/// +/// The proof percentage is the number to watch on a dry run: it is what the placeholder weights in +/// `weights.rs` are guessing at, and what keeps a block from going PoV-oversized. +struct Spent<'a>(&'a WeightMeter); + +impl core::fmt::Display for Spent<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let (used, limit) = (self.0.consumed(), self.0.limit()); + write!( + f, + "spent {}/{} ref_time ({}%), {}/{} proof ({}%)", + used.ref_time(), + limit.ref_time(), + pct(used.ref_time(), limit.ref_time()), + used.proof_size(), + limit.proof_size(), + pct(used.proof_size(), limit.proof_size()), + ) + } +} + +/// Human readable name of a `pallet-contracts` storage item, for logging. +fn source_name(item: &[u8]) -> &str { + sp_std::str::from_utf8(item).unwrap_or("") +} + +/// Per-step tally, logged on every exit path so a dry run can be followed block by block. +#[derive(Default)] +struct StepStats { + /// Top level `DeletionQueue` / `ContractInfoOf` entries dropped. + entries: u32, + /// Child trie keys removed. + child_keys: u32, + /// Consumer references handed back to contract accounts. + consumers: u32, +} + +impl StepStats { + /// Every key this step actually removed - what the budget and the rollback rule count. + fn removed(&self) -> u32 { + self.entries.saturating_add(self.child_keys) + } +} + +impl core::fmt::Display for StepStats { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + f, + "{} entries, {} child keys, {} consumer refs returned", + self.entries, self.child_keys, self.consumers + ) + } +} + +/// Outcome of a bounded removal loop. +pub(crate) enum Progress { + /// Nothing is left to remove. + Finished, + /// The loop stopped early. `required` carries the weight of the key that could not be paid + /// for, and is `None` when the loop stopped because it hit its key budget instead. + Exhausted { required: Option }, +} + +/// Multi block variant of `frame_support::migrations::RemovePallet`. +/// +/// Removes every storage key under a pallet's prefix, spread over as many blocks as +/// `pallet-migrations` needs. `RemovePallet` itself is unusable here: it wipes the whole prefix +/// within a single block. +/// +/// The cursor carries the last removed key so that iteration always resumes where it left off. +/// Restarting from the bare prefix instead would be quadratic: `next_key` has to walk over every +/// deleted-but-not-yet-committed key sitting in the block's storage overlay. +pub struct RemovePalletStepped(PhantomData<(P, W)>); + +impl, W> RemovePalletStepped { + /// Hashed storage prefix of the pallet that is being removed. + fn hashed_prefix() -> [u8; 16] { + twox_128(P::get().as_bytes()) + } +} + +impl SteppedMigration for RemovePalletStepped +where + P: Get<&'static str>, + W: WeightInfo, +{ + /// Last removed key. `None` means "start from the beginning of the prefix". + type Cursor = BoundedVec; + /// Derived from the pallet name so that two instances never collide. + type Identifier = [u8; 32]; + + fn id() -> Self::Identifier { + blake2_256( + &[ + b"contracts-mbm::RemovePalletStepped::".as_slice(), + P::get().as_bytes(), + ] + .concat(), + ) + } + + fn step( + cursor: Option, + meter: &mut WeightMeter, + ) -> Result, SteppedMigrationError> { + let prefix = Self::hashed_prefix(); + // The only bit the cursor carries: `None` means this is the first step of the migration. + if cursor.is_none() { + log::info!( + target: LOG_TARGET, + "remove<{}>: starting, budget {}", + P::get(), + Budget(meter), + ); + } + let mut from = cursor + .map(BoundedVec::into_inner) + .unwrap_or_else(|| prefix.to_vec()); + let mut removed = 0u32; + let mut bytes = 0u64; + + loop { + let Some(key) = next_key_under(&from, &prefix) else { + log::info!( + target: LOG_TARGET, + "remove<{}>: DONE ✅ prefix empty | this step: {removed} keys, {bytes} value \ + bytes | {}", + P::get(), + Spent(meter), + ); + return Ok(None); + }; + + if removed >= MAX_KEYS_PER_STEP { + log::info!( + target: LOG_TARGET, + "remove<{}>: paused on the {MAX_KEYS_PER_STEP} key/step cap | this step: \ + {removed} keys, {bytes} value bytes | {}", + P::get(), + Spent(meter), + ); + return to_cursor(from).map(Some); + } + + // Measure the value without copying it into the runtime; it enters the PoV either way. + let value_len = sp_io::storage::read(&key, &mut [], 0).unwrap_or_default(); + let cost = W::remove_key(value_len); + + if meter.try_consume(cost).is_err() { + return if removed == 0 { + log::warn!( + target: LOG_TARGET, + "remove<{}>: cannot afford a single key - needs {} ref_time / {} proof but \ + the whole block budget is {} 🚨", + P::get(), + cost.ref_time(), + cost.proof_size(), + Budget(meter), + ); + Err(SteppedMigrationError::InsufficientWeight { required: cost }) + } else { + log::info!( + target: LOG_TARGET, + "remove<{}>: out of weight, resuming next block | this step: {removed} \ + keys, {bytes} value bytes | {}", + P::get(), + Spent(meter), + ); + to_cursor(from).map(Some) + }; + } + + sp_io::storage::clear(&key); + removed = removed.saturating_add(1); + bytes = bytes.saturating_add(value_len as u64); + from = key; + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + let prefix = Self::hashed_prefix(); + let (mut keys, mut bytes) = (0u32, 0u64); + let mut from = prefix.to_vec(); + while let Some(key) = next_key_under(&from, &prefix) { + keys = keys.saturating_add(1); + bytes = bytes + .saturating_add(sp_io::storage::read(&key, &mut [], 0).unwrap_or_default() as u64); + from = key; + } + + log::info!( + target: LOG_TARGET, + "remove<{}>: pre_upgrade sees {keys} keys holding {bytes} value bytes 👀", + P::get(), + ); + Ok(Vec::new()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + use frame_support::storage::unhashed::contains_prefixed_key; + + if contains_prefixed_key(&Self::hashed_prefix()) { + return Err("Keys remaining post-removal, this should never happen 🚨".into()); + } + + log::info!( + target: LOG_TARGET, + "remove<{}>: post_upgrade OK, prefix is empty ✅", + P::get(), + ); + Ok(()) + } +} + +/// Purges the child tries owned by the decommissioned `pallet-contracts`, and hands back the +/// consumer references it took on contract accounts. +/// +/// `pallet-contracts` keeps the storage of every instantiated contract in a *child trie* addressed +/// by that contract's `trie_id`, and holds only a pointer to it in its own (top level) storage. +/// Wiping the pallet prefix therefore does **not** free the bulk of the data, it merely orphans it: +/// the `trie_id`s needed to address those child tries would be gone for good. +/// +/// This migration walks the two places where `trie_id`s are recorded and empties the corresponding +/// child tries before dropping the top level entry pointing at them: +/// * `Contracts::DeletionQueue` - tries of already terminated contracts, awaiting lazy deletion. +/// * `Contracts::ContractInfoOf` - tries of contracts that are still alive. +/// +/// It MUST be scheduled *before* [`RemovePalletStepped`] for the same pallet, otherwise the +/// `trie_id`s are deleted first and the child tries become unreachable forever. +/// +/// # Consumer references +/// +/// `pallet-contracts` calls `inc_consumers` on the contract account at instantiation and only +/// gives that reference back from `seal_terminate`. `ContractInfoOf` is the last on-chain record +/// of which accounts are contracts, so purging it is the final opportunity to hand the reference +/// back - leave it and every live contract account keeps a dangling consumer forever, and can +/// never be reaped even once its balance is gone. `DeletionQueue` entries are terminated +/// contracts, which already gave theirs back, hence the distinction below. +/// +/// # Storage layout coupling +/// +/// Values are decoded structurally rather than via `pallet-contracts` types, since the pallet is +/// no longer a dependency of the runtimes. Verified against `polkadot-sdk` `stable2512`: +/// * `DeletionQueue: StorageMap<_, Twox64Concat, u32, TrieId>` - the value *is* a `TrieId`. +/// * `ContractInfoOf: StorageMap<_, Twox64Concat, AccountId, ContractInfo>`, and +/// `ContractInfo`'s **first** field is `pub trie_id: TrieId`, so it decodes off the front. +/// +/// A `TrieId` is a length prefixed byte blob; decoding it as a [`TrieId`] (rather than a plain +/// `Vec`) makes a garbage value fail loudly instead of producing a bogus multi kilobyte id. +pub struct PurgeContractsChildTries(PhantomData<(T, P, W)>); + +impl, W> PurgeContractsChildTries { + /// Hashed prefix of a `pallet-contracts` storage map. + pub(crate) fn map_prefix(item: &[u8]) -> Vec { + [twox_128(P::get().as_bytes()), twox_128(item)].concat() + } + + /// The storage maps holding `trie_id`s, paired with whether their key is a live contract + /// account still owing a consumer reference. Terminated contracts come first, they are dead + /// weight. + fn trie_id_sources() -> [(&'static [u8], bool); 2] { + [(DELETION_QUEUE, false), (CONTRACT_INFO_OF, true)] + } + + /// Gives back the consumer reference `pallet-contracts` took on the contract account keyed by + /// `ContractInfoOf` key `key`. + pub(crate) fn release_consumer(key: &[u8]) -> bool { + let account = key + .get(MAP_PREFIX_LEN..) + .map(Twox64Concat::reverse) + .and_then(|mut raw| T::AccountId::decode(&mut raw).ok()); + + let Some(account) = account else { + log::warn!( + target: LOG_TARGET, + "purge: no account id in a ContractInfoOf key, consumer ref kept 🚨", + ); + return false; + }; + + // Guarded rather than unconditional: `dec_consumers` on an account that has none is a + // logged logic error, and it would write back a default `AccountInfo` for an account that + // no longer exists. + let before = frame_system::Pallet::::consumers(&account); + if before == 0 { + log::debug!( + target: LOG_TARGET, + "purge: {account:?} is already at 0 consumers, nothing to hand back", + ); + return false; + } + + frame_system::Pallet::::dec_consumers(&account); + log::debug!( + target: LOG_TARGET, + "purge: {account:?} consumers {before} -> {}", + before.saturating_sub(1), + ); + true + } +} + +impl SteppedMigration for PurgeContractsChildTries +where + T: frame_system::Config, + P: Get<&'static str>, + W: WeightInfo, +{ + /// Progress lives in storage itself: every step restarts from the first remaining entry, and + /// an entry is only dropped once its child trie has been fully emptied. Restarting is cheap + /// because the number of contracts is small - the child tries themselves, which are not, are + /// walked with a local cursor. + type Cursor = (); + /// Derived from the pallet name so that two instances never collide. + type Identifier = [u8; 32]; + + fn id() -> Self::Identifier { + blake2_256( + &[ + b"contracts-mbm::PurgeContractsChildTries::".as_slice(), + P::get().as_bytes(), + ] + .concat(), + ) + } + + fn step( + cursor: Option, + meter: &mut WeightMeter, + ) -> Result, SteppedMigrationError> { + // The only bit the cursor carries: `None` means this is the first step of the migration. + // Progress itself lives in storage - an entry is dropped only once its child trie is + // empty, so a restart resumes at the first entry that is still there. + if cursor.is_none() { + log::info!( + target: LOG_TARGET, + "purge: starting, budget {}", + Budget(meter), + ); + } + + // Counts every key this step actually removed, top level entries and child trie keys + // alike. Used to decide between `Err(InsufficientWeight)` (rolls back, so only valid when + // nothing was written) and `Ok(Some(()))` (commits and resumes). + let mut stats = StepStats::default(); + + for (item, live) in Self::trie_id_sources() { + let name = source_name(item); + let prefix = Self::map_prefix(item); + let mut from = prefix.clone(); + let entries_before = stats.entries; + + while let Some(key) = next_key_under(&from, &prefix) { + if stats.removed() >= MAX_KEYS_PER_STEP { + log::info!( + target: LOG_TARGET, + "purge: paused on the {MAX_KEYS_PER_STEP} key/step cap in {name} | this \ + step: {stats} | {}", + Spent(meter), + ); + return Ok(Some(())); + } + + // The whole value must be pulled into the PoV to get at the `trie_id` anyway. + let raw = unhashed::get_raw(&key); + let mut cost = + W::remove_key(raw.as_ref().map(|v| v.len() as u32).unwrap_or_default()); + if live { + cost = cost.saturating_add(W::release_contract_consumer()); + } + if meter.try_consume(cost).is_err() { + log::info!( + target: LOG_TARGET, + "purge: out of weight before a {name} entry | this step: {stats} | {}", + Spent(meter), + ); + return not_enough_weight(stats.removed(), cost); + } + + let maybe_trie_id = raw + .and_then(|raw| TrieId::decode(&mut &raw[..]).ok()) + .filter(|trie_id| trie_id.len() == TRIE_ID_LEN); + + if let Some(trie_id) = maybe_trie_id { + let budget = MAX_KEYS_PER_STEP.saturating_sub(stats.removed()); + let (progress, child_keys_removed) = + clear_child_trie_metered::(&trie_id, meter, budget); + stats.child_keys = stats.child_keys.saturating_add(child_keys_removed); + + if let Progress::Exhausted { required } = progress { + // Keep the top level entry so the next step picks the same trie up again. + log::info!( + target: LOG_TARGET, + "purge: child trie only partly emptied ({child_keys_removed} keys \ + this step), its {name} entry is kept and will be resumed | this \ + step: {stats} | {}", + Spent(meter), + ); + return match required { + Some(cost) => not_enough_weight(stats.removed(), cost), + None => Ok(Some(())), + }; + } + + log::debug!( + target: LOG_TARGET, + "purge: {name} entry cleared, {child_keys_removed} child trie keys removed", + ); + } else { + // Either the value is too short, or what sits at the front is not a 32 byte + // blob - in both cases there is nothing addressable behind it. Dropping the + // entry is safe; the loud warning is what matters, because a systematic + // occurrence would mean the assumed `ContractInfo` layout is wrong. + log::warn!( + target: LOG_TARGET, + "purge: no {TRIE_ID_LEN}-byte trie id at the front of a {name} entry, \ + dropping it without touching any child trie 🚨", + ); + } + + if live && Self::release_consumer(&key) { + stats.consumers = stats.consumers.saturating_add(1); + } + unhashed::kill(&key); + stats.entries = stats.entries.saturating_add(1); + from = key; + } + + // Only worth saying when this step is the one that finished the map off; the scan + // restarts from the top every step, so an already-empty map falls through here too. + if stats.entries > entries_before { + log::info!( + target: LOG_TARGET, + "purge: {name} is now empty, {} entries dropped this step", + stats.entries.saturating_sub(entries_before), + ); + } + } + + log::info!( + target: LOG_TARGET, + "purge: DONE ✅ every contract child trie purged | this step: {stats} | {}", + Spent(meter), + ); + Ok(None) + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + use parity_scale_codec::Encode; + + // Snapshot every trie id so `post_upgrade` can prove the child tries are really gone - + // once the top level entries are removed they would be unreachable and unverifiable. + let mut trie_ids: Vec> = Vec::new(); + let mut consumers_owed = 0u32; + + for (item, live) in Self::trie_id_sources() { + let name = source_name(item); + let prefix = Self::map_prefix(item); + let mut from = prefix.clone(); + let (mut entries, mut undecodable) = (0u32, 0u32); + + while let Some(key) = next_key_under(&from, &prefix) { + entries = entries.saturating_add(1); + match unhashed::get_raw(&key) + .and_then(|raw| TrieId::decode(&mut &raw[..]).ok()) + .filter(|trie_id| trie_id.len() == TRIE_ID_LEN) + { + Some(trie_id) => trie_ids.push(trie_id.into_inner()), + None => undecodable = undecodable.saturating_add(1), + } + + if live { + // How many accounts will actually get a reference back. Anything short of + // `entries` means some contract accounts are not carrying the ref this + // migration assumes - worth knowing before enactment, not after. + let has_ref = key + .get(MAP_PREFIX_LEN..) + .map(Twox64Concat::reverse) + .and_then(|mut raw| T::AccountId::decode(&mut raw).ok()) + .is_some_and(|who| frame_system::Pallet::::consumers(&who) > 0); + if has_ref { + consumers_owed = consumers_owed.saturating_add(1); + } + } + from = key; + } + + log::info!( + target: LOG_TARGET, + "purge: pre_upgrade sees {entries} {name} entries ({undecodable} without a usable \ + trie id) 👀", + ); + } + + log::info!( + target: LOG_TARGET, + "purge: pre_upgrade snapshotted {} child tries to empty, and {consumers_owed} contract \ + accounts are owed a consumer ref 👀", + trie_ids.len(), + ); + + Ok(trie_ids.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + use frame_support::storage::unhashed::contains_prefixed_key; + + for (item, _) in Self::trie_id_sources() { + if contains_prefixed_key(&Self::map_prefix(item)) { + return Err("Contract trie id entries remaining post-removal 🚨".into()); + } + } + + let trie_ids = Vec::>::decode(&mut &state[..]) + .map_err(|_| "Failed to decode the pre-upgrade trie id snapshot")?; + let checked = trie_ids.len(); + for trie_id in trie_ids { + if sp_io::default_child_storage::next_key(&trie_id, &[]).is_some() { + return Err("An orphaned contract child trie is still populated 🚨".into()); + } + } + + log::info!( + target: LOG_TARGET, + "purge: post_upgrade OK, {checked} child tries verified empty and no trie id entries \ + remain ✅", + ); + Ok(()) + } +} + +/// Removes keys of the default child trie `trie_id` until the meter or `max_keys` is exhausted. +/// +/// Returns how many keys were removed alongside the progress made. Iteration uses a local cursor, +/// so the cost stays linear even when a single trie spans several steps within one block. +pub(crate) fn clear_child_trie_metered( + trie_id: &[u8], + meter: &mut WeightMeter, + max_keys: u32, +) -> (Progress, u32) { + // The empty key sorts before every real key, so it yields the first entry of the trie. + let mut from: Vec = Vec::new(); + let mut removed = 0u32; + + while let Some(key) = sp_io::default_child_storage::next_key(trie_id, &from) { + if removed >= max_keys { + return (Progress::Exhausted { required: None }, removed); + } + + let value_len = + sp_io::default_child_storage::read(trie_id, &key, &mut [], 0).unwrap_or_default(); + let cost = W::remove_child_key(value_len); + + if meter.try_consume(cost).is_err() { + return ( + Progress::Exhausted { + required: Some(cost), + }, + removed, + ); + } + + sp_io::default_child_storage::clear(trie_id, &key); + removed = removed.saturating_add(1); + from = key; + } + + (Progress::Finished, removed) +} diff --git a/pallets/contracts-mbm/src/release.rs b/pallets/contracts-mbm/src/release.rs deleted file mode 100644 index 871bcb46a6..0000000000 --- a/pallets/contracts-mbm/src/release.rs +++ /dev/null @@ -1,194 +0,0 @@ -// This file is part of Astar. - -// Copyright (C) Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later - -// Astar is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Astar is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Astar. If not, see . - -//! Settles the balances `pallet-contracts` still owns, so that its pallet index can be retired. - -use frame_support::{ - migrations::{SteppedMigration, SteppedMigrationError}, - traits::{ - fungible::MutateHold, - tokens::{Fortitude, Precision, Restriction}, - Get, - }, - weights::WeightMeter, -}; -use sp_io::hashing::blake2_256; -use sp_runtime::traits::Zero; -use sp_std::marker::PhantomData; - -use crate::{WeightInfo, LOG_TARGET}; - -type HoldReasonOf = ::RuntimeHoldReason; -type BalanceOf = ::Balance; - -/// Moves every balance hold placed by `pallet-contracts` to an escrow account. -/// -/// # Why this has to happen *before* the pallet is removed -/// -/// Storage and code deposits are live `Balances::Holds` entries whose reason is -/// `RuntimeHoldReason::Contracts(..)`, i.e. tagged with the pallet's index. Dropping the pallet -/// from `construct_runtime` deletes that variant and every such entry becomes undecodable. -/// `Holds` is a `ValueQuery` map, so the decode failure silently yields an empty vector: the hold -/// record disappears while `AccountData::reserved` stays behind. Nothing can decrement that -/// `reserved` again, so the funds are frozen for good - and silently, because -/// `pallet_balances::do_try_state` only bounds the length of each hold vector, it never checks -/// that the holds sum to `reserved`. Hence the `post_upgrade` check below. -/// -/// # Why everything goes to escrow -/// -/// Storage deposits sit on keyless, derived contract accounts, so releasing them in place would -/// strand the funds; the depositor is not recorded on chain. Code upload deposits do sit on real -/// accounts, but sweeping both to a single escrow account keeps this migration to one code path -/// and leaves the refunding to a plain, auditable batch of transfers afterwards. -/// -/// # What this deliberately does not touch -/// -/// The *free* balance of contract accounts is contract owned value, not a deposit - for a vault -/// or DEX contract it is user funds whose rightful owner is encoded in the contract's own child -/// trie, not "whoever deployed it". Sweeping it is a policy decision, not a technical necessity: -/// unlike the holds it breaks no invariant if left in place, and `Balances::force_transfer` from -/// Root can still move it at any point in the future. It is deliberately left for the migration -/// that purges `ContractInfoOf`, which is the last on-chain record of which accounts are -/// contracts. -pub struct ReleaseContractsDeposits( - PhantomData<(T, CodeDeposit, StorageDeposit, Escrow, W)>, -); - -impl SteppedMigration - for ReleaseContractsDeposits -where - T: pallet_balances::Config, - HoldReasonOf: PartialEq, - CodeDeposit: Get>, - StorageDeposit: Get>, - Escrow: Get, - W: WeightInfo, -{ - /// Last account whose holds were settled. - type Cursor = T::AccountId; - type Identifier = [u8; 32]; - - fn id() -> Self::Identifier { - blake2_256(b"contracts-mbm::ReleaseContractsDeposits") - } - - fn step( - cursor: Option, - meter: &mut WeightMeter, - ) -> Result, SteppedMigrationError> { - let required = W::release_deposit(); - // Guarantees the loop below settles at least one account, so a step never returns the - // cursor it was handed and stalls the migration forever. - if meter.remaining().any_lt(required) { - return Err(SteppedMigrationError::InsufficientWeight { required }); - } - - let reasons = [CodeDeposit::get(), StorageDeposit::get()]; - let escrow = Escrow::get(); - // Removing the entry of the account currently being visited is safe: the iterator has - // already recorded its key to seek from. - let mut iter = match &cursor { - Some(last) => pallet_balances::Holds::::iter_from( - pallet_balances::Holds::::hashed_key_for(last), - ), - None => pallet_balances::Holds::::iter(), - }; - let mut last = cursor; - let mut settled = 0u32; - - loop { - if !meter.can_consume(required) { - return Ok(last); - } - - let Some((account, holds)) = iter.next() else { - log::info!( - target: LOG_TARGET, - "ReleaseContractsDeposits: finished, {settled} accounts settled in this step 💸", - ); - return Ok(None); - }; - - // Charged whether or not the account turns out to be relevant: the read happened - // either way, and this keeps the loop guaranteed to make progress. - meter.consume(required); - settled = settled.saturating_add(1); - last = Some(account.clone()); - - for hold in holds.iter() { - if hold.amount.is_zero() || !reasons.contains(&hold.id) { - continue; - } - Self::sweep_to_escrow(&account, &escrow, hold.amount, &hold.id); - } - } - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: sp_std::vec::Vec) -> Result<(), sp_runtime::TryRuntimeError> { - let reasons = [CodeDeposit::get(), StorageDeposit::get()]; - - for (_, holds) in pallet_balances::Holds::::iter() { - if holds.iter().any(|hold| reasons.contains(&hold.id)) { - return Err( - "A pallet-contracts hold survived; removing the pallet index would \ - freeze these funds permanently 🚨" - .into(), - ); - } - } - - Ok(()) - } -} - -impl - ReleaseContractsDeposits -where - T: pallet_balances::Config, -{ - fn sweep_to_escrow( - account: &T::AccountId, - escrow: &T::AccountId, - amount: BalanceOf, - reason: &HoldReasonOf, - ) { - match pallet_balances::Pallet::::transfer_on_hold( - reason, - account, - escrow, - amount, - Precision::BestEffort, - Restriction::Free, - // The source is typically a keyless contract account; the existential deposit must - // not stand in the way of recovering the funds. - Fortitude::Force, - ) { - Ok(moved) => log::info!( - target: LOG_TARGET, - "ReleaseContractsDeposits: swept {moved:?} from {account:?} to the escrow", - ), - // Failing the whole migration would leave the remaining holds in place, which is - // exactly the outcome this is meant to avoid. `post_upgrade` catches any leftover. - Err(err) => log::error!( - target: LOG_TARGET, - "ReleaseContractsDeposits: failed to sweep {amount:?} from {account:?}: {err:?}", - ), - } - } -} diff --git a/pallets/contracts-mbm/src/tests.rs b/pallets/contracts-mbm/src/tests.rs index fca19a5220..17031370cc 100644 --- a/pallets/contracts-mbm/src/tests.rs +++ b/pallets/contracts-mbm/src/tests.rs @@ -18,138 +18,311 @@ #![cfg(all(test, not(feature = "runtime-benchmarks")))] -use crate::mock::{ - migrations_in_progress, new_test_ext, run_to_block, AllPalletsWithSystem, Balances, HoldReason, - MaxServiceWeight, Runtime, ESCROW, +use crate::{ + mock::{ + migrations_in_progress, new_test_ext, run_to_block, AllPalletsWithSystem, MaxServiceWeight, + Purge, Remove, System, + }, + TrieId, CONTRACT_INFO_OF, DELETION_QUEUE, TRIE_ID_LEN, }; -use frame_support::traits::{ - fungible::{Inspect, InspectHold, Mutate, MutateHold}, - OnRuntimeUpgrade, +use frame_support::{ + migrations::{SteppedMigration, SteppedMigrationError}, + storage::unhashed, + traits::OnRuntimeUpgrade, + weights::{Weight, WeightMeter}, + StorageHasher, Twox64Concat, }; +use parity_scale_codec::Encode; +use sp_io::hashing::twox_128; -fn fund_and_hold(who: u64, free: u64, reason: HoldReason, amount: u64) { - Balances::set_balance(&who, free); - Balances::hold(&reason, &who, amount).expect("can hold"); +fn pallet_prefix() -> Vec { + twox_128(b"Contracts").to_vec() } -fn held(who: u64, reason: HoldReason) -> u64 { - Balances::balance_on_hold(&reason, &who) +fn map_prefix(item: &[u8]) -> Vec { + [twox_128(b"Contracts"), twox_128(item)].concat() +} + +fn trie_id(seed: u8) -> Vec { + vec![seed; TRIE_ID_LEN] +} + +/// Mimics a `ContractInfoOf` entry: a `trie_id` followed by the remaining `ContractInfo` fields. +fn put_contract_info(who: u64, seed: u8) { + let key = [ + map_prefix(CONTRACT_INFO_OF), + Twox64Concat::hash(&who.encode()), + ] + .concat(); + let mut value = TrieId::try_from(trie_id(seed)).unwrap().encode(); + value.extend(core::iter::repeat(0xAB).take(96)); + unhashed::put_raw(&key, &value); +} + +/// Mimics a `DeletionQueue` entry, i.e. the trie of an already terminated contract. +fn put_deletion_queue(nonce: u32, seed: u8) { + let key = [ + map_prefix(DELETION_QUEUE), + Twox64Concat::hash(&nonce.encode()), + ] + .concat(); + unhashed::put_raw(&key, &TrieId::try_from(trie_id(seed)).unwrap().encode()); +} + +/// Writes an arbitrary key under the pallet prefix, e.g. a `PristineCode` blob. +fn put_plain_entry(item: &[u8], nonce: u32, value_len: usize) { + let key = [map_prefix(item), twox_128(&nonce.to_le_bytes()).to_vec()].concat(); + unhashed::put_raw(&key, &vec![0xCD; value_len]); +} + +fn populate_child_trie(seed: u8, entries: u32) { + for i in 0..entries { + sp_io::default_child_storage::set(&trie_id(seed), &i.to_le_bytes(), &[i as u8; 64]); + } +} + +fn child_trie_is_empty(seed: u8) -> bool { + sp_io::default_child_storage::next_key(&trie_id(seed), &[]).is_none() +} + +fn prefix_is_empty(prefix: &[u8]) -> bool { + sp_io::storage::next_key(prefix) + .filter(|key| key.starts_with(prefix)) + .is_none() +} + +/// Registers an account the way `pallet-contracts` leaves a live contract account: a provider for +/// its existential deposit, plus the consumer reference taken at instantiation. +fn new_contract_account(who: u64) { + System::inc_providers(&who); + System::inc_consumers(&who).expect("the account has a provider"); } /// Onboards the multi block migrations the way a runtime upgrade does, then runs blocks until /// `pallet-migrations` reports it is done. Returns how many blocks that took. -fn run_migration_to_completion() -> u64 { +fn run_migrations_to_completion() -> u64 { AllPalletsWithSystem::on_runtime_upgrade(); - let start = frame_system::Pallet::::block_number(); + let start = System::block_number(); let mut current = start; while migrations_in_progress() { current += 1; run_to_block(current); - assert!(current - start < 1_000, "migration did not converge"); + assert!(current - start < 1_000, "migrations did not converge"); } current - start } +/// Drives a single migration to completion outside `pallet-migrations`, one meter per step. +fn run_steps(limit: Weight) -> u32 { + let mut cursor = None; + for step in 1..1_000 { + let mut meter = WeightMeter::with_limit(limit); + match M::step(cursor, &mut meter).expect("migration step succeeds") { + Some(next) => cursor = Some(next), + None => return step, + } + } + panic!("migration did not converge") +} + +#[test] +fn purge_then_remove_clears_the_whole_pallet() { + new_test_ext().execute_with(|| { + // A budget tight enough that neither migration can finish within a single block. + MaxServiceWeight::set(Weight::from_parts(50_000_000_000, 200_000)); + // Two live contracts and one already terminated, each with its own child trie, plus the + // top level keys (code blobs, ...) the removal has to sweep afterwards. + new_contract_account(1); + new_contract_account(2); + put_contract_info(1, 1); + put_contract_info(2, 2); + put_deletion_queue(0, 3); + for seed in [1, 2, 3] { + populate_child_trie(seed, 200); + } + for i in 0..20 { + put_plain_entry(b"PristineCode", i, 8 * 1024); + } + + let blocks = run_migrations_to_completion(); + + assert!( + blocks > 1, + "should have spanned several blocks, took {blocks}" + ); + for seed in [1, 2, 3] { + assert!( + child_trie_is_empty(seed), + "child trie {seed} still populated" + ); + } + assert!(prefix_is_empty(&pallet_prefix())); + }); +} + #[test] -fn both_contracts_hold_reasons_are_swept_to_the_escrow() { +fn live_contract_accounts_get_their_consumer_ref_back() { + // `pallet-contracts` takes a consumer ref at instantiation and only returns it on + // `seal_terminate`. `ContractInfoOf` is the last record of which accounts are contracts, so + // dropping the pallet without this would leave every one of them unreapable forever. new_test_ext().execute_with(|| { - // A real account holding a code upload deposit. - fund_and_hold(1, 1_000, HoldReason::CodeUploadDepositReserve, 400); - // A keyless contract account holding a storage deposit on top of its own free balance. - fund_and_hold(2, 1_000, HoldReason::StorageDepositReserve, 700); - - run_migration_to_completion(); - - assert_eq!(held(1, HoldReason::CodeUploadDepositReserve), 0); - assert_eq!(held(2, HoldReason::StorageDepositReserve), 0); - assert_eq!(Balances::balance(&ESCROW), 1_100); - // Only the deposits move; the free balances stay where they are. - assert_eq!(Balances::balance(&1), 600); - assert_eq!(Balances::balance(&2), 300); + new_contract_account(1); + put_contract_info(1, 1); + populate_child_trie(1, 10); + assert_eq!(System::consumers(&1), 1); + + run_migrations_to_completion(); + + assert_eq!(System::consumers(&1), 0); + // Only the reference `pallet-contracts` owned is handed back; the account survives. + assert_eq!(System::providers(&1), 1); }); } #[test] -fn holds_of_other_pallets_are_left_alone() { +fn only_the_contracts_own_consumer_ref_is_handed_back() { new_test_ext().execute_with(|| { - fund_and_hold(3, 1_000, HoldReason::Unrelated, 250); + new_contract_account(1); + // Some other pallet also holds a reference on the same account. + System::inc_consumers(&1).expect("the account has a provider"); + put_contract_info(1, 1); - run_migration_to_completion(); + run_migrations_to_completion(); - assert_eq!(held(3, HoldReason::Unrelated), 250); - assert_eq!(Balances::balance(&ESCROW), 0); + assert_eq!(System::consumers(&1), 1); }); } #[test] -fn settles_every_account_across_several_blocks() { +fn terminated_contracts_keep_their_consumer_count() { + // `DeletionQueue` entries belong to contracts that already ran `seal_terminate`, which gave + // the reference back. Decrementing again would underflow someone else's. new_test_ext().execute_with(|| { - // A budget that only affords a couple of accounts per block, so the migration has to - // suspend and resume - which round trips the account cursor through `pallet-migrations`. - // Derived from the weights rather than hardcoded, so it survives a regeneration of - // `weights.rs` on different hardware. `pallet-migrations` charges its own per block - // overhead against the same meter and raises a defensive failure if what is left cannot - // afford a single step, so that overhead has to be budgeted for explicitly. - let per_account = - as crate::WeightInfo>::release_deposit(); - let migrations_overhead = <() as pallet_migrations::WeightInfo>::progress_mbms_none() - .saturating_add(pallet_migrations::Pallet::::exec_migration_max_weight()); - MaxServiceWeight::set(migrations_overhead.saturating_add(per_account.saturating_mul(3))); - - for who in 10..40u64 { - fund_and_hold(who, 1_000, HoldReason::StorageDepositReserve, 100); + new_contract_account(1); + put_deletion_queue(0, 3); + populate_child_trie(3, 10); + + run_migrations_to_completion(); + + assert!(child_trie_is_empty(3)); + assert_eq!(System::consumers(&1), 1); + }); +} + +#[test] +fn purge_drops_entries_without_a_usable_trie_id() { + // A 16 byte blob decodes fine as a `BoundedVec` and a 400 byte one does not decode at + // all: both are the signature of a wrong field offset, and neither addresses a child trie. + new_test_ext().execute_with(|| { + for (nonce, garbage) in [(0u32, vec![0x11u8; 16]), (1, vec![0xFFu8; 400])] { + let key = [ + map_prefix(CONTRACT_INFO_OF), + Twox64Concat::hash(&nonce.encode()), + ] + .concat(); + unhashed::put_raw(&key, &garbage.encode()); } - let blocks = run_migration_to_completion(); + run_migrations_to_completion(); + + assert!(prefix_is_empty(&map_prefix(CONTRACT_INFO_OF))); + }); +} + +#[test] +fn removing_the_pallet_first_would_orphan_the_child_trie() { + // Documents *why* the ordering in the runtimes matters: the `trie_id` only lives inside the + // pallet prefix, so wiping it first makes the child trie unreachable for good. + new_test_ext().execute_with(|| { + put_contract_info(1, 1); + populate_child_trie(1, 10); + run_steps::(MaxServiceWeight::get()); + + assert!(prefix_is_empty(&pallet_prefix())); assert!( - blocks > 1, - "should have needed several blocks, took {blocks}" + !child_trie_is_empty(1), + "orphaned child trie - exactly what PurgeContractsChildTries prevents" ); - for who in 10..40u64 { - assert_eq!( - held(who, HoldReason::StorageDepositReserve), - 0, - "account {who}" - ); - } - assert_eq!(Balances::balance(&ESCROW), 30 * 100); }); } #[test] -fn is_a_no_op_when_there_are_no_holds() { +fn a_step_that_can_afford_nothing_reports_insufficient_weight() { new_test_ext().execute_with(|| { - run_migration_to_completion(); + put_contract_info(1, 1); + put_plain_entry(b"PristineCode", 0, 1024); + + let tiny = Weight::from_parts(1, 1); + for outcome in [ + Purge::step(None, &mut WeightMeter::with_limit(tiny)), + Remove::step(None, &mut WeightMeter::with_limit(tiny)).map(|cursor| cursor.map(|_| ())), + ] { + assert!(matches!( + outcome, + Err(SteppedMigrationError::InsufficientWeight { .. }) + )); + } + + // `Err` rolls the step back, so nothing may have been removed. + assert!(!prefix_is_empty(&pallet_prefix())); + }); +} - assert_eq!(Balances::balance(&ESCROW), 0); +#[test] +fn is_a_no_op_on_empty_storage() { + new_test_ext().execute_with(|| { + assert_eq!(run_migrations_to_completion(), 1); assert!(!migrations_in_progress()); }); } -#[cfg(feature = "try-runtime")] +/// The migrations address storage by name rather than through pallet types, so a typo would +/// silently make them no-ops. These values were read off Astar and Shiden mainnet state, where +/// the pallet prefix resolves to 781 and 1244 live keys respectively. #[test] -fn post_upgrade_catches_a_surviving_hold() { - use crate::{ - mock::{CodeDeposit, EscrowAccount, StorageDeposit}, - ReleaseContractsDeposits, +fn storage_prefixes_match_mainnet() { + let hex = |bytes: &[u8]| { + bytes.iter().fold(String::from("0x"), |mut acc, b| { + acc.push_str(&format!("{b:02x}")); + acc + }) }; - use frame_support::migrations::SteppedMigration; - type Release = ReleaseContractsDeposits< - Runtime, - CodeDeposit, - StorageDeposit, - EscrowAccount, - crate::weights::SubstrateWeight, - >; + assert_eq!( + hex(&pallet_prefix()), + "0x4342193e496fab7ec59d615ed0dc5530", + "Contracts" + ); + assert_eq!( + hex(&map_prefix(CONTRACT_INFO_OF)), + "0x4342193e496fab7ec59d615ed0dc5530060e99e5378e562537cf3bc983e17b91", + "Contracts::ContractInfoOf" + ); + assert_eq!( + hex(&map_prefix(DELETION_QUEUE)), + "0x4342193e496fab7ec59d615ed0dc553029162111ad19ef145155ee552aef2d11", + "Contracts::DeletionQueue" + ); + assert_eq!( + hex(&twox_128(b"RandomnessCollectiveFlip")), + "0xbd2a529379475088d3e29a918cd47872", + "RandomnessCollectiveFlip" + ); +} +#[cfg(feature = "try-runtime")] +#[test] +fn post_upgrade_catches_an_orphaned_child_trie() { new_test_ext().execute_with(|| { - fund_and_hold(1, 1_000, HoldReason::StorageDepositReserve, 400); - assert!(Release::post_upgrade(Vec::new()).is_err()); + put_contract_info(1, 1); + populate_child_trie(1, 10); + + let state = Purge::pre_upgrade().expect("snapshot succeeds"); + // Simulates the failure mode: the pointers are gone but the trie was never emptied. + run_steps::(MaxServiceWeight::get()); - run_migration_to_completion(); - assert!(Release::post_upgrade(Vec::new()).is_ok()); + assert!(Purge::post_upgrade(state).is_err()); }); } diff --git a/pallets/contracts-mbm/src/weights.rs b/pallets/contracts-mbm/src/weights.rs index 196ff5809c..14e1afd030 100644 --- a/pallets/contracts-mbm/src/weights.rs +++ b/pallets/contracts-mbm/src/weights.rs @@ -20,7 +20,7 @@ //! Autogenerated weights for `contracts_mbm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 49.1.0 -//! DATE: 2026-08-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-08-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `gh-runner-01-ovh`, CPU: `Intel(R) Xeon(R) E-2236 CPU @ 3.40GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 @@ -51,24 +51,60 @@ use core::marker::PhantomData; /// Weight info trait. pub trait WeightInfo { - fn release_deposit() -> Weight; + fn remove_key(x: u32, ) -> Weight; + fn remove_child_key(x: u32, ) -> Weight; + fn release_contract_consumer() -> Weight; } /// Weight functions for `contracts_mbm`. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - /// Storage: `Balances::Holds` (r:2 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(265), added: 2740, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:2 w:2) + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc5530` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc5530` (r:1 w:0) + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304d6c2b03b90028f3bf407eb433e96389` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304d6c2b03b90028f3bf407eb433e96389` (r:1 w:1) + /// The range of component `x` is `[0, 131072]`. + fn remove_key(x: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `82 + x * (1 ±0)` + // Estimated: `3546 + x * (1 ±0)` + // Minimum execution time: 18_295_000 picoseconds. + Weight::from_parts(19_400_806, 0) + .saturating_add(Weight::from_parts(0, 3546)) + // Standard Error: 0 + .saturating_add(Weight::from_parts(140, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(x.into())) + } + /// Storage: UNKNOWN KEY `0x` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x` (r:1 w:0) + /// Storage: UNKNOWN KEY `0x375e47f0a62e338ab3e389f0f35469ee` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x375e47f0a62e338ab3e389f0f35469ee` (r:1 w:1) + /// The range of component `x` is `[0, 131072]`. + fn remove_child_key(x: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `125 + x * (1 ±0)` + // Estimated: `3589 + x * (1 ±0)` + // Minimum execution time: 11_082_000 picoseconds. + Weight::from_parts(12_304_431, 0) + .saturating_add(Weight::from_parts(0, 3589)) + // Standard Error: 1 + .saturating_add(Weight::from_parts(165, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(x.into())) + } + /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn release_deposit() -> Weight { + fn release_contract_consumer() -> Weight { // Proof Size summary in bytes: - // Measured: `401` - // Estimated: `6470` - // Minimum execution time: 56_347_000 picoseconds. - Weight::from_parts(70_655_000, 0) - .saturating_add(Weight::from_parts(0, 6470)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `103` + // Estimated: `3593` + // Minimum execution time: 7_271_000 picoseconds. + Weight::from_parts(7_551_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/precompiles/dapp-staking/src/lib.rs b/precompiles/dapp-staking/src/lib.rs index 06cde7da18..6661929728 100644 --- a/precompiles/dapp-staking/src/lib.rs +++ b/precompiles/dapp-staking/src/lib.rs @@ -75,6 +75,10 @@ pub struct SmartContractV2 { } /// Convenience type for smart contract type handling. +/// +/// NOTE: the `Wasm` discriminant is part of the public Solidity ABI of the v2 interface and must +/// keep its value even though Wasm (ink!) smart contracts have been decommissioned. Calls using +/// it now revert, see [`SmartContractV2`] decoding below. #[derive(Clone, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] #[repr(u8)] pub(crate) enum SmartContractTypes { @@ -811,15 +815,12 @@ where let h160_address = H160::from_slice(smart_contract.address.as_bytes()); ::SmartContract::evm(h160_address) } + // Wasm (ink!) smart contracts have been decommissioned - `pallet-contracts` is gone + // and no Wasm dApp can be registered anymore, so reject these up front. SmartContractTypes::Wasm => { - ensure!( - smart_contract.address.as_bytes().len() == 32, - revert("Invalid address length for Astar WASM smart contract.") - ); - let mut staker_bytes = [0_u8; 32]; - staker_bytes[..].clone_from_slice(&smart_contract.address.as_bytes()); - - ::SmartContract::wasm(staker_bytes.into()) + return Err(revert( + "Wasm smart contracts are no longer supported by dApp staking.", + )) } }; diff --git a/precompiles/dapp-staking/src/test/tests_v3.rs b/precompiles/dapp-staking/src/test/tests_v3.rs index 0d3fe746df..bb61bab903 100644 --- a/precompiles/dapp-staking/src/test/tests_v3.rs +++ b/precompiles/dapp-staking/src/test/tests_v3.rs @@ -251,10 +251,9 @@ fn unstake_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; - let smart_contract_address = [0xAF; 32]; - let smart_contract = ::SmartContract::wasm( - smart_contract_address.into(), - ); + let smart_contract_address = H160::repeat_byte(0xAF); + let smart_contract = + ::SmartContract::evm(smart_contract_address); assert_ok!(DappStaking::register( RawOrigin::Root.into(), AddressMapper::into_account_id(staker_h160), @@ -274,8 +273,8 @@ fn unstake_is_ok() { )); let smart_contract_v2 = SmartContractV2 { - contract_type: SmartContractTypes::Wasm, - address: smart_contract_address.into(), + contract_type: SmartContractTypes::Evm, + address: smart_contract_address.as_bytes().into(), }; // Unstake some amount and verify event @@ -312,14 +311,12 @@ fn move_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; - let source_contract_address = [0xAF; 32]; - let source_contract = ::SmartContract::wasm( - source_contract_address.into(), - ); - let destination_contract_address = [0xB0; 32]; - let destination_contract = ::SmartContract::wasm( - destination_contract_address.into(), - ); + let source_contract_address = H160::repeat_byte(0xAF); + let source_contract = + ::SmartContract::evm(source_contract_address); + let destination_contract_address = H160::repeat_byte(0xB0); + let destination_contract = + ::SmartContract::evm(destination_contract_address); assert_ok!(DappStaking::register( RawOrigin::Root.into(), AddressMapper::into_account_id(staker_h160), @@ -344,12 +341,12 @@ fn move_is_ok() { )); let source_contract_v2 = SmartContractV2 { - contract_type: SmartContractTypes::Wasm, - address: source_contract_address.into(), + contract_type: SmartContractTypes::Evm, + address: source_contract_address.as_bytes().into(), }; let destination_contract_v2 = SmartContractV2 { - contract_type: SmartContractTypes::Wasm, - address: destination_contract_address.into(), + contract_type: SmartContractTypes::Evm, + address: destination_contract_address.as_bytes().into(), }; // Move some amount from source contract to destination contract and verify event @@ -388,10 +385,9 @@ fn claim_staker_rewards_is_ok() { // Register a dApp and stake on it let staker_h160 = ALICE; - let smart_contract_address = [0xAF; 32]; - let smart_contract = ::SmartContract::wasm( - smart_contract_address.into(), - ); + let smart_contract_address = H160::repeat_byte(0xAF); + let smart_contract = + ::SmartContract::evm(smart_contract_address); let amount = 1234; register_and_stake(staker_h160, smart_contract.clone(), amount); @@ -430,10 +426,9 @@ fn claim_bonus_reward_is_ok() { // Register a dApp and stake on it, loyally let staker_h160 = ALICE; - let smart_contract_address = [0xAF; 32]; - let smart_contract = ::SmartContract::wasm( - smart_contract_address.into(), - ); + let smart_contract_address = H160::repeat_byte(0xAF); + let smart_contract = + ::SmartContract::evm(smart_contract_address); let amount = 1234; register_and_stake(staker_h160, smart_contract.clone(), amount); @@ -441,8 +436,8 @@ fn claim_bonus_reward_is_ok() { advance_to_next_period(); let smart_contract_v2 = SmartContractV2 { - contract_type: SmartContractTypes::Wasm, - address: smart_contract_address.into(), + contract_type: SmartContractTypes::Evm, + address: smart_contract_address.as_bytes().into(), }; // Claim bonus reward and verify event @@ -474,10 +469,9 @@ fn claim_dapp_reward_is_ok() { // Register a dApp and stake on it let staker_h160 = ALICE; - let smart_contract_address = [0xAF; 32]; - let smart_contract = ::SmartContract::wasm( - smart_contract_address.into(), - ); + let smart_contract_address = H160::repeat_byte(0xAF); + let smart_contract = + ::SmartContract::evm(smart_contract_address); let amount = 1234; register_and_stake(staker_h160, smart_contract.clone(), amount); @@ -485,8 +479,8 @@ fn claim_dapp_reward_is_ok() { advance_to_era(3); let smart_contract_v2 = SmartContractV2 { - contract_type: SmartContractTypes::Wasm, - address: smart_contract_address.into(), + contract_type: SmartContractTypes::Evm, + address: smart_contract_address.as_bytes().into(), }; // Claim dApp reward and verify event @@ -520,10 +514,9 @@ fn unstake_from_unregistered_is_ok() { // Register a dApp and stake on it let staker_h160 = ALICE; - let smart_contract_address = [0xAF; 32]; - let smart_contract = ::SmartContract::wasm( - smart_contract_address.into(), - ); + let smart_contract_address = H160::repeat_byte(0xAF); + let smart_contract = + ::SmartContract::evm(smart_contract_address); let amount = 1234; register_and_stake(staker_h160, smart_contract.clone(), amount); @@ -534,8 +527,8 @@ fn unstake_from_unregistered_is_ok() { )); let smart_contract_v2 = SmartContractV2 { - contract_type: SmartContractTypes::Wasm, - address: smart_contract_address.into(), + contract_type: SmartContractTypes::Evm, + address: smart_contract_address.as_bytes().into(), }; // Unstake from the unregistered dApp and verify event @@ -575,10 +568,9 @@ fn cleanup_expired_entries_is_ok() { // Register a dApp and stake on it let staker_h160 = ALICE; - let smart_contract_address = [0xAF; 32]; - let smart_contract = ::SmartContract::wasm( - smart_contract_address.into(), - ); + let smart_contract_address = H160::repeat_byte(0xAF); + let smart_contract = + ::SmartContract::evm(smart_contract_address); let amount = 1234; register_and_stake(staker_h160, smart_contract.clone(), amount); diff --git a/precompiles/dapp-staking/src/test/types.rs b/precompiles/dapp-staking/src/test/types.rs index 7ad523c1f7..a4c96f1b6f 100644 --- a/precompiles/dapp-staking/src/test/types.rs +++ b/precompiles/dapp-staking/src/test/types.rs @@ -30,7 +30,8 @@ fn smart_contract_types_are_ok() { assert_eq!(Ok(SmartContractTypes::Evm), index.try_into()); } - // Verify Astar WASM smart contract type + // Verify Astar WASM smart contract type. + // Wasm smart contracts are decommissioned, but the ABI discriminant must stay stable. { let index: u8 = SmartContractTypes::Wasm.into(); assert_eq!(index, 1); @@ -64,7 +65,7 @@ fn decode_smart_contract_is_ok() { ); } - // Astar WASM smart contract decoding + // Astar WASM smart contract decoding is no longer supported { let address = [0x6E; 32]; let smart_contract_v2 = SmartContractV2 { @@ -72,11 +73,9 @@ fn decode_smart_contract_is_ok() { address: address.into(), }; - assert_eq!( - Ok(::SmartContract::wasm( - address.into() - )), - DappStakingV3Precompile::::decode_smart_contract(smart_contract_v2) + assert_matches!( + DappStakingV3Precompile::::decode_smart_contract(smart_contract_v2), + Err(_) ); } }); @@ -85,20 +84,6 @@ fn decode_smart_contract_is_ok() { #[test] fn decode_smart_contract_fails_when_type_and_address_mismatch() { ExternalityBuilder::build().execute_with(|| { - // H160 address for Wasm smart contract type - { - let address = H160::repeat_byte(0xCA); - let smart_contract_v2 = SmartContractV2 { - contract_type: SmartContractTypes::Wasm, - address: address.as_bytes().into(), - }; - - assert_matches!( - DappStakingV3Precompile::::decode_smart_contract(smart_contract_v2), - Err(_) - ); - } - // Native address for EVM smart contract type { let address = [0x6E; 32]; diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index ba71eee500..519c1eca85 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -43,8 +43,6 @@ xcm-executor = { workspace = true } # ORML dependencies -pallet-contracts = { workspace = true } - # Frontier dependencies pallet-evm = { workspace = true } @@ -81,7 +79,6 @@ std = [ "fp-evm/std", "pallet-assets/std", "pallet-evm/std", - "pallet-contracts/std", "pallet-evm-precompile-assets-erc20/std", "pallet-evm-precompile-dispatch/std", "sp-arithmetic/std", @@ -93,7 +90,6 @@ runtime-benchmarks = [ "pallet-assets/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-contracts/runtime-benchmarks", "pallet-evm/runtime-benchmarks", "pallet-evm-precompile-assets-erc20/runtime-benchmarks", "pallet-xc-asset-config/runtime-benchmarks", @@ -109,7 +105,6 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", ] try-runtime = [ - "pallet-contracts/try-runtime", "frame-support/try-runtime", "frame-system/try-runtime", "pallet-assets/try-runtime", diff --git a/primitives/src/dapp_staking.rs b/primitives/src/dapp_staking.rs index f0418d4b35..2deff9dc20 100644 --- a/primitives/src/dapp_staking.rs +++ b/primitives/src/dapp_staking.rs @@ -135,6 +135,13 @@ pub trait SmartContractHandle { /// Create a new smart contract representation for the specified EVM address. fn evm(address: H160) -> Self; /// Create a new smart contract representation for the specified Wasm address. + /// + /// # Deprecated + /// + /// Wasm (ink!) smart contracts have been decommissioned together with `pallet-contracts`. + /// No new Wasm dApp can be registered; this constructor is only kept so that historic + /// storage entries remain decodable. It will be removed once dApp staking storage on all + /// networks is provably free of `SmartContract::Wasm` entries. fn wasm(address: AccountId) -> Self; } @@ -156,6 +163,15 @@ pub enum SmartContract { /// EVM smart contract instance. Evm(H160), /// Wasm smart contract instance. + /// + /// # Deprecated + /// + /// `pallet-contracts` has been removed from all Astar networks and ink! is discontinued, + /// so no new dApp can be registered under this variant. + /// + /// The variant itself MUST NOT be removed until dApp staking storage (`IntegratedDApps`, + /// `StakerInfo`) is provably free of Wasm-keyed entries on Astar, Shiden and Shibuya -- + /// dropping it earlier would make those entries undecodable and brick the pallet. Wasm(AccountId), } diff --git a/runtime/astar/Cargo.toml b/runtime/astar/Cargo.toml index 3d20aa7dcf..88fe965e60 100644 --- a/runtime/astar/Cargo.toml +++ b/runtime/astar/Cargo.toml @@ -53,7 +53,6 @@ pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } pallet-collective = { workspace = true } -pallet-contracts = { workspace = true } pallet-democracy = { workspace = true } pallet-dynamic-evm-base-fee = { workspace = true } pallet-ethereum = { workspace = true } @@ -180,7 +179,6 @@ std = [ "contracts-mbm/std", "pallet-collective-proxy/std", "pallet-collective/std", - "pallet-contracts/std", "pallet-dapp-staking/std", "pallet-democracy/std", "pallet-dynamic-evm-base-fee/std", @@ -271,7 +269,6 @@ runtime-benchmarks = [ "contracts-mbm/runtime-benchmarks", "pallet-collective-proxy/runtime-benchmarks", "pallet-collective/runtime-benchmarks", - "pallet-contracts/runtime-benchmarks", "pallet-dapp-staking/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", "pallet-dynamic-evm-base-fee/runtime-benchmarks", @@ -332,7 +329,6 @@ try-runtime = [ "contracts-mbm/try-runtime", "pallet-collective-proxy/try-runtime", "pallet-collective/try-runtime", - "pallet-contracts/try-runtime", "pallet-dapp-staking/try-runtime", "pallet-democracy/try-runtime", "pallet-dynamic-evm-base-fee/try-runtime", diff --git a/runtime/astar/src/lib.rs b/runtime/astar/src/lib.rs index 6ae3d2ade1..660ed3e114 100644 --- a/runtime/astar/src/lib.rs +++ b/runtime/astar/src/lib.rs @@ -40,7 +40,7 @@ use frame_support::{ fungible::{Balanced, Credit, HoldConsideration}, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Contains, EqualPrivilegeOnly, FindAuthor, Get, Imbalance, InsideBoth, InstanceFilter, - LinearStoragePrice, Nothing, OnFinalize, OnUnbalanced, Randomness, WithdrawReasons, + LinearStoragePrice, OnFinalize, OnUnbalanced, WithdrawReasons, }, weights::{ constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, @@ -51,7 +51,7 @@ use frame_support::{ }; use frame_system::{ limits::{BlockLength, BlockWeights}, - EnsureNever, EnsureRoot, EnsureSigned, EnsureWithSuccess, + EnsureRoot, EnsureSigned, EnsureWithSuccess, }; use pallet_ethereum::PostLogContent; use pallet_evm::{FeeCalculator, GasWeightMapping, Runner}; @@ -70,7 +70,7 @@ use sp_runtime::{ generic, impl_opaque_keys, traits::{ AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, - DispatchInfoOf, Dispatchable, OpaqueKeys, PostDispatchInfoOf, UniqueSaturatedInto, Zero, + DispatchInfoOf, Dispatchable, OpaqueKeys, PostDispatchInfoOf, UniqueSaturatedInto, }, transaction_validity::{TransactionSource, TransactionValidity, TransactionValidityError}, ApplyExtrinsicResult, FixedPointNumber, Perbill, Permill, Perquintill, RuntimeDebug, @@ -145,14 +145,6 @@ pub const fn deposit(items: u32, bytes: u32) -> Balance { items as Balance * 100 * MILLIASTR + (bytes as Balance) * STORAGE_BYTE_FEE } -/// Charge fee for stored bytes and items as part of `pallet-contracts`. -/// -/// The slight difference to general `deposit` function is because there is fixed bound on how large the DB -/// key can grow so it doesn't make sense to have as high deposit per item as in the general approach. -pub const fn contracts_deposit(items: u32, bytes: u32) -> Balance { - items as Balance * 4 * MILLIASTR + (bytes as Balance) * STORAGE_BYTE_FEE -} - /// Change this to adjust the block time. pub const MILLISECS_PER_BLOCK: u64 = 6000; pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; @@ -257,8 +249,6 @@ pub struct BaseFilter; impl Contains for BaseFilter { fn contains(call: &RuntimeCall) -> bool { match call { - // Wasm (ink!) smart contracts are being decommissioned. - RuntimeCall::Contracts(..) => false, // Filter permission-less assets creation/destroying. // Custom asset's `id` should fit in `u32` as not to mix with service assets. RuntimeCall::Assets(method) => match method { @@ -433,11 +423,12 @@ impl pallet_dapp_staking::BenchmarkHelper, AccountId> for DAppStakingBenchmarkHelper, AccountId> { fn get_smart_contract(id: u32) -> SmartContract { + // Wasm smart contracts are no longer supported, only EVM ones can be registered. let id_bytes = id.to_le_bytes(); - let mut account = [0u8; 32]; - account[..id_bytes.len()].copy_from_slice(&id_bytes); + let mut address = [0u8; 20]; + address[..id_bytes.len()].copy_from_slice(&id_bytes); - SmartContract::Wasm(AccountId::from(account)) + SmartContract::Evm(H160::from(address)) } fn set_balance(account: &AccountId, amount: Balance) { @@ -740,66 +731,6 @@ impl pallet_vesting::Config for Runtime { const MAX_VESTING_SCHEDULES: u32 = 28; } -parameter_types! { - pub const DepositPerItem: Balance = contracts_deposit(1, 0); - pub const DepositPerByte: Balance = contracts_deposit(0, 1); - // Fallback value if storage deposit limit not set by the user - pub const DefaultDepositLimit: Balance = contracts_deposit(16, 16 * 1024); - pub const MaxDelegateDependencies: u32 = 32; - pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(10); - pub Schedule: pallet_contracts::Schedule = Default::default(); -} - -/// Codes using the randomness functionality cannot be uploaded. Neither can contracts -/// be instantiated from existing codes that use this deprecated functionality. -/// -/// But since some `Randomness` config type is still required for `pallet-contracts`, we provide this dummy type. -pub struct DummyDeprecatedRandomness; -impl Randomness for DummyDeprecatedRandomness { - fn random(_: &[u8]) -> (Hash, BlockNumber) { - (Default::default(), Zero::zero()) - } -} - -impl pallet_contracts::Config for Runtime { - type Time = Timestamp; - type Randomness = DummyDeprecatedRandomness; - type Currency = Balances; - type RuntimeEvent = RuntimeEvent; - type RuntimeCall = RuntimeCall; - type RuntimeHoldReason = RuntimeHoldReason; - /// The safest default is to allow no calls at all. - /// - /// Runtimes should whitelist dispatchables that are allowed to be called from contracts - /// and make sure they are stable. Dispatchables exposed to contracts are not allowed to - /// change because that would break already deployed contracts. The `Call` structure itself - /// is not allowed to change the indices of existing pallets, too. - type CallFilter = Nothing; - type DepositPerItem = DepositPerItem; - type DepositPerByte = DepositPerByte; - type DefaultDepositLimit = DefaultDepositLimit; - type CallStack = [pallet_contracts::Frame; 5]; - type WeightPrice = pallet_transaction_payment::Pallet; - type WeightInfo = pallet_contracts::weights::SubstrateWeight; - type ChainExtension = (); - type Schedule = Schedule; - type AddressGenerator = pallet_contracts::DefaultAddressGenerator; - type MaxCodeLen = ConstU32<{ 123 * 1024 }>; - type MaxStorageKeyLen = ConstU32<128>; - type UnsafeUnstableInterface = ConstBool; - type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; - type MaxDelegateDependencies = MaxDelegateDependencies; - type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = (); - type Environment = (); - type Migrations = (); - type Xcm = (); - type UploadOrigin = EnsureNever<::AccountId>; - type InstantiateOrigin = EnsureNever<::AccountId>; - type ApiVersion = (); - type MaxTransientStorageSize = ConstU32<{ 1 * 1024 * 1024 }>; -} - // These values are based on the Astar 2.0 Tokenomics Modeling report. parameter_types! { pub const TransactionLengthFeeFactor: Balance = 23_500_000_000_000; // 0.0000235 ASTR per byte @@ -1428,31 +1359,31 @@ impl pallet_collective_proxy::Config for Runtime { parameter_types! { pub MbmServiceWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block; - pub ContractsCodeDeposit: RuntimeHoldReason = - pallet_contracts::HoldReason::CodeUploadDepositReserve.into(); - pub ContractsStorageDeposit: RuntimeHoldReason = - pallet_contracts::HoldReason::StorageDepositReserve.into(); - /// Foundation controlled account collecting the settled `pallet-contracts` deposits. - /// `XPDSbfc3fcoVWEtPsxQXFDvWqnZgQfsxXv6MW8dd7G3GkZt` - pub ContractsDepositEscrow: AccountId = AccountId::from(hex_literal::hex!( - "400048a4f3672511dfcf2ddfcb34bafb80ee2f28bbec8cbe0283e90573e93474" - )); + /// Storage prefix of the decommissioned `pallet-contracts`. + pub const ContractsPalletName: &'static str = "Contracts"; } +/// Benchmarked weights backing the contract decommission migrations. +type ContractsMbmWeights = contracts_mbm::weights::SubstrateWeight; + /// Multi-block migrations executed by `pallet-migrations`. /// -/// Step one of decommissioning Wasm (ink!) smart contracts: settle every balance -/// `pallet-contracts` still owns. +/// Step two of decommissioning Wasm (ink!) smart contracts: `pallet-contracts` is gone, so the +/// storage it left behind is purged over multiple blocks. The purge also hands back the consumer +/// reference the pallet took on every live contract account - without it those accounts could +/// never be reaped. Registered dApps pointing at Wasm contracts are expected to have been +/// unregistered from dApp staking by governance beforehand. pub type MultiBlockMigrationsList = ( - contracts_mbm::ReleaseContractsDeposits< - Runtime, - ContractsCodeDeposit, - ContractsStorageDeposit, - ContractsDepositEscrow, - contracts_mbm::weights::SubstrateWeight, - >, + // Must come first: it needs the `trie_id`s stored under the `Contracts` prefix. + contracts_mbm::PurgeContractsChildTries, + contracts_mbm::RemovePalletStepped, ); +// Carries no storage and no calls, it only exists so the migrations above can be +// benchmarked. Deliberately not part of the production runtime. +#[cfg(feature = "runtime-benchmarks")] +impl contracts_mbm::Config for Runtime {} + impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; #[cfg(not(feature = "runtime-benchmarks"))] @@ -1468,11 +1399,6 @@ impl pallet_migrations::Config for Runtime { type WeightInfo = pallet_migrations::weights::SubstrateWeight; } -#[cfg(feature = "runtime-benchmarks")] -impl contracts_mbm::Config for Runtime { - type BenchmarkHoldReason = ContractsStorageDeposit; -} - /// Calls that can bypass the safe-mode pallet. pub struct SafeModeWhitelistedCalls; impl Contains for SafeModeWhitelistedCalls { @@ -1656,8 +1582,7 @@ mod runtime { #[runtime::pallet_index(63)] pub type DynamicEvmBaseFee = pallet_dynamic_evm_base_fee; - #[runtime::pallet_index(70)] - pub type Contracts = pallet_contracts; + // skip 70 - pallet_contracts previously #[runtime::pallet_index(84)] pub type Preimage = pallet_preimage; @@ -1750,11 +1675,6 @@ parameter_types! { /// Migrations/checks that do not need to be versioned and can run on every upgrade. pub type Permanent = (pallet_xcm::migration::MigrateToLatestXcmVersion,); -type EventRecord = frame_system::EventRecord< - ::RuntimeEvent, - ::Hash, ->; - impl fp_self_contained::SelfContainedCall for RuntimeCall { type SignedInfo = H160; @@ -2250,70 +2170,6 @@ impl_runtime_apis! { } } - impl pallet_contracts::ContractsApi for Runtime { - fn call( - origin: AccountId, - dest: AccountId, - value: Balance, - gas_limit: Option, - storage_deposit_limit: Option, - input_data: Vec, - ) -> pallet_contracts::ContractExecResult { - let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); - Contracts::bare_call( - origin, - dest, - value, - gas_limit, - storage_deposit_limit, - input_data, - pallet_contracts::DebugInfo::UnsafeDebug, - pallet_contracts::CollectEvents::UnsafeCollect, - pallet_contracts::Determinism::Enforced, - ) - } - - fn instantiate( - origin: AccountId, - value: Balance, - gas_limit: Option, - storage_deposit_limit: Option, - code: pallet_contracts::Code, - data: Vec, - salt: Vec, - ) -> pallet_contracts::ContractInstantiateResult { - let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); - Contracts::bare_instantiate( - origin, - value, - gas_limit, - storage_deposit_limit, - code, - data, - salt, - pallet_contracts::DebugInfo::UnsafeDebug, - pallet_contracts::CollectEvents::UnsafeCollect, - ) - } - - fn upload_code( - origin: AccountId, - code: Vec, - storage_deposit_limit: Option, - determinism: pallet_contracts::Determinism, - ) -> pallet_contracts::CodeUploadResult - { - Contracts::bare_upload_code(origin, code, storage_deposit_limit, determinism) - } - - fn get_storage( - address: AccountId, - key: Vec, - ) -> pallet_contracts::GetStorageResult { - Contracts::get_storage(address, key) - } - } - impl dapp_staking_runtime_api::DappStakingApi for Runtime { fn periods_per_cycle() -> PeriodNumber { InflationCycleConfig::periods_per_cycle() @@ -2794,7 +2650,6 @@ impl_runtime_apis! { let validate = true; let without_base_extrinsic_weight = true; - // Estimated encoded transaction size must be based on the heaviest transaction // type (EIP1559Transaction) to be compatible with all transaction types. let mut estimated_transaction_len = data.len() + diff --git a/runtime/astar/src/xcm_config.rs b/runtime/astar/src/xcm_config.rs index db0397ac85..9961924a36 100644 --- a/runtime/astar/src/xcm_config.rs +++ b/runtime/astar/src/xcm_config.rs @@ -29,7 +29,6 @@ use frame_support::{ weights::Weight, }; use frame_system::EnsureRoot; -use sp_runtime::traits::{Convert, MaybeEquivalence}; // Polkadot imports use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; @@ -155,7 +154,6 @@ impl Contains for ParentOrParentsPlurality { pub struct SafeCallFilter; impl SafeCallFilter { // 1. RuntimeCall::EVM(..) & RuntimeCall::Ethereum(..) have to be prohibited since we cannot measure PoV size properly - // 2. RuntimeCall::Contracts(..) can be allowed, but it hasn't been tested properly yet. /// Checks whether the base (non-composite) call is allowed to be executed via `Transact` XCM instruction. pub fn allow_base_call(call: &RuntimeCall) -> bool { diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index b10dc8d820..845a1bacd9 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -53,7 +53,6 @@ pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } pallet-collective = { workspace = true } -pallet-contracts = { workspace = true } pallet-democracy = { workspace = true } pallet-ethereum = { workspace = true } pallet-evm = { workspace = true } @@ -66,7 +65,6 @@ pallet-evm-precompile-modexp = { workspace = true } pallet-evm-precompile-sha3fips = { workspace = true } pallet-evm-precompile-simple = { workspace = true } pallet-identity = { workspace = true } -pallet-insecure-randomness-collective-flip = { workspace = true } pallet-membership = { workspace = true } pallet-message-queue = { workspace = true } pallet-migrations = { workspace = true } @@ -180,7 +178,7 @@ std = [ "contracts-mbm/std", "pallet-collective-proxy/std", "pallet-collective/std", - "pallet-contracts/std", + "contracts-mbm/std", "pallet-dapp-staking/std", "pallet-democracy/std", "pallet-dynamic-evm-base-fee/std", @@ -202,7 +200,6 @@ std = [ "pallet-evm/std", "pallet-identity/std", "pallet-inflation/std", - "pallet-insecure-randomness-collective-flip/std", "pallet-membership/std", "pallet-message-queue/std", "pallet-migrations/std", @@ -273,7 +270,7 @@ runtime-benchmarks = [ "contracts-mbm/runtime-benchmarks", "pallet-collective-proxy/runtime-benchmarks", "pallet-collective/runtime-benchmarks", - "pallet-contracts/runtime-benchmarks", + "contracts-mbm/runtime-benchmarks", "pallet-dapp-staking/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", "pallet-dynamic-evm-base-fee/runtime-benchmarks", @@ -334,7 +331,7 @@ try-runtime = [ "contracts-mbm/try-runtime", "pallet-collective-proxy/try-runtime", "pallet-collective/try-runtime", - "pallet-contracts/try-runtime", + "contracts-mbm/try-runtime", "pallet-dapp-staking/try-runtime", "pallet-democracy/try-runtime", "pallet-dynamic-evm-base-fee/try-runtime", @@ -343,7 +340,6 @@ try-runtime = [ "pallet-evm/try-runtime", "pallet-identity/try-runtime", "pallet-inflation/try-runtime", - "pallet-insecure-randomness-collective-flip/try-runtime", "pallet-membership/try-runtime", "pallet-message-queue/try-runtime", "pallet-migrations/try-runtime", diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index e1c74670cf..2d654c08fb 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -37,7 +37,7 @@ use frame_support::{ fungible::{Balanced, Credit, HoldConsideration}, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Contains, EqualPrivilegeOnly, FindAuthor, Get, Imbalance, InsideBoth, InstanceFilter, - LinearStoragePrice, Nothing, OnFinalize, OnUnbalanced, WithdrawReasons, + LinearStoragePrice, OnFinalize, OnUnbalanced, WithdrawReasons, }, weights::{ constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, @@ -48,7 +48,7 @@ use frame_support::{ }; use frame_system::{ limits::{BlockLength, BlockWeights}, - EnsureNever, EnsureRoot, EnsureSigned, EnsureWithSuccess, + EnsureRoot, EnsureSigned, EnsureWithSuccess, }; use pallet_ethereum::PostLogContent; use pallet_evm::{FeeCalculator, GasWeightMapping, Runner}; @@ -143,14 +143,6 @@ pub const fn deposit(items: u32, bytes: u32) -> Balance { items as Balance * 1 * SBY + (bytes as Balance) * STORAGE_BYTE_FEE } -/// Charge fee for stored bytes and items as part of `pallet-contracts`. -/// -/// The slight difference to general `deposit` function is because there is fixed bound on how large the DB -/// key can grow so it doesn't make sense to have as high deposit per item as in the general approach. -pub const fn contracts_deposit(items: u32, bytes: u32) -> Balance { - items as Balance * 40 * MICROSBY + (bytes as Balance) * STORAGE_BYTE_FEE -} - /// Change this to adjust the block time. pub const MILLISECS_PER_BLOCK: u64 = 6000; pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; @@ -278,8 +270,6 @@ pub struct BaseFilter; impl Contains for BaseFilter { fn contains(call: &RuntimeCall) -> bool { match call { - // Wasm (ink!) smart contracts are being decommissioned. - RuntimeCall::Contracts(..) => false, // Filter permission-less assets creation/destroying. // Custom asset's `id` should fit in `u32` as not to mix with service assets. RuntimeCall::Assets(method) => match method { @@ -356,8 +346,6 @@ impl pallet_timestamp::Config for Runtime { type WeightInfo = pallet_timestamp::weights::SubstrateWeight; } -impl pallet_insecure_randomness_collective_flip::Config for Runtime {} - parameter_types! { pub const BasicDeposit: Balance = deposit(1, 258); // 258 bytes on-chain pub const ByteDeposit: Balance = deposit(0, 1); @@ -456,11 +444,12 @@ impl pallet_dapp_staking::BenchmarkHelper, AccountId> for DAppStakingBenchmarkHelper, AccountId> { fn get_smart_contract(id: u32) -> SmartContract { + // Wasm smart contracts are no longer supported, only EVM ones can be registered. let id_bytes = id.to_le_bytes(); - let mut account = [0u8; 32]; - account[..id_bytes.len()].copy_from_slice(&id_bytes); + let mut address = [0u8; 20]; + address[..id_bytes.len()].copy_from_slice(&id_bytes); - SmartContract::Wasm(AccountId::from(account)) + SmartContract::Evm(H160::from(address)) } fn set_balance(account: &AccountId, amount: Balance) { @@ -750,55 +739,6 @@ impl pallet_vesting::Config for Runtime { const MAX_VESTING_SCHEDULES: u32 = 28; } -parameter_types! { - pub const DepositPerItem: Balance = contracts_deposit(1, 0); - pub const DepositPerByte: Balance = contracts_deposit(0, 1); - // Fallback value if storage deposit limit not set by the user - pub const DefaultDepositLimit: Balance = contracts_deposit(16, 16 * 1024); - pub const MaxDelegateDependencies: u32 = 32; - pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(10); - pub Schedule: pallet_contracts::Schedule = Default::default(); -} - -impl pallet_contracts::Config for Runtime { - type Time = Timestamp; - type Randomness = RandomnessCollectiveFlip; - type Currency = Balances; - type RuntimeEvent = RuntimeEvent; - type RuntimeCall = RuntimeCall; - type RuntimeHoldReason = RuntimeHoldReason; - /// The safest default is to allow no calls at all. - /// - /// Runtimes should whitelist dispatchables that are allowed to be called from contracts - /// and make sure they are stable. Dispatchables exposed to contracts are not allowed to - /// change because that would break already deployed contracts. The `Call` structure itself - /// is not allowed to change the indices of existing pallets, too. - type CallFilter = Nothing; - type DepositPerItem = DepositPerItem; - type DepositPerByte = DepositPerByte; - type DefaultDepositLimit = DefaultDepositLimit; - type CallStack = [pallet_contracts::Frame; 5]; - type WeightPrice = pallet_transaction_payment::Pallet; - type WeightInfo = pallet_contracts::weights::SubstrateWeight; - type ChainExtension = (); - type Schedule = Schedule; - type AddressGenerator = pallet_contracts::DefaultAddressGenerator; - type MaxCodeLen = ConstU32<{ 123 * 1024 }>; - type MaxStorageKeyLen = ConstU32<128>; - type UnsafeUnstableInterface = ConstBool; - type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; - type MaxDelegateDependencies = MaxDelegateDependencies; - type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = (); - type Environment = (); - type Migrations = (); - type Xcm = (); - type UploadOrigin = EnsureNever<::AccountId>; - type InstantiateOrigin = EnsureNever<::AccountId>; - type ApiVersion = (); - type MaxTransientStorageSize = ConstU32<{ 1 * 1024 * 1024 }>; -} - // These values are based on the Astar 2.0 Tokenomics Modeling report. parameter_types! { pub const TransactionLengthFeeFactor: Balance = 23_500_000_000_000; // 0.000_023_500_000_000_000 SBY per byte @@ -1433,31 +1373,34 @@ impl pallet_collective_proxy::Config for Runtime { parameter_types! { pub MbmServiceWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block; - pub ContractsCodeDeposit: RuntimeHoldReason = - pallet_contracts::HoldReason::CodeUploadDepositReserve.into(); - pub ContractsStorageDeposit: RuntimeHoldReason = - pallet_contracts::HoldReason::StorageDepositReserve.into(); - /// Foundation controlled account collecting the settled `pallet-contracts` deposits. - /// `XPDSbfc3fcoVWEtPsxQXFDvWqnZgQfsxXv6MW8dd7G3GkZt` - pub ContractsDepositEscrow: AccountId = AccountId::from(hex_literal::hex!( - "400048a4f3672511dfcf2ddfcb34bafb80ee2f28bbec8cbe0283e90573e93474" - )); + /// Storage prefix of the decommissioned `pallet-contracts`. + pub const ContractsPalletName: &'static str = "Contracts"; + /// Storage prefix of the decommissioned `pallet-insecure-randomness-collective-flip`. + pub const RandomnessPalletName: &'static str = "RandomnessCollectiveFlip"; } +/// Benchmarked weights backing the contract decommission migrations. +type ContractsMbmWeights = contracts_mbm::weights::SubstrateWeight; + /// Multi-block migrations executed by `pallet-migrations`. /// -/// Step one of decommissioning Wasm (ink!) smart contracts: settle every balance -/// `pallet-contracts` still owns. +/// Step two of decommissioning Wasm (ink!) smart contracts: `pallet-contracts` is gone, so the +/// storage it left behind is purged over multiple blocks. The purge also hands back the consumer +/// reference the pallet took on every live contract account - without it those accounts could +/// never be reaped. Registered dApps pointing at Wasm contracts are expected to have been +/// unregistered from dApp staking by governance beforehand. pub type MultiBlockMigrationsList = ( - contracts_mbm::ReleaseContractsDeposits< - Runtime, - ContractsCodeDeposit, - ContractsStorageDeposit, - ContractsDepositEscrow, - contracts_mbm::weights::SubstrateWeight, - >, + // Must come first: it needs the `trie_id`s stored under the `Contracts` prefix. + contracts_mbm::PurgeContractsChildTries, + contracts_mbm::RemovePalletStepped, + contracts_mbm::RemovePalletStepped, ); +// Carries no storage and no calls, it only exists so the migrations above can be +// benchmarked. Deliberately not part of the production runtime. +#[cfg(feature = "runtime-benchmarks")] +impl contracts_mbm::Config for Runtime {} + impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; #[cfg(not(feature = "runtime-benchmarks"))] @@ -1473,11 +1416,6 @@ impl pallet_migrations::Config for Runtime { type WeightInfo = pallet_migrations::weights::SubstrateWeight; } -#[cfg(feature = "runtime-benchmarks")] -impl contracts_mbm::Config for Runtime { - type BenchmarkHoldReason = ContractsStorageDeposit; -} - /// Calls that can bypass the safe-mode pallet. pub struct SafeModeWhitelistedCalls; impl Contains for SafeModeWhitelistedCalls { @@ -1593,8 +1531,7 @@ mod runtime { pub type Timestamp = pallet_timestamp; #[runtime::pallet_index(14)] pub type Multisig = pallet_multisig; - #[runtime::pallet_index(16)] - pub type RandomnessCollectiveFlip = pallet_insecure_randomness_collective_flip; + // skip 16 - pallet_insecure_randomness_collective_flip previously #[runtime::pallet_index(17)] pub type Scheduler = pallet_scheduler; #[runtime::pallet_index(18)] @@ -1654,8 +1591,7 @@ mod runtime { // skip 64 - pallet_ethereum_checked previously // skip 65 - pallet_unified_accounts previously - #[runtime::pallet_index(70)] - pub type Contracts = pallet_contracts; + // skip 70 - pallet_contracts previously #[runtime::pallet_index(84)] pub type Preimage = pallet_preimage; @@ -1747,11 +1683,6 @@ parameter_types! { /// Migrations/checks that do not need to be versioned and can run on every upgrade. pub type Permanent = (pallet_xcm::migration::MigrateToLatestXcmVersion,); -type EventRecord = frame_system::EventRecord< - ::RuntimeEvent, - ::Hash, ->; - impl fp_self_contained::SelfContainedCall for RuntimeCall { type SignedInfo = H160; @@ -2252,70 +2183,6 @@ impl_runtime_apis! { } } - impl pallet_contracts::ContractsApi for Runtime { - fn call( - origin: AccountId, - dest: AccountId, - value: Balance, - gas_limit: Option, - storage_deposit_limit: Option, - input_data: Vec, - ) -> pallet_contracts::ContractExecResult { - let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); - Contracts::bare_call( - origin, - dest, - value, - gas_limit, - storage_deposit_limit, - input_data, - pallet_contracts::DebugInfo::UnsafeDebug, - pallet_contracts::CollectEvents::UnsafeCollect, - pallet_contracts::Determinism::Enforced, - ) - } - - fn instantiate( - origin: AccountId, - value: Balance, - gas_limit: Option, - storage_deposit_limit: Option, - code: pallet_contracts::Code, - data: Vec, - salt: Vec, - ) -> pallet_contracts::ContractInstantiateResult { - let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); - Contracts::bare_instantiate( - origin, - value, - gas_limit, - storage_deposit_limit, - code, - data, - salt, - pallet_contracts::DebugInfo::UnsafeDebug, - pallet_contracts::CollectEvents::UnsafeCollect, - ) - } - - fn upload_code( - origin: AccountId, - code: Vec, - storage_deposit_limit: Option, - determinism: pallet_contracts::Determinism, - ) -> pallet_contracts::CodeUploadResult - { - Contracts::bare_upload_code(origin, code, storage_deposit_limit, determinism) - } - - fn get_storage( - address: AccountId, - key: Vec, - ) -> pallet_contracts::GetStorageResult { - Contracts::get_storage(address, key) - } - } - impl dapp_staking_runtime_api::DappStakingApi for Runtime { fn periods_per_cycle() -> PeriodNumber { InflationCycleConfig::periods_per_cycle() @@ -2797,7 +2664,6 @@ impl_runtime_apis! { let validate = true; let without_base_extrinsic_weight = true; - // Estimated encoded transaction size must be based on the heaviest transaction // type (EIP1559Transaction) to be compatible with all transaction types. let mut estimated_transaction_len = data.len() + diff --git a/runtime/shibuya/src/xcm_config.rs b/runtime/shibuya/src/xcm_config.rs index 80b4334a90..0fa3f051d0 100644 --- a/runtime/shibuya/src/xcm_config.rs +++ b/runtime/shibuya/src/xcm_config.rs @@ -29,7 +29,6 @@ use frame_support::{ weights::Weight, }; use frame_system::EnsureRoot; -use sp_runtime::traits::{Convert, MaybeEquivalence}; // Polkadot imports use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; diff --git a/runtime/shiden/Cargo.toml b/runtime/shiden/Cargo.toml index 0f512397e7..d15a501676 100644 --- a/runtime/shiden/Cargo.toml +++ b/runtime/shiden/Cargo.toml @@ -53,7 +53,6 @@ pallet-assets = { workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } -pallet-contracts = { workspace = true } pallet-dynamic-evm-base-fee = { workspace = true } pallet-ethereum = { workspace = true } pallet-evm = { workspace = true } @@ -65,7 +64,6 @@ pallet-evm-precompile-modexp = { workspace = true } pallet-evm-precompile-sha3fips = { workspace = true } pallet-evm-precompile-simple = { workspace = true } pallet-identity = { workspace = true } -pallet-insecure-randomness-collective-flip = { workspace = true } pallet-membership = { workspace = true } pallet-message-queue = { workspace = true } pallet-migrations = { workspace = true } @@ -166,7 +164,6 @@ std = [ "pallet-aura/std", "pallet-assets/std", "pallet-balances/std", - "pallet-contracts/std", "pallet-dynamic-evm-base-fee/std", "pallet-ethereum/std", "pallet-evm/std", @@ -180,6 +177,7 @@ std = [ "pallet-evm-precompile-modexp/std", "xcm-runtime-apis/std", "pallet-evm-precompile-sha3fips/std", + "contracts-mbm/std", "pallet-dapp-staking/std", "dapp-staking-runtime-api/std", "pallet-inflation/std", @@ -192,7 +190,6 @@ std = [ "pallet-multisig/std", "pallet-migrations/std", "pallet-membership/std", - "pallet-insecure-randomness-collective-flip/std", "pallet-session/std", "pallet-utility/std", "pallet-proxy/std", @@ -247,6 +244,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "contracts-mbm/runtime-benchmarks", "pallet-dapp-staking/runtime-benchmarks", "pallet-inflation/runtime-benchmarks", "pallet-session/runtime-benchmarks", @@ -268,7 +266,6 @@ runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", - "pallet-contracts/runtime-benchmarks", "pallet-evm/runtime-benchmarks", "pallet-evm-precompile-assets-erc20/runtime-benchmarks", "pallet-evm-precompile-dapp-staking/runtime-benchmarks", @@ -301,6 +298,7 @@ try-runtime = [ "pallet-aura/try-runtime", "pallet-balances/try-runtime", "pallet-membership/try-runtime", + "contracts-mbm/try-runtime", "pallet-dapp-staking/try-runtime", "pallet-inflation/try-runtime", "pallet-sudo/try-runtime", @@ -320,8 +318,6 @@ try-runtime = [ "pallet-multisig/try-runtime", "pallet-migrations/try-runtime", "pallet-proxy/try-runtime", - "pallet-insecure-randomness-collective-flip/try-runtime", - "pallet-contracts/try-runtime", "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", "cumulus-pallet-xcm/try-runtime", diff --git a/runtime/shiden/src/lib.rs b/runtime/shiden/src/lib.rs index bd14474338..00e3e41c0f 100644 --- a/runtime/shiden/src/lib.rs +++ b/runtime/shiden/src/lib.rs @@ -39,7 +39,7 @@ use frame_support::{ traits::{ fungible::{Balanced, Credit}, AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, Contains, FindAuthor, Get, - Imbalance, InstanceFilter, Nothing, OnFinalize, OnUnbalanced, WithdrawReasons, + Imbalance, InstanceFilter, OnFinalize, OnUnbalanced, WithdrawReasons, }, weights::{ constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, @@ -50,7 +50,7 @@ use frame_support::{ }; use frame_system::{ limits::{BlockLength, BlockWeights}, - EnsureNever, EnsureRoot, EnsureSigned, + EnsureRoot, EnsureSigned, }; use pallet_ethereum::PostLogContent; use pallet_evm::{FeeCalculator, GasWeightMapping, Runner}; @@ -135,14 +135,6 @@ pub const fn deposit(items: u32, bytes: u32) -> Balance { items as Balance * MILLISDN + (bytes as Balance) * STORAGE_BYTE_FEE } -/// Charge fee for stored bytes and items as part of `pallet-contracts`. -/// -/// The slight difference to general `deposit` function is because there is fixed bound on how large the DB -/// key can grow so it doesn't make sense to have as high deposit per item as in the general approach. -pub const fn contracts_deposit(items: u32, bytes: u32) -> Balance { - items as Balance * 40 * MICROSDN + (bytes as Balance) * STORAGE_BYTE_FEE -} - /// Change this to adjust the block time. pub const MILLISECS_PER_BLOCK: u64 = 6000; pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; @@ -247,8 +239,6 @@ pub struct BaseFilter; impl Contains for BaseFilter { fn contains(call: &RuntimeCall) -> bool { match call { - // Wasm (ink!) smart contracts are being decommissioned. - RuntimeCall::Contracts(..) => false, // Filter permission-less assets creation/destroying. // Custom asset's `id` should fit in `u32` as not to mix with service assets. RuntimeCall::Assets(method) => match method { @@ -344,8 +334,6 @@ impl pallet_message_queue::Config for Runtime { type IdleMaxServiceWeight = MessageQueueServiceWeight; } -impl pallet_insecure_randomness_collective_flip::Config for Runtime {} - parameter_types! { pub const BasicDeposit: Balance = deposit(1, 258); // 258 bytes on-chain pub const ByteDeposit: Balance = deposit(0, 1); @@ -406,11 +394,12 @@ impl pallet_dapp_staking::BenchmarkHelper, AccountId> for DAppStakingBenchmarkHelper, AccountId> { fn get_smart_contract(id: u32) -> SmartContract { + // Wasm smart contracts are no longer supported, only EVM ones can be registered. let id_bytes = id.to_le_bytes(); - let mut account = [0u8; 32]; - account[..id_bytes.len()].copy_from_slice(&id_bytes); + let mut address = [0u8; 20]; + address[..id_bytes.len()].copy_from_slice(&id_bytes); - SmartContract::Wasm(AccountId::from(account)) + SmartContract::Evm(H160::from(address)) } fn set_balance(account: &AccountId, amount: Balance) { @@ -716,55 +705,6 @@ impl pallet_vesting::Config for Runtime { const MAX_VESTING_SCHEDULES: u32 = 28; } -parameter_types! { - pub const DepositPerItem: Balance = contracts_deposit(1, 0); - pub const DepositPerByte: Balance = contracts_deposit(0, 1); - // Fallback value if storage deposit limit not set by the user - pub const DefaultDepositLimit: Balance = contracts_deposit(16, 16 * 1024); - pub const MaxDelegateDependencies: u32 = 32; - pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(10); - pub Schedule: pallet_contracts::Schedule = Default::default(); -} - -impl pallet_contracts::Config for Runtime { - type Time = Timestamp; - type Randomness = RandomnessCollectiveFlip; - type Currency = Balances; - type RuntimeEvent = RuntimeEvent; - type RuntimeCall = RuntimeCall; - type RuntimeHoldReason = RuntimeHoldReason; - /// The safest default is to allow no calls at all. - /// - /// Runtimes should whitelist dispatchables that are allowed to be called from contracts - /// and make sure they are stable. Dispatchables exposed to contracts are not allowed to - /// change because that would break already deployed contracts. The `Call` structure itself - /// is not allowed to change the indices of existing pallets, too. - type CallFilter = Nothing; - type DepositPerItem = DepositPerItem; - type DepositPerByte = DepositPerByte; - type DefaultDepositLimit = DefaultDepositLimit; - type CallStack = [pallet_contracts::Frame; 5]; - type WeightPrice = pallet_transaction_payment::Pallet; - type WeightInfo = pallet_contracts::weights::SubstrateWeight; - type ChainExtension = (); - type Schedule = Schedule; - type AddressGenerator = pallet_contracts::DefaultAddressGenerator; - type MaxCodeLen = ConstU32<{ 123 * 1024 }>; - type MaxStorageKeyLen = ConstU32<128>; - type UnsafeUnstableInterface = ConstBool; - type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; - type MaxDelegateDependencies = MaxDelegateDependencies; - type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = (); - type Environment = (); - type Migrations = (); - type Xcm = (); - type UploadOrigin = EnsureNever<::AccountId>; - type InstantiateOrigin = EnsureNever<::AccountId>; - type ApiVersion = (); - type MaxTransientStorageSize = ConstU32<{ 1 * 1024 * 1024 }>; -} - parameter_types! { pub const TransactionLengthFeeFactor: Balance = 235_000_000_000; // 0.000_000_235_000_000_000 SDN per byte pub const WeightFeeFactor: Balance = 308_550_000_000_000; // Around 0.000_300 SDN per unit of base weight. @@ -1122,31 +1062,34 @@ impl pallet_proxy::Config for Runtime { parameter_types! { pub MbmServiceWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block; - pub ContractsCodeDeposit: RuntimeHoldReason = - pallet_contracts::HoldReason::CodeUploadDepositReserve.into(); - pub ContractsStorageDeposit: RuntimeHoldReason = - pallet_contracts::HoldReason::StorageDepositReserve.into(); - /// Foundation controlled account collecting the settled `pallet-contracts` deposits. - /// `XPDSbfc3fcoVWEtPsxQXFDvWqnZgQfsxXv6MW8dd7G3GkZt` - pub ContractsDepositEscrow: AccountId = AccountId::from(hex_literal::hex!( - "400048a4f3672511dfcf2ddfcb34bafb80ee2f28bbec8cbe0283e90573e93474" - )); + /// Storage prefix of the decommissioned `pallet-contracts`. + pub const ContractsPalletName: &'static str = "Contracts"; + /// Storage prefix of the decommissioned `pallet-insecure-randomness-collective-flip`. + pub const RandomnessPalletName: &'static str = "RandomnessCollectiveFlip"; } +/// Benchmarked weights backing the contract decommission migrations. +type ContractsMbmWeights = contracts_mbm::weights::SubstrateWeight; + /// Multi-block migrations executed by `pallet-migrations`. /// -/// Step one of decommissioning Wasm (ink!) smart contracts: settle every balance -/// `pallet-contracts` still owns. +/// Step two of decommissioning Wasm (ink!) smart contracts: `pallet-contracts` is gone, so the +/// storage it left behind is purged over multiple blocks. The purge also hands back the consumer +/// reference the pallet took on every live contract account - without it those accounts could +/// never be reaped. Registered dApps pointing at Wasm contracts are expected to have been +/// unregistered from dApp staking by governance beforehand. pub type MultiBlockMigrationsList = ( - contracts_mbm::ReleaseContractsDeposits< - Runtime, - ContractsCodeDeposit, - ContractsStorageDeposit, - ContractsDepositEscrow, - contracts_mbm::weights::SubstrateWeight, - >, + // Must come first: it needs the `trie_id`s stored under the `Contracts` prefix. + contracts_mbm::PurgeContractsChildTries, + contracts_mbm::RemovePalletStepped, + contracts_mbm::RemovePalletStepped, ); +// Carries no storage and no calls, it only exists so the migrations above can be +// benchmarked. Deliberately not part of the production runtime. +#[cfg(feature = "runtime-benchmarks")] +impl contracts_mbm::Config for Runtime {} + impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; #[cfg(not(feature = "runtime-benchmarks"))] @@ -1162,11 +1105,6 @@ impl pallet_migrations::Config for Runtime { type WeightInfo = pallet_migrations::weights::SubstrateWeight; } -#[cfg(feature = "runtime-benchmarks")] -impl contracts_mbm::Config for Runtime { - type BenchmarkHoldReason = ContractsStorageDeposit; -} - #[frame_support::runtime] mod runtime { #[runtime::runtime] @@ -1250,11 +1188,8 @@ mod runtime { #[runtime::pallet_index(63)] pub type DynamicEvmBaseFee = pallet_dynamic_evm_base_fee; - #[runtime::pallet_index(70)] - pub type Contracts = pallet_contracts; - #[runtime::pallet_index(71)] - pub type RandomnessCollectiveFlip = pallet_insecure_randomness_collective_flip; - + // skip 70 - pallet_contracts previously + // skip 71 - pallet_insecure_randomness_collective_flip previously #[runtime::pallet_index(99)] pub type Sudo = pallet_sudo; @@ -1322,11 +1257,6 @@ parameter_types! { /// Migrations/checks that do not need to be versioned and can run on every upgrade. pub type Permanent = (pallet_xcm::migration::MigrateToLatestXcmVersion,); -type EventRecord = frame_system::EventRecord< - ::RuntimeEvent, - ::Hash, ->; - impl fp_self_contained::SelfContainedCall for RuntimeCall { type SignedInfo = H160; @@ -1820,70 +1750,6 @@ impl_runtime_apis! { } } - impl pallet_contracts::ContractsApi for Runtime { - fn call( - origin: AccountId, - dest: AccountId, - value: Balance, - gas_limit: Option, - storage_deposit_limit: Option, - input_data: Vec, - ) -> pallet_contracts::ContractExecResult { - let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); - Contracts::bare_call( - origin, - dest, - value, - gas_limit, - storage_deposit_limit, - input_data, - pallet_contracts::DebugInfo::UnsafeDebug, - pallet_contracts::CollectEvents::UnsafeCollect, - pallet_contracts::Determinism::Enforced, - ) - } - - fn instantiate( - origin: AccountId, - value: Balance, - gas_limit: Option, - storage_deposit_limit: Option, - code: pallet_contracts::Code, - data: Vec, - salt: Vec, - ) -> pallet_contracts::ContractInstantiateResult { - let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); - Contracts::bare_instantiate( - origin, - value, - gas_limit, - storage_deposit_limit, - code, - data, - salt, - pallet_contracts::DebugInfo::UnsafeDebug, - pallet_contracts::CollectEvents::UnsafeCollect, - ) - } - - fn upload_code( - origin: AccountId, - code: Vec, - storage_deposit_limit: Option, - determinism: pallet_contracts::Determinism, - ) -> pallet_contracts::CodeUploadResult - { - Contracts::bare_upload_code(origin, code, storage_deposit_limit, determinism) - } - - fn get_storage( - address: AccountId, - key: Vec, - ) -> pallet_contracts::GetStorageResult { - Contracts::get_storage(address, key) - } - } - impl dapp_staking_runtime_api::DappStakingApi for Runtime { fn periods_per_cycle() -> PeriodNumber { InflationCycleConfig::periods_per_cycle() @@ -2367,7 +2233,6 @@ impl_runtime_apis! { let validate = true; let without_base_extrinsic_weight = true; - // Estimated encoded transaction size must be based on the heaviest transaction // type (EIP1559Transaction) to be compatible with all transaction types. let mut estimated_transaction_len = data.len() + diff --git a/runtime/shiden/src/xcm_config.rs b/runtime/shiden/src/xcm_config.rs index 0e61656393..60719a920a 100644 --- a/runtime/shiden/src/xcm_config.rs +++ b/runtime/shiden/src/xcm_config.rs @@ -29,7 +29,6 @@ use frame_support::{ weights::Weight, }; use frame_system::EnsureRoot; -use sp_runtime::traits::{Convert, MaybeEquivalence}; // Polkadot imports use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; @@ -162,7 +161,6 @@ impl SafeCallFilter { pub fn allow_base_call(call: &RuntimeCall) -> bool { match call { RuntimeCall::System(..) - | RuntimeCall::Contracts(..) | RuntimeCall::Identity(..) | RuntimeCall::Balances(..) | RuntimeCall::Vesting(..) diff --git a/tests/ink-contracts/README.md b/tests/ink-contracts/README.md deleted file mode 100644 index 41a45de469..0000000000 --- a/tests/ink-contracts/README.md +++ /dev/null @@ -1,8 +0,0 @@ -## Ink! Contracts - -This directory contains Ink! contracts which are used in integration tests. - -`.wasm` files in this directory are used by `pallet-contracts` scenarios. The json -files are for informational purposes only and are not consumed by the tests. - -The source code for the contracts can be found at https://github.com/AstarNetwork/ink-test-contracts diff --git a/tests/ink-contracts/async-xcm-call-no-ce.json b/tests/ink-contracts/async-xcm-call-no-ce.json deleted file mode 100644 index 50c5e3e07b..0000000000 --- a/tests/ink-contracts/async-xcm-call-no-ce.json +++ /dev/null @@ -1,1509 +0,0 @@ -{ - "source": { - "hash": "0xea6312435caaa3765aee3a8640b33fa45317e13d097a1d51a4d211fdfe2d2088", - "language": "ink! 4.2.0", - "compiler": "rustc 1.68.0-nightly", - "build_info": { - "build_mode": "Debug", - "cargo_contract_version": "2.2.1", - "rust_toolchain": "nightly-aarch64-apple-darwin", - "wasm_opt_settings": { - "keep_debug_symbols": true, - "optimization_passes": "Z" - } - } - }, - "contract": { - "name": "async_xcm_call_contract", - "version": "0.1.0", - "authors": [ - "Stake Technologies " - ], - "repository": "https://github.com/AstarNetwork/Astar", - "homepage": "https://astar.network/" - }, - "spec": { - "constructors": [ - { - "args": [ - { - "label": "here_para_id", - "type": { - "displayName": [ - "u32" - ], - "type": 1 - } - } - ], - "default": false, - "docs": [], - "label": "new", - "payable": false, - "returnType": { - "displayName": [ - "ink_primitives", - "ConstructorResult" - ], - "type": 2 - }, - "selector": "0x00001111" - } - ], - "docs": [], - "environment": { - "accountId": { - "displayName": [ - "AccountId" - ], - "type": 33 - }, - "balance": { - "displayName": [ - "Balance" - ], - "type": 21 - }, - "blockNumber": { - "displayName": [ - "BlockNumber" - ], - "type": 1 - }, - "chainExtension": { - "displayName": [ - "ChainExtension" - ], - "type": 35 - }, - "hash": { - "displayName": [ - "Hash" - ], - "type": 34 - }, - "maxEventTopics": 4, - "timestamp": { - "displayName": [ - "Timestamp" - ], - "type": 17 - } - }, - "events": [], - "lang_error": { - "displayName": [ - "ink", - "LangError" - ], - "type": 4 - }, - "messages": [ - { - "args": [ - { - "label": "parachain_id", - "type": { - "displayName": [ - "u32" - ], - "type": 1 - } - }, - { - "label": "remark", - "type": { - "displayName": [ - "Vec" - ], - "type": 5 - } - }, - { - "label": "weight_and_fees", - "type": { - "displayName": [ - "WeightsAndFees" - ], - "type": 7 - } - } - ], - "default": false, - "docs": [ - " Attempt to perform remark operation on given parachain by", - " sending a XCM using `call_runtime`." - ], - "label": "attempt_remark_via_xcm", - "mutates": true, - "payable": false, - "returnType": { - "displayName": [ - "ink", - "MessageResult" - ], - "type": 30 - }, - "selector": "0x00002222" - }, - { - "args": [ - { - "label": "success", - "type": { - "displayName": [ - "bool" - ], - "type": 0 - } - } - ], - "default": false, - "docs": [], - "label": "handle_response", - "mutates": true, - "payable": false, - "returnType": { - "displayName": [ - "ink", - "MessageResult" - ], - "type": 2 - }, - "selector": "0x00003333" - }, - { - "args": [], - "default": false, - "docs": [], - "label": "result", - "mutates": false, - "payable": false, - "returnType": { - "displayName": [ - "ink", - "MessageResult" - ], - "type": 31 - }, - "selector": "0x00004444" - } - ] - }, - "storage": { - "root": { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "enum": { - "dispatchKey": "0x00000000", - "name": "Option", - "variants": { - "0": { - "fields": [], - "name": "None" - }, - "1": { - "fields": [ - { - "layout": { - "leaf": { - "key": "0x00000000", - "ty": 0 - } - }, - "name": "0" - } - ], - "name": "Some" - } - } - } - }, - "name": "result" - }, - { - "layout": { - "leaf": { - "key": "0x00000000", - "ty": 1 - } - }, - "name": "here_para_id" - } - ], - "name": "AsyncCall" - } - }, - "root_key": "0x00000000" - } - }, - "types": [ - { - "id": 0, - "type": { - "def": { - "primitive": "bool" - } - } - }, - { - "id": 1, - "type": { - "def": { - "primitive": "u32" - } - } - }, - { - "id": 2, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 3 - } - ], - "index": 0, - "name": "Ok" - }, - { - "fields": [ - { - "type": 4 - } - ], - "index": 1, - "name": "Err" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 3 - }, - { - "name": "E", - "type": 4 - } - ], - "path": [ - "Result" - ] - } - }, - { - "id": 3, - "type": { - "def": { - "tuple": [] - } - } - }, - { - "id": 4, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 1, - "name": "CouldNotReadInput" - } - ] - } - }, - "path": [ - "ink_primitives", - "LangError" - ] - } - }, - { - "id": 5, - "type": { - "def": { - "sequence": { - "type": 6 - } - } - } - }, - { - "id": 6, - "type": { - "def": { - "primitive": "u8" - } - } - }, - { - "id": 7, - "type": { - "def": { - "composite": { - "fields": [ - { - "name": "foreign_base_fee", - "type": 8, - "typeName": "MultiAsset" - }, - { - "name": "foreign_transact_weight", - "type": 29, - "typeName": "Weight" - }, - { - "name": "foreign_transcat_pallet_xcm", - "type": 29, - "typeName": "Weight" - }, - { - "name": "here_callback_base_fee", - "type": 8, - "typeName": "MultiAsset" - }, - { - "name": "here_callback_transact_weight", - "type": 29, - "typeName": "Weight" - }, - { - "name": "here_callback_contract_weight", - "type": 29, - "typeName": "Weight" - } - ] - } - }, - "path": [ - "async_xcm_call_contract", - "async_xcm_call_no_ce", - "WeightsAndFees" - ] - } - }, - { - "id": 8, - "type": { - "def": { - "composite": { - "fields": [ - { - "name": "id", - "type": 9, - "typeName": "AssetId" - }, - { - "name": "fun", - "type": 25, - "typeName": "Fungibility" - } - ] - } - }, - "path": [ - "xcm", - "v3", - "multiasset", - "MultiAsset" - ] - } - }, - { - "id": 9, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 10, - "typeName": "MultiLocation" - } - ], - "index": 0, - "name": "Concrete" - }, - { - "fields": [ - { - "type": 16, - "typeName": "[u8; 32]" - } - ], - "index": 1, - "name": "Abstract" - } - ] - } - }, - "path": [ - "xcm", - "v3", - "multiasset", - "AssetId" - ] - } - }, - { - "id": 10, - "type": { - "def": { - "composite": { - "fields": [ - { - "name": "parents", - "type": 6, - "typeName": "u8" - }, - { - "name": "interior", - "type": 11, - "typeName": "Junctions" - } - ] - } - }, - "path": [ - "xcm", - "v3", - "multilocation", - "MultiLocation" - ] - } - }, - { - "id": 11, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 0, - "name": "Here" - }, - { - "fields": [ - { - "type": 12, - "typeName": "Junction" - } - ], - "index": 1, - "name": "X1" - }, - { - "fields": [ - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - } - ], - "index": 2, - "name": "X2" - }, - { - "fields": [ - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - } - ], - "index": 3, - "name": "X3" - }, - { - "fields": [ - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - } - ], - "index": 4, - "name": "X4" - }, - { - "fields": [ - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - } - ], - "index": 5, - "name": "X5" - }, - { - "fields": [ - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - } - ], - "index": 6, - "name": "X6" - }, - { - "fields": [ - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - } - ], - "index": 7, - "name": "X7" - }, - { - "fields": [ - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - }, - { - "type": 12, - "typeName": "Junction" - } - ], - "index": 8, - "name": "X8" - } - ] - } - }, - "path": [ - "xcm", - "v3", - "junctions", - "Junctions" - ] - } - }, - { - "id": 12, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 13, - "typeName": "u32" - } - ], - "index": 0, - "name": "Parachain" - }, - { - "fields": [ - { - "name": "network", - "type": 14, - "typeName": "Option" - }, - { - "name": "id", - "type": 16, - "typeName": "[u8; 32]" - } - ], - "index": 1, - "name": "AccountId32" - }, - { - "fields": [ - { - "name": "network", - "type": 14, - "typeName": "Option" - }, - { - "name": "index", - "type": 18, - "typeName": "u64" - } - ], - "index": 2, - "name": "AccountIndex64" - }, - { - "fields": [ - { - "name": "network", - "type": 14, - "typeName": "Option" - }, - { - "name": "key", - "type": 19, - "typeName": "[u8; 20]" - } - ], - "index": 3, - "name": "AccountKey20" - }, - { - "fields": [ - { - "type": 6, - "typeName": "u8" - } - ], - "index": 4, - "name": "PalletInstance" - }, - { - "fields": [ - { - "type": 20, - "typeName": "u128" - } - ], - "index": 5, - "name": "GeneralIndex" - }, - { - "fields": [ - { - "name": "length", - "type": 6, - "typeName": "u8" - }, - { - "name": "data", - "type": 16, - "typeName": "[u8; 32]" - } - ], - "index": 6, - "name": "GeneralKey" - }, - { - "index": 7, - "name": "OnlyChild" - }, - { - "fields": [ - { - "name": "id", - "type": 22, - "typeName": "BodyId" - }, - { - "name": "part", - "type": 24, - "typeName": "BodyPart" - } - ], - "index": 8, - "name": "Plurality" - }, - { - "fields": [ - { - "type": 15, - "typeName": "NetworkId" - } - ], - "index": 9, - "name": "GlobalConsensus" - } - ] - } - }, - "path": [ - "xcm", - "v3", - "junction", - "Junction" - ] - } - }, - { - "id": 13, - "type": { - "def": { - "compact": { - "type": 1 - } - } - } - }, - { - "id": 14, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 0, - "name": "None" - }, - { - "fields": [ - { - "type": 15 - } - ], - "index": 1, - "name": "Some" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 15 - } - ], - "path": [ - "Option" - ] - } - }, - { - "id": 15, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 16, - "typeName": "[u8; 32]" - } - ], - "index": 0, - "name": "ByGenesis" - }, - { - "fields": [ - { - "name": "block_number", - "type": 17, - "typeName": "u64" - }, - { - "name": "block_hash", - "type": 16, - "typeName": "[u8; 32]" - } - ], - "index": 1, - "name": "ByFork" - }, - { - "index": 2, - "name": "Polkadot" - }, - { - "index": 3, - "name": "Kusama" - }, - { - "index": 4, - "name": "Westend" - }, - { - "index": 5, - "name": "Rococo" - }, - { - "index": 6, - "name": "Wococo" - }, - { - "fields": [ - { - "name": "chain_id", - "type": 18, - "typeName": "u64" - } - ], - "index": 7, - "name": "Ethereum" - }, - { - "index": 8, - "name": "BitcoinCore" - }, - { - "index": 9, - "name": "BitcoinCash" - } - ] - } - }, - "path": [ - "xcm", - "v3", - "junction", - "NetworkId" - ] - } - }, - { - "id": 16, - "type": { - "def": { - "array": { - "len": 32, - "type": 6 - } - } - } - }, - { - "id": 17, - "type": { - "def": { - "primitive": "u64" - } - } - }, - { - "id": 18, - "type": { - "def": { - "compact": { - "type": 17 - } - } - } - }, - { - "id": 19, - "type": { - "def": { - "array": { - "len": 20, - "type": 6 - } - } - } - }, - { - "id": 20, - "type": { - "def": { - "compact": { - "type": 21 - } - } - } - }, - { - "id": 21, - "type": { - "def": { - "primitive": "u128" - } - } - }, - { - "id": 22, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 0, - "name": "Unit" - }, - { - "fields": [ - { - "type": 23, - "typeName": "[u8; 4]" - } - ], - "index": 1, - "name": "Moniker" - }, - { - "fields": [ - { - "type": 13, - "typeName": "u32" - } - ], - "index": 2, - "name": "Index" - }, - { - "index": 3, - "name": "Executive" - }, - { - "index": 4, - "name": "Technical" - }, - { - "index": 5, - "name": "Legislative" - }, - { - "index": 6, - "name": "Judicial" - }, - { - "index": 7, - "name": "Defense" - }, - { - "index": 8, - "name": "Administration" - }, - { - "index": 9, - "name": "Treasury" - } - ] - } - }, - "path": [ - "xcm", - "v3", - "junction", - "BodyId" - ] - } - }, - { - "id": 23, - "type": { - "def": { - "array": { - "len": 4, - "type": 6 - } - } - } - }, - { - "id": 24, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 0, - "name": "Voice" - }, - { - "fields": [ - { - "name": "count", - "type": 13, - "typeName": "u32" - } - ], - "index": 1, - "name": "Members" - }, - { - "fields": [ - { - "name": "nom", - "type": 13, - "typeName": "u32" - }, - { - "name": "denom", - "type": 13, - "typeName": "u32" - } - ], - "index": 2, - "name": "Fraction" - }, - { - "fields": [ - { - "name": "nom", - "type": 13, - "typeName": "u32" - }, - { - "name": "denom", - "type": 13, - "typeName": "u32" - } - ], - "index": 3, - "name": "AtLeastProportion" - }, - { - "fields": [ - { - "name": "nom", - "type": 13, - "typeName": "u32" - }, - { - "name": "denom", - "type": 13, - "typeName": "u32" - } - ], - "index": 4, - "name": "MoreThanProportion" - } - ] - } - }, - "path": [ - "xcm", - "v3", - "junction", - "BodyPart" - ] - } - }, - { - "id": 25, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 20, - "typeName": "u128" - } - ], - "index": 0, - "name": "Fungible" - }, - { - "fields": [ - { - "type": 26, - "typeName": "AssetInstance" - } - ], - "index": 1, - "name": "NonFungible" - } - ] - } - }, - "path": [ - "xcm", - "v3", - "multiasset", - "Fungibility" - ] - } - }, - { - "id": 26, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 0, - "name": "Undefined" - }, - { - "fields": [ - { - "type": 20, - "typeName": "u128" - } - ], - "index": 1, - "name": "Index" - }, - { - "fields": [ - { - "type": 23, - "typeName": "[u8; 4]" - } - ], - "index": 2, - "name": "Array4" - }, - { - "fields": [ - { - "type": 27, - "typeName": "[u8; 8]" - } - ], - "index": 3, - "name": "Array8" - }, - { - "fields": [ - { - "type": 28, - "typeName": "[u8; 16]" - } - ], - "index": 4, - "name": "Array16" - }, - { - "fields": [ - { - "type": 16, - "typeName": "[u8; 32]" - } - ], - "index": 5, - "name": "Array32" - } - ] - } - }, - "path": [ - "xcm", - "v3", - "multiasset", - "AssetInstance" - ] - } - }, - { - "id": 27, - "type": { - "def": { - "array": { - "len": 8, - "type": 6 - } - } - } - }, - { - "id": 28, - "type": { - "def": { - "array": { - "len": 16, - "type": 6 - } - } - } - }, - { - "id": 29, - "type": { - "def": { - "composite": { - "fields": [ - { - "name": "ref_time", - "type": 18, - "typeName": "u64" - }, - { - "name": "proof_size", - "type": 18, - "typeName": "u64" - } - ] - } - }, - "path": [ - "sp_weights", - "weight_v2", - "Weight" - ] - } - }, - { - "id": 30, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 0 - } - ], - "index": 0, - "name": "Ok" - }, - { - "fields": [ - { - "type": 4 - } - ], - "index": 1, - "name": "Err" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 0 - }, - { - "name": "E", - "type": 4 - } - ], - "path": [ - "Result" - ] - } - }, - { - "id": 31, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 32 - } - ], - "index": 0, - "name": "Ok" - }, - { - "fields": [ - { - "type": 4 - } - ], - "index": 1, - "name": "Err" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 32 - }, - { - "name": "E", - "type": 4 - } - ], - "path": [ - "Result" - ] - } - }, - { - "id": 32, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 0, - "name": "None" - }, - { - "fields": [ - { - "type": 0 - } - ], - "index": 1, - "name": "Some" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 0 - } - ], - "path": [ - "Option" - ] - } - }, - { - "id": 33, - "type": { - "def": { - "composite": { - "fields": [ - { - "type": 16, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_primitives", - "types", - "AccountId" - ] - } - }, - { - "id": 34, - "type": { - "def": { - "composite": { - "fields": [ - { - "type": 16, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_primitives", - "types", - "Hash" - ] - } - }, - { - "id": 35, - "type": { - "def": { - "variant": {} - }, - "path": [ - "ink_env", - "types", - "NoChainExtension" - ] - } - } - ], - "version": "4" -} diff --git a/tests/ink-contracts/async-xcm-call-no-ce.wasm b/tests/ink-contracts/async-xcm-call-no-ce.wasm deleted file mode 100644 index b00d619810..0000000000 Binary files a/tests/ink-contracts/async-xcm-call-no-ce.wasm and /dev/null differ diff --git a/tests/ink-contracts/dummy_error.json b/tests/ink-contracts/dummy_error.json deleted file mode 100644 index 45b7aad6b0..0000000000 --- a/tests/ink-contracts/dummy_error.json +++ /dev/null @@ -1,391 +0,0 @@ -{ - "source": { - "hash": "0x5f210bc642632bb2a7728b9ef6ce2883f288525db397fda7e2a6e5746fbd2410", - "language": "ink! 4.3.0", - "compiler": "rustc 1.68.0-nightly", - "build_info": { - "build_mode": "Debug", - "cargo_contract_version": "3.0.1", - "rust_toolchain": "nightly-aarch64-apple-darwin", - "wasm_opt_settings": { - "keep_debug_symbols": false, - "optimization_passes": "Z" - } - } - }, - "contract": { - "name": "dummy_error", - "version": "0.1.0", - "authors": [ - "[your_name] <[your_email]>" - ] - }, - "spec": { - "constructors": [ - { - "args": [], - "default": false, - "docs": [], - "label": "new", - "payable": false, - "returnType": { - "displayName": [ - "ink_primitives", - "ConstructorResult" - ], - "type": 0 - }, - "selector": "0x9bae9d5e" - } - ], - "docs": [], - "environment": { - "accountId": { - "displayName": [ - "AccountId" - ], - "type": 6 - }, - "balance": { - "displayName": [ - "Balance" - ], - "type": 9 - }, - "blockNumber": { - "displayName": [ - "BlockNumber" - ], - "type": 12 - }, - "chainExtension": { - "displayName": [ - "ChainExtension" - ], - "type": 13 - }, - "hash": { - "displayName": [ - "Hash" - ], - "type": 10 - }, - "maxEventTopics": 4, - "timestamp": { - "displayName": [ - "Timestamp" - ], - "type": 11 - } - }, - "events": [], - "lang_error": { - "displayName": [ - "ink", - "LangError" - ], - "type": 2 - }, - "messages": [ - { - "args": [], - "default": false, - "docs": [], - "label": "do_revert", - "mutates": false, - "payable": false, - "returnType": { - "displayName": [ - "ink", - "MessageResult" - ], - "type": 3 - }, - "selector": "0x0000002a" - } - ] - }, - "storage": { - "root": { - "layout": { - "struct": { - "fields": [], - "name": "DummyError" - } - }, - "root_key": "0x00000000" - } - }, - "types": [ - { - "id": 0, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 1 - } - ], - "index": 0, - "name": "Ok" - }, - { - "fields": [ - { - "type": 2 - } - ], - "index": 1, - "name": "Err" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 1 - }, - { - "name": "E", - "type": 2 - } - ], - "path": [ - "Result" - ] - } - }, - { - "id": 1, - "type": { - "def": { - "tuple": [] - } - } - }, - { - "id": 2, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 1, - "name": "CouldNotReadInput" - } - ] - } - }, - "path": [ - "ink_primitives", - "LangError" - ] - } - }, - { - "id": 3, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 4 - } - ], - "index": 0, - "name": "Ok" - }, - { - "fields": [ - { - "type": 2 - } - ], - "index": 1, - "name": "Err" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 4 - }, - { - "name": "E", - "type": 2 - } - ], - "path": [ - "Result" - ] - } - }, - { - "id": 4, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 1 - } - ], - "index": 0, - "name": "Ok" - }, - { - "fields": [ - { - "type": 5 - } - ], - "index": 1, - "name": "Err" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 1 - }, - { - "name": "E", - "type": 5 - } - ], - "path": [ - "Result" - ] - } - }, - { - "id": 5, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 7, - "name": "DummyError" - } - ] - } - }, - "path": [ - "dummy_error", - "dummy_error", - "Error" - ] - } - }, - { - "id": 6, - "type": { - "def": { - "composite": { - "fields": [ - { - "type": 7, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_primitives", - "types", - "AccountId" - ] - } - }, - { - "id": 7, - "type": { - "def": { - "array": { - "len": 32, - "type": 8 - } - } - } - }, - { - "id": 8, - "type": { - "def": { - "primitive": "u8" - } - } - }, - { - "id": 9, - "type": { - "def": { - "primitive": "u128" - } - } - }, - { - "id": 10, - "type": { - "def": { - "composite": { - "fields": [ - { - "type": 7, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_primitives", - "types", - "Hash" - ] - } - }, - { - "id": 11, - "type": { - "def": { - "primitive": "u64" - } - } - }, - { - "id": 12, - "type": { - "def": { - "primitive": "u32" - } - } - }, - { - "id": 13, - "type": { - "def": { - "variant": {} - }, - "path": [ - "ink_env", - "types", - "NoChainExtension" - ] - } - } - ], - "version": "4" -} \ No newline at end of file diff --git a/tests/ink-contracts/dummy_error.wasm b/tests/ink-contracts/dummy_error.wasm deleted file mode 100644 index f72770149b..0000000000 Binary files a/tests/ink-contracts/dummy_error.wasm and /dev/null differ diff --git a/tests/ink-contracts/flipper.json b/tests/ink-contracts/flipper.json deleted file mode 100644 index f262449163..0000000000 --- a/tests/ink-contracts/flipper.json +++ /dev/null @@ -1,383 +0,0 @@ -{ - "source": { - "hash": "0xe8dbb2816fed4f8414c4a9fde5e9e89a00ab561c022cae1bd90c071aa0a1275a", - "language": "ink! 4.2.0", - "compiler": "rustc 1.68.0-nightly", - "build_info": { - "build_mode": "Debug", - "cargo_contract_version": "2.2.1", - "rust_toolchain": "nightly-aarch64-apple-darwin", - "wasm_opt_settings": { - "keep_debug_symbols": true, - "optimization_passes": "Z" - } - } - }, - "contract": { - "name": "flipper", - "version": "0.1.0", - "authors": [ - "Stake Technologies " - ], - "repository": "https://github.com/AstarNetwork/Astar", - "homepage": "https://astar.network/" - }, - "spec": { - "constructors": [ - { - "args": [ - { - "label": "init_value", - "type": { - "displayName": [ - "bool" - ], - "type": 0 - } - } - ], - "default": false, - "docs": [ - "Constructor that initializes the `bool` value to the given `init_value`." - ], - "label": "new", - "payable": false, - "returnType": { - "displayName": [ - "ink_primitives", - "ConstructorResult" - ], - "type": 1 - }, - "selector": "0x9bae9d5e" - } - ], - "docs": [], - "environment": { - "accountId": { - "displayName": [ - "AccountId" - ], - "type": 5 - }, - "balance": { - "displayName": [ - "Balance" - ], - "type": 8 - }, - "blockNumber": { - "displayName": [ - "BlockNumber" - ], - "type": 11 - }, - "chainExtension": { - "displayName": [ - "ChainExtension" - ], - "type": 12 - }, - "hash": { - "displayName": [ - "Hash" - ], - "type": 9 - }, - "maxEventTopics": 4, - "timestamp": { - "displayName": [ - "Timestamp" - ], - "type": 10 - } - }, - "events": [], - "lang_error": { - "displayName": [ - "ink", - "LangError" - ], - "type": 3 - }, - "messages": [ - { - "args": [], - "default": false, - "docs": [ - " A message that can be called on instantiated contracts.", - " This one flips the value of the stored `bool` from `true`", - " to `false` and vice versa." - ], - "label": "flip", - "mutates": true, - "payable": false, - "returnType": { - "displayName": [ - "ink", - "MessageResult" - ], - "type": 1 - }, - "selector": "0x633aa551" - }, - { - "args": [], - "default": false, - "docs": [ - " Simply returns the current value of our `bool`." - ], - "label": "get", - "mutates": false, - "payable": false, - "returnType": { - "displayName": [ - "ink", - "MessageResult" - ], - "type": 4 - }, - "selector": "0x2f865bd9" - } - ] - }, - "storage": { - "root": { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "leaf": { - "key": "0x00000000", - "ty": 0 - } - }, - "name": "value" - } - ], - "name": "Flipper" - } - }, - "root_key": "0x00000000" - } - }, - "types": [ - { - "id": 0, - "type": { - "def": { - "primitive": "bool" - } - } - }, - { - "id": 1, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 2 - } - ], - "index": 0, - "name": "Ok" - }, - { - "fields": [ - { - "type": 3 - } - ], - "index": 1, - "name": "Err" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 2 - }, - { - "name": "E", - "type": 3 - } - ], - "path": [ - "Result" - ] - } - }, - { - "id": 2, - "type": { - "def": { - "tuple": [] - } - } - }, - { - "id": 3, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 1, - "name": "CouldNotReadInput" - } - ] - } - }, - "path": [ - "ink_primitives", - "LangError" - ] - } - }, - { - "id": 4, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 0 - } - ], - "index": 0, - "name": "Ok" - }, - { - "fields": [ - { - "type": 3 - } - ], - "index": 1, - "name": "Err" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 0 - }, - { - "name": "E", - "type": 3 - } - ], - "path": [ - "Result" - ] - } - }, - { - "id": 5, - "type": { - "def": { - "composite": { - "fields": [ - { - "type": 6, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_primitives", - "types", - "AccountId" - ] - } - }, - { - "id": 6, - "type": { - "def": { - "array": { - "len": 32, - "type": 7 - } - } - } - }, - { - "id": 7, - "type": { - "def": { - "primitive": "u8" - } - } - }, - { - "id": 8, - "type": { - "def": { - "primitive": "u128" - } - } - }, - { - "id": 9, - "type": { - "def": { - "composite": { - "fields": [ - { - "type": 6, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_primitives", - "types", - "Hash" - ] - } - }, - { - "id": 10, - "type": { - "def": { - "primitive": "u64" - } - } - }, - { - "id": 11, - "type": { - "def": { - "primitive": "u32" - } - } - }, - { - "id": 12, - "type": { - "def": { - "variant": {} - }, - "path": [ - "ink_env", - "types", - "NoChainExtension" - ] - } - } - ], - "version": "4" -} \ No newline at end of file diff --git a/tests/ink-contracts/flipper.wasm b/tests/ink-contracts/flipper.wasm deleted file mode 100644 index a064cb4252..0000000000 Binary files a/tests/ink-contracts/flipper.wasm and /dev/null differ diff --git a/tests/ink-contracts/payable.json b/tests/ink-contracts/payable.json deleted file mode 100644 index 5be916d5c7..0000000000 --- a/tests/ink-contracts/payable.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "source": { - "hash": "0xcfbb81744c83a08b460d939a434f4c9876a6901f6a4a48e1516004632c5d02f0", - "language": "ink! 4.2.1", - "compiler": "rustc 1.68.0-nightly", - "build_info": { - "build_mode": "Debug", - "cargo_contract_version": "3.0.1", - "rust_toolchain": "nightly-aarch64-apple-darwin", - "wasm_opt_settings": { - "keep_debug_symbols": false, - "optimization_passes": "Z" - } - } - }, - "contract": { - "name": "payable", - "version": "0.1.0", - "authors": [ - "[your_name] <[your_email]>" - ] - }, - "spec": { - "constructors": [ - { - "args": [], - "default": false, - "docs": [], - "label": "new", - "payable": false, - "returnType": { - "displayName": [ - "ink_primitives", - "ConstructorResult" - ], - "type": 0 - }, - "selector": "0x9bae9d5e" - } - ], - "docs": [], - "environment": { - "accountId": { - "displayName": [ - "AccountId" - ], - "type": 3 - }, - "balance": { - "displayName": [ - "Balance" - ], - "type": 6 - }, - "blockNumber": { - "displayName": [ - "BlockNumber" - ], - "type": 9 - }, - "chainExtension": { - "displayName": [ - "ChainExtension" - ], - "type": 10 - }, - "hash": { - "displayName": [ - "Hash" - ], - "type": 7 - }, - "maxEventTopics": 4, - "timestamp": { - "displayName": [ - "Timestamp" - ], - "type": 8 - } - }, - "events": [], - "lang_error": { - "displayName": [ - "ink", - "LangError" - ], - "type": 2 - }, - "messages": [ - { - "args": [], - "default": false, - "docs": [], - "label": "deposit", - "mutates": false, - "payable": true, - "returnType": { - "displayName": [ - "ink", - "MessageResult" - ], - "type": 0 - }, - "selector": "0x0000002a" - } - ] - }, - "storage": { - "root": { - "layout": { - "struct": { - "fields": [], - "name": "Payable" - } - }, - "root_key": "0x00000000" - } - }, - "types": [ - { - "id": 0, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 1 - } - ], - "index": 0, - "name": "Ok" - }, - { - "fields": [ - { - "type": 2 - } - ], - "index": 1, - "name": "Err" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 1 - }, - { - "name": "E", - "type": 2 - } - ], - "path": [ - "Result" - ] - } - }, - { - "id": 1, - "type": { - "def": { - "tuple": [] - } - } - }, - { - "id": 2, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 1, - "name": "CouldNotReadInput" - } - ] - } - }, - "path": [ - "ink_primitives", - "LangError" - ] - } - }, - { - "id": 3, - "type": { - "def": { - "composite": { - "fields": [ - { - "type": 4, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_primitives", - "types", - "AccountId" - ] - } - }, - { - "id": 4, - "type": { - "def": { - "array": { - "len": 32, - "type": 5 - } - } - } - }, - { - "id": 5, - "type": { - "def": { - "primitive": "u8" - } - } - }, - { - "id": 6, - "type": { - "def": { - "primitive": "u128" - } - } - }, - { - "id": 7, - "type": { - "def": { - "composite": { - "fields": [ - { - "type": 4, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_primitives", - "types", - "Hash" - ] - } - }, - { - "id": 8, - "type": { - "def": { - "primitive": "u64" - } - } - }, - { - "id": 9, - "type": { - "def": { - "primitive": "u32" - } - } - }, - { - "id": 10, - "type": { - "def": { - "variant": {} - }, - "path": [ - "ink_env", - "types", - "NoChainExtension" - ] - } - } - ], - "version": "4" -} \ No newline at end of file diff --git a/tests/ink-contracts/payable.wasm b/tests/ink-contracts/payable.wasm deleted file mode 100644 index 34587fc018..0000000000 Binary files a/tests/ink-contracts/payable.wasm and /dev/null differ diff --git a/tests/ink-contracts/simple_storage.json b/tests/ink-contracts/simple_storage.json deleted file mode 100644 index 8e3841c6ca..0000000000 --- a/tests/ink-contracts/simple_storage.json +++ /dev/null @@ -1,360 +0,0 @@ -{ - "source": { - "hash": "0x32a4821b53ac490829d2bc44270c7f31f99d66ad12ed7902ae5a52b461ab38b8", - "language": "ink! 4.3.0", - "compiler": "rustc 1.68.0-nightly", - "build_info": { - "build_mode": "Debug", - "cargo_contract_version": "3.0.1", - "rust_toolchain": "nightly-aarch64-apple-darwin", - "wasm_opt_settings": { - "keep_debug_symbols": false, - "optimization_passes": "Z" - } - } - }, - "contract": { - "name": "simple_storage", - "version": "0.1.0", - "authors": [ - "[your_name] <[your_email]>" - ] - }, - "spec": { - "constructors": [ - { - "args": [], - "default": false, - "docs": [], - "label": "new", - "payable": false, - "returnType": { - "displayName": [ - "ink_primitives", - "ConstructorResult" - ], - "type": 1 - }, - "selector": "0x9bae9d5e" - } - ], - "docs": [], - "environment": { - "accountId": { - "displayName": [ - "AccountId" - ], - "type": 5 - }, - "balance": { - "displayName": [ - "Balance" - ], - "type": 8 - }, - "blockNumber": { - "displayName": [ - "BlockNumber" - ], - "type": 0 - }, - "chainExtension": { - "displayName": [ - "ChainExtension" - ], - "type": 11 - }, - "hash": { - "displayName": [ - "Hash" - ], - "type": 9 - }, - "maxEventTopics": 4, - "timestamp": { - "displayName": [ - "Timestamp" - ], - "type": 10 - } - }, - "events": [], - "lang_error": { - "displayName": [ - "ink", - "LangError" - ], - "type": 3 - }, - "messages": [ - { - "args": [], - "default": false, - "docs": [], - "label": "store", - "mutates": true, - "payable": false, - "returnType": { - "displayName": [ - "ink", - "MessageResult" - ], - "type": 1 - }, - "selector": "0x0000002a" - }, - { - "args": [], - "default": false, - "docs": [], - "label": "get", - "mutates": false, - "payable": false, - "returnType": { - "displayName": [ - "ink", - "MessageResult" - ], - "type": 4 - }, - "selector": "0x0000002b" - } - ] - }, - "storage": { - "root": { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "root": { - "layout": { - "leaf": { - "key": "0x8eba7622", - "ty": 0 - } - }, - "root_key": "0x8eba7622" - } - }, - "name": "storages" - } - ], - "name": "SimpleStorage" - } - }, - "root_key": "0x00000000" - } - }, - "types": [ - { - "id": 0, - "type": { - "def": { - "primitive": "u32" - } - } - }, - { - "id": 1, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 2 - } - ], - "index": 0, - "name": "Ok" - }, - { - "fields": [ - { - "type": 3 - } - ], - "index": 1, - "name": "Err" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 2 - }, - { - "name": "E", - "type": 3 - } - ], - "path": [ - "Result" - ] - } - }, - { - "id": 2, - "type": { - "def": { - "tuple": [] - } - } - }, - { - "id": 3, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 1, - "name": "CouldNotReadInput" - } - ] - } - }, - "path": [ - "ink_primitives", - "LangError" - ] - } - }, - { - "id": 4, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 0 - } - ], - "index": 0, - "name": "Ok" - }, - { - "fields": [ - { - "type": 3 - } - ], - "index": 1, - "name": "Err" - } - ] - } - }, - "params": [ - { - "name": "T", - "type": 0 - }, - { - "name": "E", - "type": 3 - } - ], - "path": [ - "Result" - ] - } - }, - { - "id": 5, - "type": { - "def": { - "composite": { - "fields": [ - { - "type": 6, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_primitives", - "types", - "AccountId" - ] - } - }, - { - "id": 6, - "type": { - "def": { - "array": { - "len": 32, - "type": 7 - } - } - } - }, - { - "id": 7, - "type": { - "def": { - "primitive": "u8" - } - } - }, - { - "id": 8, - "type": { - "def": { - "primitive": "u128" - } - } - }, - { - "id": 9, - "type": { - "def": { - "composite": { - "fields": [ - { - "type": 6, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_primitives", - "types", - "Hash" - ] - } - }, - { - "id": 10, - "type": { - "def": { - "primitive": "u64" - } - } - }, - { - "id": 11, - "type": { - "def": { - "variant": {} - }, - "path": [ - "ink_env", - "types", - "NoChainExtension" - ] - } - } - ], - "version": "4" -} \ No newline at end of file diff --git a/tests/ink-contracts/simple_storage.wasm b/tests/ink-contracts/simple_storage.wasm deleted file mode 100644 index caa61775e2..0000000000 Binary files a/tests/ink-contracts/simple_storage.wasm and /dev/null differ diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index dd47969c84..7d616db986 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -32,7 +32,6 @@ pallet-assets = { workspace = true } pallet-aura = { workspace = true } pallet-balances = { workspace = true } pallet-collective = { workspace = true } -pallet-contracts = { workspace = true } pallet-democracy = { workspace = true } pallet-membership = { workspace = true } pallet-proxy = { workspace = true } @@ -63,7 +62,7 @@ precompile-utils = { workspace = true } astar-primitives = { workspace = true } astar-runtime = { workspace = true, optional = true } -astar-test-utils = { workspace = true } +contracts-mbm = { workspace = true } shibuya-runtime = { workspace = true, optional = true } shiden-runtime = { workspace = true, optional = true } @@ -80,7 +79,7 @@ std = [ "shibuya-runtime/std", "shiden-runtime/std", "astar-primitives/std", - "astar-test-utils/std", + "contracts-mbm/std", "pallet-collective-proxy/std", "ethereum?/std", "fp-evm/std", @@ -94,7 +93,6 @@ std = [ "sp-trie/std", "pallet-balances/std", "pallet-collator-selection/std", - "pallet-contracts/std", "cumulus-primitives-parachain-inherent/std", "cumulus-primitives-core/std", "cumulus-pallet-parachain-system/std", diff --git a/tests/integration/src/contracts.rs b/tests/integration/src/contracts.rs index 47f89d192a..10cf096771 100644 --- a/tests/integration/src/contracts.rs +++ b/tests/integration/src/contracts.rs @@ -16,108 +16,116 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . +//! End-to-end checks of the `pallet-contracts` decommission against the real runtimes rather than +//! a mock: the storage prefixes, the account id encoding and the migration ordering are all the +//! runtime's own. + use crate::setup::*; +use contracts_mbm::{TrieId, CONTRACT_INFO_OF}; use frame_support::{ - migrations::SteppedMigrations, - traits::fungible::{Inspect, InspectHold, MutateHold}, - weights::WeightMeter, + migrations::SteppedMigrations, storage::unhashed, weights::WeightMeter, StorageHasher, + Twox64Concat, }; -use sp_runtime::DispatchError; +use parity_scale_codec::Encode; +use sp_io::hashing::twox_128; -/// New WASM code uploads are frozen. -#[test] -fn upload_code_is_disabled() { - new_test_ext().execute_with(|| { - assert_noop!( - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - vec![], - None, - pallet_contracts::Determinism::Enforced, - ), - DispatchError::BadOrigin, - ); - }); -} +/// Writes the storage `pallet-contracts` left behind for one live contract: a `ContractInfoOf` +/// entry pointing at a populated child trie, plus the consumer reference the pallet took on the +/// contract account at instantiation. +fn plant_live_contract(contract: &AccountId32, trie_id: &[u8]) { + let key = [ + twox_128(b"Contracts").to_vec(), + twox_128(CONTRACT_INFO_OF).to_vec(), + Twox64Concat::hash(&contract.encode()), + ] + .concat(); + let mut value = TrieId::try_from(trie_id.to_vec()).unwrap().encode(); + // Stands in for the rest of `ContractInfo`, which the migration never looks at. + value.extend(core::iter::repeat(0xAB).take(96)); + unhashed::put_raw(&key, &value); -/// Deploying a new contract with inline code is frozen. -#[test] -fn instantiate_with_code_is_disabled() { - new_test_ext().execute_with(|| { - assert_noop!( - Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), - 0u128, - Weight::zero(), - None, - vec![], - vec![], - vec![], - ), - DispatchError::BadOrigin, - ); - }); -} + sp_io::default_child_storage::set(trie_id, b"some-key", &[0xCD; 64]); -/// Instantiating from an existing code hash is frozen. -#[test] -fn instantiate_is_disabled() { - new_test_ext().execute_with(|| { - assert_noop!( - Contracts::instantiate( - RuntimeOrigin::signed(ALICE), - 0u128, - Weight::zero(), - None, - Default::default(), - vec![], - vec![], - ), - DispatchError::BadOrigin, - ); - }); + frame_system::Pallet::::inc_providers(contract); + frame_system::Pallet::::inc_consumers(contract).expect("the account has a provider"); } -/// The escrow collecting the settled contract deposits must stay the foundation account. -#[test] -fn deposit_escrow_is_the_foundation_account() { - // XPDSbfc3fcoVWEtPsxQXFDvWqnZgQfsxXv6MW8dd7G3GkZt - assert_eq!( - ContractsDepositEscrow::get(), - AccountId32::new(hex_literal::hex!( - "400048a4f3672511dfcf2ddfcb34bafb80ee2f28bbec8cbe0283e90573e93474" - )), - ); -} - -/// End-to-end check against the real runtime rather than a mock: a hold carrying the actual -/// `pallet_contracts` reason - i.e. the real `RuntimeHoldReason` variant, at the real pallet -/// index - is swept to the configured escrow by the migration tuple the runtime actually runs. -#[test] -fn contracts_holds_are_swept_to_the_escrow() { - new_test_ext().execute_with(|| { - let reason: RuntimeHoldReason = pallet_contracts::HoldReason::StorageDepositReserve.into(); - let escrow = ContractsDepositEscrow::get(); - let amount = 1_000 * UNIT; - - assert_ok!(Balances::hold(&reason, &ALICE, amount)); - assert_eq!(Balances::balance_on_hold(&reason, &ALICE), amount); - let escrow_before = Balances::balance(&escrow); - - // Driven the way `pallet-migrations` drives it, through the runtime's own tuple. +/// Runs the runtime's own migration tuple to completion, the way `pallet-migrations` does. +fn run_multi_block_migrations() { + for index in 0..MultiBlockMigrationsList::len() { let mut cursor = None; loop { let mut meter = WeightMeter::new(); - match MultiBlockMigrationsList::nth_step(0, cursor, &mut meter) - .expect("the tuple has a migration at index 0") + match MultiBlockMigrationsList::nth_step(index, cursor, &mut meter) + .expect("the tuple has a migration at this index") .expect("the step succeeds") { Some(next) => cursor = Some(next), None => break, } } + } +} + +/// A live contract account carries a consumer reference taken by `pallet-contracts` at +/// instantiation and only released by `seal_terminate`. `ContractInfoOf` is the last on-chain +/// record of which accounts are contracts, so the purge is the last chance to hand it back - +/// without that, every contract account stays unreapable forever. +#[test] +fn purging_a_contract_releases_its_consumer_ref_and_child_trie() { + new_test_ext().execute_with(|| { + let contract = AccountId32::new([9_u8; 32]); + let trie_id = [7_u8; 32]; + plant_live_contract(&contract, &trie_id); + + assert_eq!(frame_system::Pallet::::consumers(&contract), 1); - assert_eq!(Balances::balance_on_hold(&reason, &ALICE), 0); - assert_eq!(Balances::balance(&escrow), escrow_before + amount); + run_multi_block_migrations(); + + assert_eq!(frame_system::Pallet::::consumers(&contract), 0); + assert_eq!(frame_system::Pallet::::providers(&contract), 1); + assert!(sp_io::default_child_storage::next_key(&trie_id, &[]).is_none()); + }); +} + +/// Nothing may survive under the retired pallet prefixes; the indices themselves stay reserved. +#[test] +fn the_retired_pallet_prefixes_are_fully_purged() { + new_test_ext().execute_with(|| { + let contract = AccountId32::new([9_u8; 32]); + plant_live_contract(&contract, &[7_u8; 32]); + // A `PristineCode` blob, i.e. a key the child trie purge does not own. + unhashed::put_raw( + &[ + twox_128(b"Contracts").to_vec(), + twox_128(b"PristineCode").to_vec(), + Twox64Concat::hash(&[1_u8; 32]), + ] + .concat(), + &[0xEF; 1024], + ); + #[cfg(any(feature = "shiden", feature = "shibuya"))] + unhashed::put_raw( + &[ + twox_128(b"RandomnessCollectiveFlip").to_vec(), + twox_128(b"RandomMaterial").to_vec(), + ] + .concat(), + &[0xEF; 32], + ); + + run_multi_block_migrations(); + + for prefix in [ + twox_128(b"Contracts"), + twox_128(b"RandomnessCollectiveFlip"), + ] { + assert!( + sp_io::storage::next_key(&prefix) + .filter(|key| key.starts_with(&prefix)) + .is_none(), + "keys left under a retired pallet prefix", + ); + } }); } diff --git a/tests/integration/src/dapp_staking.rs b/tests/integration/src/dapp_staking.rs index 956cbd5c26..4d6af2a9a6 100644 --- a/tests/integration/src/dapp_staking.rs +++ b/tests/integration/src/dapp_staking.rs @@ -260,11 +260,11 @@ fn full_period_transition_recalculation_and_reward_distribution() { let base_id = NextDAppId::::get(); let contracts: Vec<_> = (0u8..16) .map(|i| { - let c = AccountId32::new([20 + i; 32]); + let c = H160::repeat_byte(20 + i); assert_ok!(DappStaking::register( RuntimeOrigin::root(), ALICE, - SmartContract::Wasm(c.clone()) + SmartContract::Evm(c) )); c }) @@ -322,7 +322,7 @@ fn full_period_transition_recalculation_and_reward_distribution() { for (i, &s) in stakes.iter().enumerate() { assert_ok!(DappStaking::stake( RuntimeOrigin::signed(stakers[i % 2].clone()), - SmartContract::Wasm(contracts[i].clone()), + SmartContract::Evm(contracts[i]), s, )); } @@ -401,7 +401,7 @@ fn full_period_transition_recalculation_and_reward_distribution() { // ── 7. Claim rewards BEFORE recalculation ── let claim_and_check = |idx: usize, exp_tier: u8, exp_rank: u8| { - let sc = SmartContract::Wasm(contracts[idx].clone()); + let sc = SmartContract::Evm(contracts[idx]); assert_ok!(DappStaking::claim_dapp_reward( RuntimeOrigin::signed(ALICE.clone()), sc.clone(), @@ -432,7 +432,7 @@ fn full_period_transition_recalculation_and_reward_distribution() { assert_noop!( DappStaking::claim_dapp_reward( RuntimeOrigin::signed(ALICE.clone()), - SmartContract::Wasm(contracts[15].clone()), + SmartContract::Evm(contracts[15]), assigned_era, ), pallet_dapp_staking::Error::::NoClaimableRewards diff --git a/tests/utils/Cargo.toml b/tests/utils/Cargo.toml deleted file mode 100644 index f4f015bd4b..0000000000 --- a/tests/utils/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "astar-test-utils" -version = "0.1.0" -description = "Astar test utils" -authors.workspace = true -edition.workspace = true -homepage.workspace = true -repository.workspace = true - -[lints] -workspace = true - -[dependencies] -parity-scale-codec = { workspace = true } -scale-info = { workspace = true } - -# Base functionality -frame-support = { workspace = true } -frame-system = { workspace = true } -pallet-contracts = { workspace = true } -pallet-contracts-uapi = { workspace = true } -sp-runtime = { workspace = true } - -[features] -default = ["std"] -std = [ - "parity-scale-codec/std", - "frame-support/std", - "frame-system/std", - "pallet-contracts/std", - "scale-info/std", - "sp-runtime/std", -] diff --git a/tests/utils/src/lib.rs b/tests/utils/src/lib.rs deleted file mode 100644 index 457b71533b..0000000000 --- a/tests/utils/src/lib.rs +++ /dev/null @@ -1,118 +0,0 @@ -// This file is part of Astar. - -// Copyright (C) Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later - -// Astar is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Astar is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Astar. If not, see . - -use frame_support::{traits::fungible::Inspect, weights::Weight}; -use pallet_contracts::Code; -use pallet_contracts_uapi::ReturnFlags; -use parity_scale_codec::Decode; -use sp_runtime::traits::Hash; - -type ContractBalanceOf = <::Currency as Inspect< - ::AccountId, ->>::Balance; - -/// Load a given wasm module from wasm binary contents along -/// with it's hash. -/// -/// The fixture files are located under the `../ink-contracts/` directory. -pub fn load_wasm_module( - fixture_name: &str, -) -> std::io::Result<(Vec, ::Output)> -where - T: frame_system::Config, -{ - let fixture_path = ["../ink-contracts/", fixture_name, ".wasm"].concat(); - let wasm_binary = std::fs::read(fixture_path)?; - let code_hash = T::Hashing::hash(&wasm_binary); - Ok((wasm_binary, code_hash)) -} - -// Load and deploy the contract from wasm binary -/// and check for successful deploy -pub fn deploy_wasm_contract( - contract_name: &str, - origin: T::AccountId, - value: ContractBalanceOf, - gas_limit: Weight, - storage_deposit_limit: Option>, - data: Vec, -) -> (T::AccountId, ::Output) { - let (code, hash) = load_wasm_module::(contract_name).unwrap(); - let outcome = pallet_contracts::Pallet::::bare_instantiate( - origin, - value, - gas_limit, - storage_deposit_limit, - Code::Upload(code), - data, - vec![], - pallet_contracts::DebugInfo::Skip, - pallet_contracts::CollectEvents::Skip, - ); - - // make sure it does not revert - let result = outcome.result.unwrap(); - assert!( - !result.result.did_revert(), - "deploy_contract: reverted - {:?}", - result - ); - (result.account_id, hash) -} - -/// Call the wasm contract method and returns the decoded return -/// values along with return flags and consumed weight -pub fn call_wasm_contract_method( - origin: T::AccountId, - dest: T::AccountId, - value: ContractBalanceOf, - gas_limit: Weight, - storage_deposit_limit: Option>, - data: Vec, - debug: bool, -) -> (V, ReturnFlags, Weight) { - let outcome = pallet_contracts::Pallet::::bare_call( - origin, - dest, - value, - gas_limit, - storage_deposit_limit, - data, - pallet_contracts::DebugInfo::Skip, - pallet_contracts::CollectEvents::Skip, - pallet_contracts::Determinism::Enforced, - ); - - if debug { - println!( - "Contract debug buffer - {:?}", - String::from_utf8(outcome.debug_message.clone()) - ); - println!("Contract outcome - {outcome:?}"); - } - - let res = outcome.result.unwrap(); - // check for revert - assert!(!res.did_revert(), "Contract reverted!"); - - let value = Result::::decode(&mut res.data.as_ref()) - .expect("decoding failed") - .expect("ink! lang error"); - - (value, res.flags, outcome.gas_consumed) -} diff --git a/tests/xcm-simulator/Cargo.toml b/tests/xcm-simulator/Cargo.toml index 8fb153d558..f5a6a92a84 100644 --- a/tests/xcm-simulator/Cargo.toml +++ b/tests/xcm-simulator/Cargo.toml @@ -22,8 +22,6 @@ frame-support = { workspace = true } frame-system = { workspace = true } pallet-assets = { workspace = true } pallet-balances = { workspace = true } -pallet-contracts = { workspace = true } -pallet-insecure-randomness-collective-flip = { workspace = true } pallet-proxy = { workspace = true } pallet-timestamp = { workspace = true } pallet-utility = { workspace = true } @@ -35,7 +33,6 @@ sp-tracing = { workspace = true } # Custom Astar inclusions astar-primitives = { workspace = true } -astar-test-utils = { workspace = true } pallet-dapp-staking = { workspace = true } pallet-xc-asset-config = { workspace = true } @@ -70,8 +67,6 @@ std = [ "frame-system/std", "pallet-balances/std", "pallet-timestamp/std", - "pallet-contracts/std", - "pallet-insecure-randomness-collective-flip/std", "pallet-xcm/std", "cumulus-pallet-xcm/std", "pallet-assets/std", @@ -87,7 +82,6 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", - "pallet-contracts/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", diff --git a/tests/xcm-simulator/src/mocks/mod.rs b/tests/xcm-simulator/src/mocks/mod.rs index a148de63f8..108d8a6358 100644 --- a/tests/xcm-simulator/src/mocks/mod.rs +++ b/tests/xcm-simulator/src/mocks/mod.rs @@ -35,7 +35,6 @@ use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chai pub const ALICE: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0xFAu8; 32]); pub const BOB: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0xFBu8; 32]); pub const INITIAL_BALANCE: u128 = 1_000_000_000_000_000_000_000_000; -pub const ONE: u128 = 1_000_000_000_000_000_000; decl_test_parachain! { pub struct ParaA { diff --git a/tests/xcm-simulator/src/mocks/parachain.rs b/tests/xcm-simulator/src/mocks/parachain.rs index 59f2c7ee57..d59f296639 100644 --- a/tests/xcm-simulator/src/mocks/parachain.rs +++ b/tests/xcm-simulator/src/mocks/parachain.rs @@ -134,11 +134,7 @@ impl pallet_timestamp::Config for Runtime { type MinimumPeriod = ConstU64<1>; } -impl pallet_insecure_randomness_collective_flip::Config for Runtime {} - /// Constant values used within the runtime. -pub const MICROSDN: Balance = 1_000_000_000_000; -pub const MILLISDN: Balance = 1_000 * MICROSDN; /// We assume that ~10% of the block weight is consumed by `on_initialize` handlers. /// This is used to limit the maximal weight of a single extrinsic. const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); @@ -175,56 +171,6 @@ parameter_types! { pub SS58Prefix: u8 = 5; } -parameter_types! { - pub const DepositPerItem: Balance = MILLISDN / 1_000_000; - pub const DepositPerByte: Balance = MILLISDN / 1_000_000; - pub const DefaultDepositLimit: Balance = 1000 * MILLISDN; - pub const MaxDelegateDependencies: u32 = 32; - pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(10); - pub Schedule: pallet_contracts::Schedule = Default::default(); -} - -impl Convert for Runtime { - fn convert(w: Weight) -> Balance { - w.ref_time().into() - } -} - -pub struct CallFilter; -impl Contains for CallFilter { - fn contains(call: &RuntimeCall) -> bool { - match call { - // allow pallet_xcm::send() - RuntimeCall::PolkadotXcm(pallet_xcm::Call::send { .. }) => true, - // no other calls allowed - _ => false, - } - } -} - -#[derive_impl(pallet_contracts::config_preludes::TestDefaultConfig)] -impl pallet_contracts::Config for Runtime { - type Time = Timestamp; - type Randomness = Randomness; - type Currency = Balances; - type RuntimeEvent = RuntimeEvent; - type RuntimeCall = RuntimeCall; - type RuntimeHoldReason = RuntimeHoldReason; - /// The safest default is to allow no calls at all. - /// - /// Runtimes should whitelist dispatchables that are allowed to be called from contracts - /// and make sure they are stable. Dispatchables exposed to contracts are not allowed to - /// change because that would break already deployed contracts. The `Call` structure itself - /// is not allowed to change the indices of existing pallets, too. - type CallFilter = CallFilter; - type CallStack = [pallet_contracts::Frame; 5]; - /// We are not using the pallet_transaction_payment for simplicity - type WeightInfo = pallet_contracts::weights::SubstrateWeight; - type Schedule = Schedule; - type UploadOrigin = EnsureSigned; - type InstantiateOrigin = EnsureSigned; -} - /// The type used to represent the kinds of proxying allowed. #[derive( Copy, @@ -621,8 +567,6 @@ construct_runtime!( DappStaking: pallet_dapp_staking, Proxy: pallet_proxy, Utility: pallet_utility, - Randomness: pallet_insecure_randomness_collective_flip, Timestamp: pallet_timestamp, - Contracts: pallet_contracts, } ); diff --git a/tests/xcm-simulator/src/tests/experimental.rs b/tests/xcm-simulator/src/tests/experimental.rs deleted file mode 100644 index 460755b8de..0000000000 --- a/tests/xcm-simulator/src/tests/experimental.rs +++ /dev/null @@ -1,386 +0,0 @@ -// This file is part of Astar. - -// Copyright (C) Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later - -// Astar is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Astar is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Astar. If not, see . - -use crate::mocks::{ - msg_queue::mock_msg_queue, - parachain::{self, System}, - *, -}; - -use astar_test_utils::{call_wasm_contract_method, deploy_wasm_contract}; -use frame_support::{assert_ok, weights::Weight}; -use parity_scale_codec::Encode; -use sp_runtime::traits::Bounded; -use xcm::{prelude::*, v5::Response}; -use xcm_executor::traits::QueryHandler; -use xcm_simulator::TestExt; - -const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); - -#[test] -fn basic_xcmp_transact_outcome_query_response() { - MockNet::reset(); - - // basic remark call - let remark = parachain::RuntimeCall::System( - frame_system::Call::::remark_with_event { - remark: vec![1, 2, 3], - }, - ); - - // priveleged root call - let root_call = - parachain::RuntimeCall::System(frame_system::Call::::set_storage { - items: vec![(vec![0], vec![1])], - }); - - // Closure for sending Transact(call) expecting success to dest returning - // query id for response - let send_transact = |call: parachain::RuntimeCall, dest: Location| { - // this will register the query and add `SetApendix` with `ReportError`. - let query_id = ParachainPalletXcm::new_query(dest.clone(), Bounded::max_value(), Here); - - // build xcm message - let xcm = Xcm(vec![ - WithdrawAsset((Here, 100_000_000_000_u128).into()), - BuyExecution { - fees: (Here, 100_000_000_000_u128).into(), - weight_limit: Unlimited, - }, - SetAppendix(Xcm(vec![ReportError(QueryResponseInfo { - destination: (Parent, Parachain(1)).into(), - query_id, - max_weight: Weight::zero(), - })])), - Transact { - origin_kind: OriginKind::SovereignAccount, - fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 1024 * 1024)), - call: call.encode().into(), - }, - ExpectTransactStatus(MaybeErrorCode::Success), - ]); - - // send the XCM to ParaB - assert_ok!(ParachainPalletXcm::send_xcm(Here, dest, xcm,)); - query_id - }; - - // send the remark Transct to ParaB expecting success and have outcome back - let mut query_id_success = 999u64; - ParaA::execute_with(|| { - query_id_success = send_transact(remark, (Parent, Parachain(2)).into()); - }); - - // check for if remark was executed in ParaB - ParaB::execute_with(|| { - use parachain::{RuntimeEvent, System}; - // check remark events - assert!(System::events().iter().any(|r| matches!( - r.event, - RuntimeEvent::System(frame_system::Event::Remarked { .. }) - ))); - - // clear the events - System::reset_events(); - }); - - // check the outcome we recieved from ParaB - ParaA::execute_with(move || { - let xcms = parachain::MsgQueue::received_xcmp(); - // sanity check - assert!( - xcms.len() == 1, - "Expected only one XCMP message, found {}", - xcms.len() - ); - assert!( - xcms[0].len() == 2, - "Response XCM should only have one instruction, i.e QueryResponse, found {}", - xcms[0].len() - ); - assert!(matches!( - xcms[0].0.as_slice(), - &[QueryResponse { - query_id, - response: Response::ExecutionResult(None), - .. - }, SetTopic(..)] if query_id == query_id_success - )); - - // clear the events - System::reset_events(); - }); - - // - // Failure - // - - // send the root_call Transct to ParaB expecting failure and have outcome back - let mut query_id_failure = 999u64; - ParaA::execute_with(|| { - query_id_failure = send_transact(root_call, (Parent, Parachain(2)).into()); - }); - - // check for if remark was executed in ParaB - ParaB::execute_with(|| { - use parachain::{RuntimeEvent, System}; - // check queue failed events - assert!(System::events().iter().any(|r| matches!( - r.event, - RuntimeEvent::MsgQueue(mock_msg_queue::Event::Fail { .. }) - ))); - }); - - // check the outcome we recieved from ParaB - ParaA::execute_with(|| { - let xcms = parachain::MsgQueue::received_xcmp(); - // sanity check - assert!(xcms.len() == 2 && xcms[1].len() == 2); - assert!(matches!( - xcms[1].0.as_slice(), - &[QueryResponse { - query_id, - response: Response::ExecutionResult(Some((4, xcm::v5::Error::ExpectationFalse))), - .. - },SetTopic(..)] if query_id == query_id_failure - )); - }); -} - -#[test] -fn xcm_remote_transact_contract() { - MockNet::reset(); - - const SELECTOR_CONSTRUCTOR: [u8; 4] = [0x9b, 0xae, 0x9d, 0x5e]; - const SELECTOR_GET: [u8; 4] = [0x2f, 0x86, 0x5b, 0xd9]; - const SELECTOR_FLIP: [u8; 4] = [0x63, 0x3a, 0xa5, 0x51]; - - // deploy and initialize flipper contract with `true` in ParaA - let mut contract_id = [0u8; 32].into(); - ParaA::execute_with(|| { - (contract_id, _) = deploy_wasm_contract::( - "flipper", - ALICE.into(), - 0, - GAS_LIMIT, - None, - // selector + true - [SELECTOR_CONSTRUCTOR.to_vec(), vec![0x01]].concat(), - ); - - // check for flip status - let (res, _, _) = call_wasm_contract_method::( - ALICE.into(), - contract_id.clone(), - 0, - GAS_LIMIT, - None, - SELECTOR_GET.to_vec(), - true, - ); - assert_eq!(res, true); - }); - - ParaB::execute_with(|| { - // dispatch call to flip contract - let call = parachain::RuntimeCall::Contracts(pallet_contracts::Call::call { - dest: contract_id.clone(), - value: 0, - gas_limit: Weight::from_parts(100_000_000_000, 1024 * 1024), - storage_deposit_limit: None, - data: SELECTOR_FLIP.to_vec(), - }); - - let xcm: Xcm<()> = Xcm(vec![ - WithdrawAsset((Here, INITIAL_BALANCE).into()), - BuyExecution { - fees: (Here, INITIAL_BALANCE).into(), - weight_limit: Unlimited, - }, - Transact { - origin_kind: OriginKind::SovereignAccount, - fallback_max_weight: Some(Weight::from_parts( - 100_000_000_000_000, - 1024 * 1024 * 1024, - )), - call: call.encode().into(), - }, - ExpectTransactStatus(MaybeErrorCode::Success), - ]); - - // send the XCM to ParaA - assert_ok!(ParachainPalletXcm::send( - parachain::RuntimeOrigin::signed(ALICE), - Box::new((Parent, Parachain(1)).into()), - Box::new(VersionedXcm::V5(xcm)), - )); - }); - - // check for flip status, it should be false - ParaA::execute_with(|| { - let (res, _, _) = call_wasm_contract_method::( - ALICE.into(), - contract_id.clone(), - 0, - GAS_LIMIT, - None, - SELECTOR_GET.to_vec(), - true, - ); - assert_eq!(res, false); - }); -} - -#[test] -fn test_async_xcm_contract_call_no_ce() { - /// All the fees and weights values required for the whole - /// operation. - #[derive(Encode)] - pub struct WeightsAndFees { - /// Max fee for whole XCM operation in foreign chain - /// This includes fees for sending XCM back to original - /// chain via Transact(pallet_xcm::send). - pub foreign_base_fee: xcm::v3::MultiAsset, // match smart-contract xcm version - /// Max weight for operation (remark) - pub foreign_transact_weight: Weight, - /// Max weight for Transact(pallet_xcm::send) operation - pub foreign_transcat_pallet_xcm: Weight, - /// Max fee for the callback operation - /// send by foreign chain - pub here_callback_base_fee: xcm::v3::MultiAsset, // match smart-contract xcm version - /// Max weight for Transact(pallet_contracts::call) - pub here_callback_transact_weight: Weight, - /// Max weight for contract call - pub here_callback_contract_weight: Weight, - } - - const CONSTRUCTOR_SELECTOR: [u8; 4] = [0x00, 0x00, 0x11, 0x11]; - const ATTEMPT_REMARK_SELECTOR: [u8; 4] = [0x00, 0x00, 0x22, 0x22]; - const RESULT_REMARK_SELECTOR: [u8; 4] = [0x00, 0x00, 0x44, 0x44]; - - // - // Setup - // - let contract_id = ParaA::execute_with(|| { - // deploy contract - let (contract_id, _) = deploy_wasm_contract::( - "async-xcm-call-no-ce", - ALICE.into(), - 0, - GAS_LIMIT, - Some(ONE * 100), - [CONSTRUCTOR_SELECTOR.to_vec(), 1.encode()].concat(), - ); - - // topup soverigin account of contract's derieve account in ParaB - assert_ok!(ParachainBalances::force_set_balance( - parachain::RuntimeOrigin::root(), - sibling_para_account_account_id( - 2, - sibling_para_account_account_id(1, contract_id.clone()) - ), - INITIAL_BALANCE, - )); - - contract_id - }); - - ParaB::execute_with(|| { - // topup contract's ParaB derieve account - assert_ok!(ParachainBalances::force_set_balance( - parachain::RuntimeOrigin::root(), - sibling_para_account_account_id(1, contract_id.clone()), - INITIAL_BALANCE, - )); - }); - - // - // Send the XCM - // - ParaA::execute_with(|| { - assert_eq!( - call_wasm_contract_method::( - ALICE.into(), - contract_id.clone(), - 0, - Weight::max_value(), - None, - [ - ATTEMPT_REMARK_SELECTOR.to_vec(), - 2u32.encode(), - [1u8, 2u8, 3u8].to_vec().encode(), - WeightsAndFees { - foreign_base_fee: xcm::v3::MultiAsset::from(( - xcm::v3::Junctions::Here, - 100_000_000_000_000_000_000_u128 - )), - foreign_transact_weight: GAS_LIMIT, - foreign_transcat_pallet_xcm: Weight::from_parts( - 2_000_000_000_000, - 3 * 1024 * 1024 - ), - here_callback_base_fee: xcm::v3::MultiAsset::from(( - xcm::v3::Junctions::Here, - 100_000_000_000_000_000_u128 - )), - here_callback_contract_weight: Weight::from_parts( - 400_000_000_000, - 1024 * 1024, - ), - here_callback_transact_weight: Weight::from_parts( - 500_000_000_000, - 2 * 1024 * 1024 - ), - } - .encode(), - ] - .concat(), - true, - ) - .0, - true - ); - }); - - // check for if remark was executed in ParaB - ParaB::execute_with(|| { - use parachain::{RuntimeEvent, System}; - // check remark events - assert!(System::events().iter().any(|r| matches!( - r.event, - RuntimeEvent::System(frame_system::Event::Remarked { .. }) - ))); - }); - - // Check for contract method called - ParaA::execute_with(|| { - assert_eq!( - call_wasm_contract_method::>( - ALICE.into(), - contract_id.clone(), - 0, - GAS_LIMIT, - None, - RESULT_REMARK_SELECTOR.to_vec(), - true, - ) - .0, - Some(true) - ); - }); -} diff --git a/tests/xcm-simulator/src/tests/mod.rs b/tests/xcm-simulator/src/tests/mod.rs index d237827118..909f459055 100644 --- a/tests/xcm-simulator/src/tests/mod.rs +++ b/tests/xcm-simulator/src/tests/mod.rs @@ -16,9 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -#[cfg(test)] -mod experimental; - #[cfg(test)] mod fungible_assets;