diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ff4b4bec0922..a0976b8a1a605 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -82,7 +82,7 @@ jobs: clippy: runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 60 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@clippy diff --git a/Cargo.lock b/Cargo.lock index dc586e841b510..f617563d1801f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1223,7 +1223,7 @@ dependencies = [ "parking_lot 0.12.4", "polkadot-sdk", "rand 0.8.5", - "rstest", + "rstest 0.26.1", "secp256k1 0.28.2", "serde", "serde_json", @@ -6320,6 +6320,7 @@ dependencies = [ "reqwest", "revive-strategy", "revm", + "rstest 0.24.0", "rvm-rs", "semver 1.0.26", "serde", @@ -6767,7 +6768,7 @@ dependencies = [ [[package]] name = "foundry-compilers" version = "0.18.2" -source = "git+https://github.com/paritytech/foundry-compilers-polkadot.git?branch=main#0ea2c2f7ecfb42b19a32b7e233273b682e0cb7a2" +source = "git+https://github.com/paritytech/foundry-compilers-polkadot.git?branch=main#479cf2b36f591d9489fd0cb5f5a3ba72c0ee6db0" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -6805,7 +6806,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" version = "0.18.2" -source = "git+https://github.com/paritytech/foundry-compilers-polkadot.git?branch=main#0ea2c2f7ecfb42b19a32b7e233273b682e0cb7a2" +source = "git+https://github.com/paritytech/foundry-compilers-polkadot.git?branch=main#479cf2b36f591d9489fd0cb5f5a3ba72c0ee6db0" dependencies = [ "foundry-compilers-artifacts-resolc", "foundry-compilers-artifacts-solc", @@ -6815,7 +6816,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-resolc" version = "0.18.2" -source = "git+https://github.com/paritytech/foundry-compilers-polkadot.git?branch=main#0ea2c2f7ecfb42b19a32b7e233273b682e0cb7a2" +source = "git+https://github.com/paritytech/foundry-compilers-polkadot.git?branch=main#479cf2b36f591d9489fd0cb5f5a3ba72c0ee6db0" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -6835,7 +6836,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" version = "0.18.2" -source = "git+https://github.com/paritytech/foundry-compilers-polkadot.git?branch=main#0ea2c2f7ecfb42b19a32b7e233273b682e0cb7a2" +source = "git+https://github.com/paritytech/foundry-compilers-polkadot.git?branch=main#479cf2b36f591d9489fd0cb5f5a3ba72c0ee6db0" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -6857,7 +6858,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" version = "0.18.2" -source = "git+https://github.com/paritytech/foundry-compilers-polkadot.git?branch=main#0ea2c2f7ecfb42b19a32b7e233273b682e0cb7a2" +source = "git+https://github.com/paritytech/foundry-compilers-polkadot.git?branch=main#479cf2b36f591d9489fd0cb5f5a3ba72c0ee6db0" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -6871,7 +6872,7 @@ dependencies = [ [[package]] name = "foundry-compilers-core" version = "0.18.2" -source = "git+https://github.com/paritytech/foundry-compilers-polkadot.git?branch=main#0ea2c2f7ecfb42b19a32b7e233273b682e0cb7a2" +source = "git+https://github.com/paritytech/foundry-compilers-polkadot.git?branch=main#479cf2b36f591d9489fd0cb5f5a3ba72c0ee6db0" dependencies = [ "alloy-primitives", "cfg-if", @@ -16110,6 +16111,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rstest" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e905296805ab93e13c1ec3a03f4b6c4f35e9498a3d5fa96dc626d22c03cd89" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros 0.24.0", + "rustc_version 0.4.1", +] + [[package]] name = "rstest" version = "0.26.1" @@ -16118,7 +16131,25 @@ checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" dependencies = [ "futures-timer", "futures-util", - "rstest_macros", + "rstest_macros 0.26.1", +] + +[[package]] +name = "rstest_macros" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef0053bbffce09062bee4bcc499b0fbe7a57b879f1efe088d6d8d4c7adcdef9b" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version 0.4.1", + "syn 2.0.104", + "unicode-ident", ] [[package]] diff --git a/crates/cast/src/rlp_converter.rs b/crates/cast/src/rlp_converter.rs index 36b974fb2e976..f386edddc541e 100644 --- a/crates/cast/src/rlp_converter.rs +++ b/crates/cast/src/rlp_converter.rs @@ -11,7 +11,7 @@ use std::fmt; #[derive(Clone, Debug, PartialEq, Eq)] pub enum Item { Data(Vec), - Array(Vec), + Array(Vec), } impl Encodable for Item { diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index 9cfe878082542..41034c174397f 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -542,22 +542,22 @@ enum Type { Builtin(DynSolType), /// (type) - Array(Box), + Array(Box), /// (type, length) - FixedArray(Box, usize), + FixedArray(Box, usize), /// (type, index) - ArrayIndex(Box, Option), + ArrayIndex(Box, Option), /// (types) - Tuple(Vec>), + Tuple(Vec>), /// (name, params, returns) - Function(Box, Vec>, Vec>), + Function(Box, Vec>, Vec>), /// (lhs, rhs) - Access(Box, String), + Access(Box, String), /// (types) Custom(Vec), diff --git a/crates/cli/src/opts/build/revive.rs b/crates/cli/src/opts/build/revive.rs index b98b59d6a4f98..601a1fb640bd6 100644 --- a/crates/cli/src/opts/build/revive.rs +++ b/crates/cli/src/opts/build/revive.rs @@ -15,14 +15,14 @@ pub struct ResolcOpts { )] pub resolc_compile: Option, - /// Enable PVM mode at startup (independent of compilation) + /// Use pallet-revive runtime backend #[arg( - long = "resolc-startup", - help = "Enable PVM mode at startup", - value_name = "RESOLC_STARTUP", + long = "polkadot", + help = "Use pallet-revive runtime backend", + value_name = "POLKADOT", action = clap::ArgAction::SetTrue )] - pub resolc_startup: Option, + pub polkadot: Option, /// Specify the resolc version, or a path to a local resolc, to build with. /// @@ -88,8 +88,8 @@ impl ResolcOpts { resolc.resolc_compile ); set_if_some!( - self.resolc_startup.and_then(|v| if v { Some(true) } else { None }), - resolc.resolc_startup + self.polkadot.and_then(|v| if v { Some(true) } else { None }), + resolc.polkadot ); set_if_some!( self.use_resolc.as_ref().map(|v| SolcReq::from(v.trim_start_matches("resolc:"))), diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 1591f288c9e03..e02d8ae975c5b 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -133,13 +133,28 @@ pub fn get_provider_builder(config: &Config) -> Result { /// Return an [ExecutorStrategy] via the config. pub fn get_executor_strategy(config: &Config) -> ExecutorStrategy { - // TODO: using resolc compiler: `[FAIL: EvmError: StackUnderflow] constructor() (gas: 0)` - if config.resolc.resolc_compile { - info!("using revive strategy"); - use revive_strategy::ReviveExecutorStrategyBuilder; - ExecutorStrategy::new_revive(config.resolc.resolc_startup) - } else { - ExecutorStrategy::new_evm() + use revive_strategy::{ReviveExecutorStrategyBuilder, ReviveRuntimeMode}; + + let polkadot = config.resolc.polkadot; + let resolc = config.resolc.resolc_compile; + + match (resolc, polkadot) { + // (default) - Standard Foundry EVM test + (false, false) => { + info!("using standard EVM strategy"); + ExecutorStrategy::new_evm() + } + // --resolc - Run PolkaVM backend on pallet-revive (PVM) + // --resolc --polkadot - Run PolkaVM backend on pallet-revive (PVM) + (true, false) | (true, true) => { + info!("using revive strategy with PVM backend"); + ExecutorStrategy::new_revive(ReviveRuntimeMode::Pvm) + } + // --polkadot (without resolc) - Run EVM backend on pallet-revive + (false, true) => { + info!("using revive strategy with EVM backend on pallet-revive"); + ExecutorStrategy::new_revive(ReviveRuntimeMode::Evm) + } } } diff --git a/crates/config/src/revive.rs b/crates/config/src/revive.rs index 4575dee5a02e7..d7cc3b93e3707 100644 --- a/crates/config/src/revive.rs +++ b/crates/config/src/revive.rs @@ -20,8 +20,8 @@ pub struct ResolcConfig { /// Enable compilation using resolc pub resolc_compile: bool, - /// Enable PVM mode at startup (independent of compilation) - pub resolc_startup: bool, + /// Use pallet-revive runtime backend + pub polkadot: bool, /// The resolc compiler pub resolc: Option, diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 7a21288d522b6..22810cd5f004b 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -113,6 +113,7 @@ serial_test.workspace = true mockall = "0.13" globset = "0.4" paste = "1.0" +rstest = "0.24" similar-asserts.workspace = true svm = { package = "svm-rs", version = "0.5", default-features = false, features = [ "rustls", diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 0b17cb7cc0dd7..aa5a2fecb037b 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -449,7 +449,7 @@ impl<'a> ContractRunner<'a> { f() } }; - let backend = if self.config.resolc.resolc_compile { + let backend = if self.config.resolc.resolc_compile || self.config.resolc.polkadot { Some(revive_strategy::Backend::get()) } else { None diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index ae2eeca9a9b9e..0e6ae580d6b44 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -1065,7 +1065,7 @@ path = "out" [profile.default.resolc] resolc_compile = false -resolc_startup = false +polkadot = false [fmt] line_length = 120 @@ -1345,7 +1345,7 @@ exclude = [] "script_execution_protection": true, "resolc": { "resolc_compile": false, - "resolc_startup": false, + "polkadot": false, "resolc": null, "optimizer_mode": null, "heap_size": null, diff --git a/crates/forge/tests/cli/revive_vm.rs b/crates/forge/tests/cli/revive_vm.rs index 9b5532e67c4a3..da592d7a77253 100644 --- a/crates/forge/tests/cli/revive_vm.rs +++ b/crates/forge/tests/cli/revive_vm.rs @@ -73,7 +73,7 @@ contract CounterTest is DSTest { .unwrap(); prj.update_config(|config| config.evm_version = EvmVersion::Cancun); - let res = cmd.args(["test", "--resolc", "-vvv", "--resolc-startup"]).assert(); + let res = cmd.args(["test", "--resolc", "-vvv", "--polkadot"]).assert(); res.stderr_eq(str![""]).stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -121,7 +121,7 @@ contract SetNonce is DSTest { .unwrap(); prj.update_config(|config| config.evm_version = EvmVersion::Cancun); - let res = cmd.args(["test", "--resolc", "-vvv", "--resolc-startup"]).assert_success(); + let res = cmd.args(["test", "--resolc", "-vvv", "--polkadot"]).assert_success(); res.stderr_eq(str![""]).stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -165,7 +165,7 @@ contract Roll is DSTest { ) .unwrap(); - let res = cmd.args(["test", "--resolc", "-vvv", "--resolc-startup"]).assert_success(); + let res = cmd.args(["test", "--resolc", "-vvv", "--polkadot"]).assert_success(); res.stderr_eq(str![""]).stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -209,7 +209,7 @@ contract Warp is DSTest { ) .unwrap(); - let res = cmd.args(["test", "--resolc", "-vvv", "--resolc-startup"]).assert_success(); + let res = cmd.args(["test", "--resolc", "-vvv", "--polkadot"]).assert_success(); res.stderr_eq(str![""]).stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -254,7 +254,7 @@ function test_Balance() public { ) .unwrap(); - let res = cmd.args(["test", "--resolc", "-vvv", "--resolc-startup"]).assert_success(); + let res = cmd.args(["test", "--resolc", "-vvv", "--polkadot"]).assert_success(); res.stderr_eq(str![""]).stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -313,7 +313,7 @@ function testFuzz_Load(uint256 x) public { ) .unwrap(); - let res = cmd.args(["test", "--resolc", "--resolc-startup", "-vvv"]).assert_success(); + let res = cmd.args(["test", "--resolc", "--polkadot", "-vvv"]).assert_success(); res.stderr_eq(str![""]).stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -331,6 +331,55 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); +// Test --polkadot flag: EVM execution on pallet-revive backend +forgetest!(polkadot_evm_backend, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.add_source( + "Counter.sol", + r#" +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; +contract Counter { + uint256 public number; + constructor(uint256 _initial) { + number = _initial; + } + function increment() public { + number = number + 1; + } + function getNumber() public view returns (uint256) { + return number; + } +} +"#, + ) + .unwrap(); + + prj.add_source( + "CounterTest.t.sol", + r#" +import "./test.sol"; +import {Counter} from "./Counter.sol"; +contract CounterTest is DSTest { + function test_PolkadotEVMBackend() public { + // This test runs EVM bytecode on pallet-revive EVM backend + Counter counter = new Counter(42); + assertEq(counter.getNumber(), 42); + counter.increment(); + assertEq(counter.getNumber(), 43); + counter.increment(); + assertEq(counter.getNumber(), 44); + } +} +"#, + ) + .unwrap(); + + // Test with --polkadot flag (EVM backend on pallet-revive) + cmd.args(["test", "--polkadot", "-vvv"]).assert_success(); +}); + forgetest!(trace_counter_test, |prj, cmd| { prj.insert_ds_test(); prj.insert_vm(); @@ -404,7 +453,7 @@ function test_expectRevert() public { .unwrap(); prj.update_config(|config| config.evm_version = EvmVersion::Cancun); - let res = cmd.args(["test", "--resolc", "--resolc-startup", "-vvvvv"]).assert_success(); + let res = cmd.args(["test", "--resolc", "--polkadot", "-vvvvv"]).assert_success(); res.stderr_eq("").stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -586,7 +635,7 @@ contract RecordTest is DSTest { .unwrap(); prj.update_config(|config| config.evm_version = EvmVersion::Cancun); - let res = cmd.args(["test", "--resolc", "--resolc-startup", "-vvvvv"]).assert_success(); + let res = cmd.args(["test", "--resolc", "--polkadot", "-vvvvv"]).assert_success(); res.stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -903,7 +952,7 @@ contract Emitterv2 { .unwrap(); prj.update_config(|config| config.evm_version = EvmVersion::Cancun); - let res = cmd.args(["test", "--resolc", "--resolc-startup", "-vvvvv"]).assert_success(); + let res = cmd.args(["test", "--resolc", "--polkadot", "-vvvvv"]).assert_success(); res.stderr_eq("").stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -1199,7 +1248,7 @@ forgetest!(record_accesses, |prj, cmd| { .unwrap(); prj.update_config(|config| config.evm_version = EvmVersion::Cancun); - let res = cmd.args(["test", "--resolc", "--resolc-startup", "-vvvvv"]).assert_success(); + let res = cmd.args(["test", "--resolc", "--polkadot", "-vvvvv"]).assert_success(); res.stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] diff --git a/crates/forge/tests/it/revive/cheat_store.rs b/crates/forge/tests/it/revive/cheat_store.rs index c4b6387965c45..f65c13d62c28b 100644 --- a/crates/forge/tests/it/revive/cheat_store.rs +++ b/crates/forge/tests/it/revive/cheat_store.rs @@ -1,26 +1,37 @@ use crate::{config::*, test_helpers::TEST_DATA_REVIVE}; use foundry_test_utils::Filter; +use revive_strategy::ReviveRuntimeMode; use revm::primitives::hardfork::SpecId; +use rstest::rstest; +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] #[tokio::test(flavor = "multi_thread")] -async fn test_store_works() { - let runner: forge::MultiContractRunner = TEST_DATA_REVIVE.runner_revive(); +async fn test_store_works(#[case] runtime_mode: ReviveRuntimeMode) { + let runner: forge::MultiContractRunner = TEST_DATA_REVIVE.runner_revive(runtime_mode); let filter = Filter::new("testStoreWorks", "Store", ".*/revive/.*"); TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await; } +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] #[tokio::test(flavor = "multi_thread")] -async fn test_store_fuzzed() { - let runner: forge::MultiContractRunner = TEST_DATA_REVIVE.runner_revive(); +async fn test_store_fuzzed(#[case] runtime_mode: ReviveRuntimeMode) { + let runner: forge::MultiContractRunner = TEST_DATA_REVIVE.runner_revive(runtime_mode); let filter = Filter::new("testStoreFuzzed", "Store", ".*/revive/.*"); TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await; } +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] #[tokio::test(flavor = "multi_thread")] -async fn test_store_not_available_on_precompiles() { - let runner: forge::MultiContractRunner = TEST_DATA_REVIVE.runner_revive(); +async fn test_store_not_available_on_precompiles(#[case] runtime_mode: ReviveRuntimeMode) { + let runner: forge::MultiContractRunner = TEST_DATA_REVIVE.runner_revive(runtime_mode); let filter = Filter::new("testStoreNotAvailableOnPrecompiles", "Store", ".*/revive/.*"); TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await; diff --git a/crates/forge/tests/it/revive/migration.rs b/crates/forge/tests/it/revive/migration.rs index 43b9485cdaa33..cef92a26815bd 100644 --- a/crates/forge/tests/it/revive/migration.rs +++ b/crates/forge/tests/it/revive/migration.rs @@ -2,56 +2,69 @@ use crate::{config::*, test_helpers::TEST_DATA_REVIVE}; use foundry_test_utils::Filter; +use revive_strategy::ReviveRuntimeMode; use revm::primitives::hardfork::SpecId; +use rstest::rstest; +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] #[tokio::test(flavor = "multi_thread")] -async fn test_revive_balance_migration() { - let runner = TEST_DATA_REVIVE.runner_revive(); +async fn test_revive_balance_migration(#[case] runtime_mode: ReviveRuntimeMode) { + let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); let filter = Filter::new("testBalanceMigration", "EvmReviveMigrationTest", ".*/revive/.*"); - TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] #[tokio::test(flavor = "multi_thread")] -async fn test_revive_nonce_migration() { - let runner = TEST_DATA_REVIVE.runner_revive(); +async fn test_revive_nonce_migration(#[case] runtime_mode: ReviveRuntimeMode) { + let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); let filter = Filter::new("testNonceMigration", "EvmReviveMigrationTest", ".*/revive/.*"); - TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } -// Enable it after new pallet-revive is being used -// #[tokio::test(flavor = "multi_thread")] -// async fn test_revive_precision_preservation() { -// let runner = TEST_DATA_REVIVE.runner_revive(); -// let filter = Filter::new("testPrecisionPreservation", "EvmReviveMigrationTest", -// ".*/revive/.*"); -// -// TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; -// } - +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] #[tokio::test(flavor = "multi_thread")] -async fn test_revive_bytecode_migration() { - let runner = TEST_DATA_REVIVE.runner_revive(); +async fn test_revive_bytecode_migration(#[case] runtime_mode: ReviveRuntimeMode) { + let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); let filter = Filter::new("testBytecodeMigrationToEvm", "EvmReviveMigrationTest", ".*/revive/.*"); - TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +// TODO: Add Evm test when pallet-revive will allow for Evm bytecode upload #[tokio::test(flavor = "multi_thread")] -async fn test_evm_bytecode_migration() { - let runner = TEST_DATA_REVIVE.runner_revive(); +async fn test_revive_bytecode_migration_to_revive(#[case] runtime_mode: ReviveRuntimeMode) { + let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); let filter = Filter::new("testBytecodeMigrationToRevive", "EvmReviveMigrationTest", ".*/revive/.*"); - TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } +// Enable it after new pallet-revive is being used +// #[rstest] +// #[case::pvm(ReviveRuntimeMode::Pvm)] +// #[case::evm(ReviveRuntimeMode::Evm)] // #[tokio::test(flavor = "multi_thread")] -// async fn test_revive_timestamp_migration() { -// let runner = TEST_DATA_REVIVE.runner_revive(); -// let filter = Filter::new("testTimestampMigration", "EvmReviveMigrationTest", ".*/revive/.*"); +// async fn test_revive_precision_preservation(#[case] runtime_mode: ReviveRuntimeMode) { +// let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); +// let filter = Filter::new("testPrecisionPreservation", "EvmReviveMigrationTest", +// ".*/revive/.*"); TestConfig::with_filter(runner, +// filter).spec_id(SpecId::SHANGHAI).run().await; } +// #[rstest] +// #[case::pvm(ReviveRuntimeMode::Pvm)] +// #[case::evm(ReviveRuntimeMode::Evm)] +// #[tokio::test(flavor = "multi_thread")] +// async fn test_revive_timestamp_migration(#[case] runtime_mode: ReviveRuntimeMode) { +// let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); +// let filter = Filter::new("testTimestampMigration", "EvmReviveMigrationTest", ".*/revive/.*"); // TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; // } diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 4997b2d816451..fc1e2b329c057 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -19,7 +19,7 @@ use foundry_test_utils::{ fd_lock, init_tracing, rpc::{next_http_archive_rpc_url, next_rpc_endpoint}, }; -use revive_strategy::ReviveExecutorStrategyBuilder; +use revive_strategy::{ReviveExecutorStrategyBuilder, ReviveRuntimeMode}; use revm::primitives::hardfork::SpecId; use std::{ env, fmt, @@ -326,7 +326,7 @@ impl ForgeTestData { } /// Builds a runner with revive strategy for polkadot/substrate testing - pub fn runner_revive(&self) -> MultiContractRunner { + pub fn runner_revive(&self, runtime_mode: ReviveRuntimeMode) -> MultiContractRunner { let mut config = (*self.config).clone(); config.rpc_endpoints = rpc_endpoints(); config.allow_paths.push(manifest_root().to_path_buf()); @@ -345,7 +345,7 @@ impl ForgeTestData { let root = self.project.root(); builder.config = config.clone(); - let mut strategy = ExecutorStrategy::new_revive(true); + let mut strategy = ExecutorStrategy::new_revive(runtime_mode); strategy .runner diff --git a/crates/revive-strategy/src/cheatcodes/mod.rs b/crates/revive-strategy/src/cheatcodes/mod.rs index 1d6472c566b24..746472580c4b2 100644 --- a/crates/revive-strategy/src/cheatcodes/mod.rs +++ b/crates/revive-strategy/src/cheatcodes/mod.rs @@ -50,28 +50,34 @@ use revm::{ state::Bytecode, }; pub trait PvmCheatcodeInspectorStrategyBuilder { - fn new_pvm(dual_compiled_contracts: DualCompiledContracts, resolc_startup: bool) -> Self; + fn new_pvm( + dual_compiled_contracts: DualCompiledContracts, + runtime_mode: crate::ReviveRuntimeMode, + ) -> Self; } impl PvmCheatcodeInspectorStrategyBuilder for CheatcodeInspectorStrategy { // Creates a new PVM strategy - fn new_pvm(dual_compiled_contracts: DualCompiledContracts, resolc_startup: bool) -> Self { + fn new_pvm( + dual_compiled_contracts: DualCompiledContracts, + runtime_mode: crate::ReviveRuntimeMode, + ) -> Self { Self { runner: &PvmCheatcodeInspectorStrategyRunner, context: Box::new(PvmCheatcodeInspectorStrategyContext::new( dual_compiled_contracts, - resolc_startup, + runtime_mode, )), } } } -/// Controls the automatic migration to PVM mode during test execution. +/// Controls the automatic migration to pallet-revive during test execution. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum PvmStartupMigration { /// Defer database migration to a later execution point. /// This is the initial state - waiting for the test contract to be deployed. Defer, - /// Allow database migration to PVM. + /// Allow database migration to pallet-revive (EVM or PVM mode). /// Set by `base_contract_deployed()` when the test contract is deployed. #[default] Allow, @@ -99,24 +105,28 @@ impl PvmStartupMigration { /// PVM-specific strategy context. #[derive(Debug, Default, Clone)] pub struct PvmCheatcodeInspectorStrategyContext { - /// Whether we're using PVM mode + /// Whether we're currently using pallet-revive (migrated from REVM) pub using_pvm: bool, - /// Controls automatic migration to PVM mode + /// Controls automatic migration to pallet-revive pub pvm_startup_migration: PvmStartupMigration, pub dual_compiled_contracts: DualCompiledContracts, + /// Runtime backend mode when using pallet-revive (PVM or EVM) + pub runtime_mode: crate::ReviveRuntimeMode, pub remove_recorded_access_at: Option, } impl PvmCheatcodeInspectorStrategyContext { - pub fn new(dual_compiled_contracts: DualCompiledContracts, resolc_startup: bool) -> Self { + pub fn new( + dual_compiled_contracts: DualCompiledContracts, + runtime_mode: crate::ReviveRuntimeMode, + ) -> Self { Self { - using_pvm: false, // Start in EVM mode by default - pvm_startup_migration: if resolc_startup { - PvmStartupMigration::Defer // Will be set to Allow when test contract deploys - } else { - PvmStartupMigration::Done // Disabled - never migrate - }, + // Start in REVM mode by default + using_pvm: false, + // Will be set to Allow when test contract deploys + pvm_startup_migration: PvmStartupMigration::Defer, dual_compiled_contracts, + runtime_mode, remove_recorded_access_at: None, } } @@ -321,7 +331,7 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner { let ctx: &mut PvmCheatcodeInspectorStrategyContext = get_context_ref_mut(ccx.state.strategy.context.as_mut()); if *enabled { - select_pvm(ctx, ccx.ecx); + select_revive(ctx, ccx.ecx); } else { select_evm(ctx, ccx.ecx); } @@ -484,10 +494,10 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner { let ctx = get_context_ref_mut(ctx); if ctx.pvm_startup_migration.is_allowed() && !ctx.using_pvm { - tracing::info!("startup PVM migration initiated"); - select_pvm(ctx, ecx); + tracing::info!("startup pallet-revive migration initiated"); + select_revive(ctx, ecx); ctx.pvm_startup_migration.done(); - tracing::info!("startup PVM migration completed"); + tracing::info!("startup pallet-revive migration completed"); } } @@ -534,13 +544,13 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner { } } -fn select_pvm(ctx: &mut PvmCheatcodeInspectorStrategyContext, data: Ecx<'_, '_, '_>) { +fn select_revive(ctx: &mut PvmCheatcodeInspectorStrategyContext, data: Ecx<'_, '_, '_>) { if ctx.using_pvm { - tracing::info!("already in PVM"); + tracing::info!("already using pallet-revive"); return; } - tracing::info!("switching to PVM"); + tracing::info!("switching to pallet-revive ({} mode)", ctx.runtime_mode); ctx.using_pvm = true; let block_number = data.block.number; @@ -600,29 +610,45 @@ fn select_pvm(ctx: &mut PvmCheatcodeInspectorStrategyContext, data: Ecx<'_, '_, let account_h160 = H160::from_slice(address.as_slice()); - // Skip if contract already exists in PVM + // Skip if contract already exists in pallet-revive if AccountInfo::::load_contract(&account_h160).is_none() { - if let Some(pvm_bytecode) = ctx.dual_compiled_contracts - .find_by_evm_deployed_bytecode_with_immutables(bytecode.original_byte_slice()) - .and_then(|(_, contract)| { - contract.resolc_bytecode.as_bytes() - }) - { + // Determine which bytecode to upload based on runtime mode + let bytecode_to_upload = ctx.dual_compiled_contracts + .find_by_evm_deployed_bytecode_with_immutables(bytecode.original_byte_slice()) + .and_then(|(_, contract)| { + match ctx.runtime_mode { + crate::ReviveRuntimeMode::Pvm => contract.resolc_bytecode.as_bytes().map(|b| b.to_vec()), + crate::ReviveRuntimeMode::Evm => None, + // TODO: We do not have method to upload the EVM bytecode to pallet-revive + //contract.evm_bytecode.as_bytes().map(|b| b.to_vec()) + } + }); + + if let Some(code_bytes) = bytecode_to_upload { let origin = OriginFor::::signed(Pallet::::account_id()); - let code_hash = Pallet::::bare_upload_code( + let upload_result = Pallet::::bare_upload_code( origin, - pvm_bytecode.to_vec(), + code_bytes.clone(), BalanceOf::::MAX, - ) - .ok() - .map(|upload_result| upload_result.code_hash) - .expect("Failed to upload PVM bytecode"); - - let contract_info = ContractInfo::::new(&account_h160, nonce as u32, code_hash) - .expect("Failed to create contract info"); - - AccountInfo::::insert_contract(&account_h160, contract_info); + ); + match upload_result { + Ok(result) => { + let code_hash = result.code_hash; + let contract_info = ContractInfo::::new(&account_h160, nonce as u32, code_hash) + .expect("Failed to create contract info"); + AccountInfo::::insert_contract(&account_h160, contract_info); + } + Err(err) => { + tracing::warn!( + address = ?address, + runtime_mode = ?ctx.runtime_mode, + bytecode_len = code_bytes.len(), + error = ?err, + "Failed to upload bytecode to pallet-revive, skipping migration" + ); + } + } } else { tracing::info!( address = ?address, @@ -638,11 +664,11 @@ fn select_pvm(ctx: &mut PvmCheatcodeInspectorStrategyContext, data: Ecx<'_, '_, fn select_evm(ctx: &mut PvmCheatcodeInspectorStrategyContext, data: Ecx<'_, '_, '_>) { if !ctx.using_pvm { - tracing::info!("already in EVM"); + tracing::info!("already using REVM"); return; } - tracing::info!("switching to EVM"); + tracing::info!("switching from pallet-revive back to REVM"); ctx.using_pvm = false; execute_with_externalities(|externalities| { @@ -669,18 +695,31 @@ fn select_evm(ctx: &mut PvmCheatcodeInspectorStrategyContext, data: Ecx<'_, '_, && let Some(info) = AccountInfo::::load_contract(&account_evm) { let hash = hex::encode(info.code_hash); - if let Some((code_hash, bytecode)) = ctx - .dual_compiled_contracts - .find_by_resolc_bytecode_hash(hash) - .and_then(|(_, contract)| { - contract.evm_deployed_bytecode.as_bytes().map(|evm_bytecode| { - ( - contract.evm_bytecode_hash, - Bytecode::new_raw(evm_bytecode.clone()), - ) - }) - }) - { + + if let Some((code_hash, bytecode)) = match ctx.runtime_mode { + crate::ReviveRuntimeMode::Pvm => ctx + .dual_compiled_contracts + .find_by_resolc_bytecode_hash(hash) + .and_then(|(_, contract)| { + contract.evm_deployed_bytecode.as_bytes().map(|evm_bytecode| { + ( + contract.evm_bytecode_hash, + Bytecode::new_raw(evm_bytecode.clone()), + ) + }) + }), + crate::ReviveRuntimeMode::Evm => ctx + .dual_compiled_contracts + .find_by_evm_bytecode_hash(hash) + .and_then(|(_, contract)| { + contract.evm_deployed_bytecode.as_bytes().map(|evm_bytecode| { + ( + contract.evm_bytecode_hash, + Bytecode::new_raw(evm_bytecode.clone()), + ) + }) + }), + } { account.info.code_hash = code_hash; account.info.code = Some(bytecode); } else { @@ -738,15 +777,27 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector } let init_code = input.init_code(); - tracing::info!("running create in PVM"); - let find_contract = ctx - .dual_compiled_contracts - .find_bytecode(&init_code.0) - .unwrap_or_else(|| panic!("failed finding contract for {init_code:?}")); + // Determine which bytecode to use based on runtime mode + let (code_bytes, constructor_args) = match ctx.runtime_mode { + crate::ReviveRuntimeMode::Pvm => { + // PVM mode: use resolc (PVM) bytecode + tracing::info!("running create in PVM mode with PVM bytecode"); + let find_contract = ctx + .dual_compiled_contracts + .find_bytecode(&init_code.0) + .unwrap_or_else(|| panic!("failed finding contract for {init_code:?}")); + let constructor_args = find_contract.constructor_args(); + let contract = find_contract.contract(); + (contract.resolc_bytecode.as_bytes().unwrap().to_vec(), constructor_args.to_vec()) + } + crate::ReviveRuntimeMode::Evm => { + // EVM mode: use EVM bytecode directly + tracing::info!("running create in EVM mode with EVM bytecode"); + (init_code.0.to_vec(), vec![]) + } + }; - let constructor_args = find_contract.constructor_args(); - let contract = find_contract.contract().clone(); let mut tracer = Tracer::new(true); let res = execute_with_externalities(|externalities| { externalities.execute_with(|| { @@ -756,8 +807,8 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector )); let evm_value = sp_core::U256::from_little_endian(&input.value().as_le_bytes()); - let code = Code::Upload(contract.resolc_bytecode.as_bytes().unwrap().to_vec()); - let data = constructor_args.to_vec(); + let code = Code::Upload(code_bytes.clone()); + let data = constructor_args; let salt = match input.scheme() { Some(CreateScheme::Create2 { salt }) => Some( salt.as_limbs() @@ -807,7 +858,7 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector CreateOutcome { result: InterpreterResult { result: InstructionResult::Return, - output: contract.resolc_bytecode.as_bytes().unwrap().to_owned(), + output: code_bytes.into(), gas, }, address: Some(Address::from_slice(result.addr.as_bytes())), @@ -857,13 +908,13 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector .unwrap_or_default() { tracing::info!( - "running call in EVM, instead of PVM (Test Contract) {:#?}", + "running call in EVM, instead of pallet-revive (Test Contract) {:#?}", call.bytecode_address ); return None; } - tracing::info!("running call in PVM {:#?}", call); + tracing::info!("running call on pallet-revive with {} {:#?}", ctx.runtime_mode, call); let mut tracer = Tracer::new(true); let res = execute_with_externalities(|externalities| { externalities.execute_with(|| { diff --git a/crates/revive-strategy/src/executor/context.rs b/crates/revive-strategy/src/executor/context.rs index 909086c771f29..66af906dacfe4 100644 --- a/crates/revive-strategy/src/executor/context.rs +++ b/crates/revive-strategy/src/executor/context.rs @@ -3,11 +3,13 @@ use foundry_compilers::{ }; use foundry_evm::executors::ExecutorStrategyContext; +use crate::ReviveRuntimeMode; + /// Defines the context for [crate::ReviveExecutorStrategyRunner]. #[derive(Debug, Default, Clone)] pub struct ReviveExecutorStrategyContext { - /// Whether to start in PVM mode (from config) - pub(crate) resolc_startup: bool, + /// Runtime backend mode (PVM or EVM on Polkadot) + pub(crate) runtime_mode: ReviveRuntimeMode, /// Dual compiled contracts. pub(crate) dual_compiled_contracts: DualCompiledContracts, /// Compilation output. @@ -15,8 +17,8 @@ pub struct ReviveExecutorStrategyContext { } impl ReviveExecutorStrategyContext { - pub fn new(resolc_startup: bool) -> Self { - Self { resolc_startup, ..Default::default() } + pub fn new(runtime_mode: ReviveRuntimeMode) -> Self { + Self { runtime_mode, ..Default::default() } } } diff --git a/crates/revive-strategy/src/executor/runner.rs b/crates/revive-strategy/src/executor/runner.rs index 9bdcd77dafc7f..b2be9b29fdabd 100644 --- a/crates/revive-strategy/src/executor/runner.rs +++ b/crates/revive-strategy/src/executor/runner.rs @@ -45,7 +45,7 @@ impl ExecutorStrategyRunner for ReviveExecutorStrategyRunner { ctx: &dyn ExecutorStrategyContext, ) -> foundry_cheatcodes::CheatcodesStrategy { let ctx = get_context_ref(ctx); - CheatcodeInspectorStrategy::new_pvm(ctx.dual_compiled_contracts.clone(), ctx.resolc_startup) + CheatcodeInspectorStrategy::new_pvm(ctx.dual_compiled_contracts.clone(), ctx.runtime_mode) } /// Sets the balance of an account. diff --git a/crates/revive-strategy/src/lib.rs b/crates/revive-strategy/src/lib.rs index 82a1e7e845e19..711040cd4a417 100644 --- a/crates/revive-strategy/src/lib.rs +++ b/crates/revive-strategy/src/lib.rs @@ -4,6 +4,8 @@ //! in a Polkadot environment. //! //! It is heavily inspired from +use std::fmt::Display; + use foundry_evm::executors::ExecutorStrategy; use polkadot_sdk::{ sp_core::{self, H160}, @@ -23,17 +25,36 @@ mod tracing; pub use cheatcodes::PvmStartupMigration; +/// Runtime backend mode for pallet-revive +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum ReviveRuntimeMode { + /// Run PolkaVM backend on pallet-revive (PVM mode) + Pvm, + #[default] + /// Run EVM backend on pallet-revive (EVM mode on Polkadot) + Evm, +} + +impl Display for ReviveRuntimeMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Pvm => write!(f, "PVM"), + Self::Evm => write!(f, "EVM"), + } + } +} + /// Create Revive strategy for [ExecutorStrategy]. pub trait ReviveExecutorStrategyBuilder { /// Create new revive strategy. - fn new_revive(resolc_startup: bool) -> Self; + fn new_revive(runtime_mode: ReviveRuntimeMode) -> Self; } impl ReviveExecutorStrategyBuilder for ExecutorStrategy { - fn new_revive(resolc_startup: bool) -> Self { + fn new_revive(runtime_mode: ReviveRuntimeMode) -> Self { Self { runner: Box::leak(Box::new(ReviveExecutorStrategyRunner::new())), - context: Box::new(ReviveExecutorStrategyContext::new(resolc_startup)), + context: Box::new(ReviveExecutorStrategyContext::new(runtime_mode)), } } } diff --git a/deny.toml b/deny.toml index eb4831947c607..5555bc9f57f44 100644 --- a/deny.toml +++ b/deny.toml @@ -27,6 +27,9 @@ ignore = [ "RUSTSEC-2024-0384", # https://rustsec.org/advisories/RUSTSEC-2025-0057 `fxhash` is unmaintained "RUSTSEC-2025-0057", + # https://rustsec.org/advisories/RUSTSEC-2025-0073 DoS vulnerability on `alloy_dyn_abi::TypedData` hashing + # Cannot upgrade to 1.4.1 without breaking revm compatibility (requires revm upgrade) + "RUSTSEC-2025-0073", ] # This section is considered when running `cargo deny check bans`.