diff --git a/ethjson/src/spec/spec.rs b/ethjson/src/spec/spec.rs index 534d556..c882721 100644 --- a/ethjson/src/spec/spec.rs +++ b/ethjson/src/spec/spec.rs @@ -63,6 +63,17 @@ pub enum ForkSpec { ConstantinopleFixToIstanbulAt5, } +impl ForkSpec { + /// Returns true if the fork is at or after the merge. + pub fn is_eth2(&self) -> bool { + // NOTE: Include new forks in this match arm. + matches!( + *self, + ForkSpec::London | ForkSpec::Merge | ForkSpec::Shanghai + ) + } +} + /// Spec deserialization. #[derive(Debug, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] diff --git a/ethjson/src/vm.rs b/ethjson/src/vm.rs index 4578145..ca23153 100644 --- a/ethjson/src/vm.rs +++ b/ethjson/src/vm.rs @@ -121,6 +121,10 @@ pub struct Env { #[serde(rename = "currentBaseFee")] #[serde(default)] pub block_base_fee_per_gas: Uint, + /// Pre-seeded random value for testing + #[serde(rename = "currentRandom")] + #[serde(default)] + pub random: Option, } #[cfg(test)] @@ -145,7 +149,8 @@ mod tests { "currentDifficulty" : "0x0100", "currentGasLimit" : "0x0f4240", "currentNumber" : "0x00", - "currentTimestamp" : "0x01" + "currentTimestamp" : "0x01", + "currentRandom" : "0x01" }, "exec" : { "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", @@ -192,7 +197,8 @@ mod tests { gas_limit: Uint(0x0f4240.into()), number: Uint(0.into()), timestamp: Uint(1.into()), - block_base_fee_per_gas: Uint(0.into()) + block_base_fee_per_gas: Uint(0.into()), + random: Some(Uint(1.into())), } ); assert_eq!( diff --git a/jsontests/Cargo.toml b/jsontests/Cargo.toml index 6f3c60f..10dbd0c 100644 --- a/jsontests/Cargo.toml +++ b/jsontests/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["no_std", "ethereum"] edition = "2021" [dependencies] -ethereum = "^0.14" +ethereum = "0.15.0" scale-info = { version = "^2.9", features = ["derive"] } codec = { package = "parity-scale-codec", version = "^3.0" } serde = { version = "^1.0", features = ["derive"] } diff --git a/jsontests/src/state.rs b/jsontests/src/state.rs index 19bbbd3..5f04e26 100644 --- a/jsontests/src/state.rs +++ b/jsontests/src/state.rs @@ -1,14 +1,14 @@ use crate::mock::{deposit, get_state, new_test_ext, setup_state, withdraw, Runtime, EVM}; use crate::utils::*; use ethjson::spec::ForkSpec; -use evm_utility::evm::{backend::MemoryAccount, Config}; +use evm_utility::evm::{backend::MemoryAccount, executor::stack::PrecompileFn, Config}; use libsecp256k1::SecretKey; use module_evm::{ precompiles::{ Blake2F, Bn128Add, Bn128Mul, Bn128Pairing, ECRecover, Identity, IstanbulModexp, Modexp, Precompile, Ripemd160, Sha256, }, - runner::state::{PrecompileFn, StackState}, + runner::state::StackState, StackExecutor, StackSubstateMetadata, SubstrateStackState, Vicinity, }; use primitives::convert_decimals_to_evm; @@ -77,6 +77,23 @@ impl Test { return None; } + let block_randomness = if spec.is_eth2() { + self.0.env.random.map(|r| { + // Convert between U256 and H256. U256 is in little-endian but since H256 is just + // a string-like byte array, it's big endian (MSB is the first element of the array). + // + // Byte order here is important because this opcode has the same value as DIFFICULTY + // (0x44), and so for older forks of Ethereum, the threshold value of 2^64 is used to + // distinguish between the two: if it's below, the value corresponds to the DIFFICULTY + // opcode, otherwise to the PREVRANDAO opcode. + let mut buf = [0u8; 32]; + r.0.to_big_endian(&mut buf); + H256(buf) + }) + } else { + None + }; + Some(Vicinity { gas_price, origin: self.unwrap_caller(), @@ -88,6 +105,7 @@ impl Test { block_gas_limit: Some(self.0.env.gas_limit.into()), // chain_id: U256::one(), block_base_fee_per_gas: Some(block_base_fee_per_gas), + block_randomness, }) } } @@ -99,24 +117,40 @@ impl JsonPrecompile { match spec { ForkSpec::Istanbul => { let mut map = BTreeMap::::new(); - map.insert(H160::from_low_u64_be(1), ::execute); - map.insert(H160::from_low_u64_be(2), ::execute); - map.insert(H160::from_low_u64_be(3), ::execute); - map.insert(H160::from_low_u64_be(4), ::execute); - map.insert(H160::from_low_u64_be(5), IstanbulModexp::execute); - map.insert(H160::from_low_u64_be(6), Bn128Add::execute); - map.insert(H160::from_low_u64_be(7), Bn128Mul::execute); - map.insert(H160::from_low_u64_be(8), Bn128Pairing::execute); - map.insert(H160::from_low_u64_be(9), Blake2F::execute); + map.insert( + H160::from_low_u64_be(1), + ::execute_ext, + ); + map.insert( + H160::from_low_u64_be(2), + ::execute_ext, + ); + map.insert( + H160::from_low_u64_be(3), + ::execute_ext, + ); + map.insert( + H160::from_low_u64_be(4), + ::execute_ext, + ); + map.insert(H160::from_low_u64_be(5), IstanbulModexp::execute_ext); + map.insert(H160::from_low_u64_be(6), Bn128Add::execute_ext); + map.insert(H160::from_low_u64_be(7), Bn128Mul::execute_ext); + map.insert(H160::from_low_u64_be(8), Bn128Pairing::execute_ext); + map.insert(H160::from_low_u64_be(9), Blake2F::execute_ext); Some(map) } ForkSpec::Berlin => { let mut map = Self::precompile(&ForkSpec::Istanbul).unwrap(); - map.insert(H160::from_low_u64_be(5), Modexp::execute); + map.insert(H160::from_low_u64_be(5), Modexp::execute_ext); Some(map) } // precompiles for London and Berlin are the same ForkSpec::London => Self::precompile(&ForkSpec::Berlin), + // precompiles for Merge and Berlin are the same + ForkSpec::Merge => Self::precompile(&ForkSpec::Berlin), + // precompiles for Shanghai and Berlin are the same + ForkSpec::Shanghai => Self::precompile(&ForkSpec::Berlin), _ => None, } } @@ -173,8 +207,10 @@ fn test_run(name: &str, test: Test) { ethjson::spec::ForkSpec::Istanbul => (Config::istanbul(), true), ethjson::spec::ForkSpec::Berlin => (Config::berlin(), true), ethjson::spec::ForkSpec::London => (Config::london(), true), + ethjson::spec::ForkSpec::Merge => (Config::merge(), true), + ethjson::spec::ForkSpec::Shanghai => (Config::shanghai(), true), _spec => { - println!("Skip spec {:?}", spec); + println!("Skip spec {spec:?}"); return; } }; @@ -291,8 +327,9 @@ fn test_run(name: &str, test: Test) { } let actual_fee = executor.fee(vicinity.gas_price).saturated_into::(); - let miner_reward = if let ForkSpec::London = spec { - // see EIP-1559 + // Forks after London burn miner rewards and thus have different gas fee + // calculation (see EIP-1559) + let miner_reward = if spec.is_eth2() { let max_priority_fee_per_gas = test.0.transaction.max_priority_fee_per_gas(); let max_fee_per_gas = test.0.transaction.max_fee_per_gas(); diff --git a/jsontests/src/vm.rs b/jsontests/src/vm.rs index 5346a64..4c53c78 100644 --- a/jsontests/src/vm.rs +++ b/jsontests/src/vm.rs @@ -4,7 +4,7 @@ use evm_utility::evm::backend::MemoryAccount; use evm_utility::evm::Config; use module_evm::{StackExecutor, StackSubstateMetadata, SubstrateStackState, Vicinity}; use serde::Deserialize; -use sp_core::H160; +use sp_core::{H160, H256}; use std::collections::BTreeMap; use std::rc::Rc; @@ -17,6 +17,19 @@ impl Test { } pub fn unwrap_to_vicinity(&self) -> Vicinity { + let block_randomness = self.0.env.random.map(|r| { + // Convert between U256 and H256. U256 is in little-endian but since H256 is just + // a string-like byte array, it's big endian (MSB is the first element of the array). + // + // Byte order here is important because this opcode has the same value as DIFFICULTY + // (0x44), and so for older forks of Ethereum, the threshold value of 2^64 is used to + // distinguish between the two: if it's below, the value corresponds to the DIFFICULTY + // opcode, otherwise to the PREVRANDAO opcode. + let mut buf = [0u8; 32]; + r.0.to_big_endian(&mut buf); + H256(buf) + }); + Vicinity { gas_price: self.0.transaction.gas_price.into(), origin: self.0.transaction.origin.into(), @@ -24,6 +37,7 @@ impl Test { block_difficulty: Some(self.0.env.difficulty.into()), block_coinbase: Some(self.0.env.author.into()), block_base_fee_per_gas: Some(self.0.env.block_base_fee_per_gas.0), + block_randomness, } } @@ -78,7 +92,13 @@ pub fn test(name: &str, test: Test) { let code = test.unwrap_to_code(); let data = test.unwrap_to_data(); let context = test.unwrap_to_context(); - let mut runtime = module_evm::evm::Runtime::new(code, data, context, &config); + let mut runtime = module_evm::evm::Runtime::new( + code, + data, + context, + config.stack_limit, + config.memory_limit, + ); let reason = executor.execute(&mut runtime); diff --git a/jsontests/tests/state.rs b/jsontests/tests/state.rs index 6fa60e6..afbbf1d 100644 --- a/jsontests/tests/state.rs +++ b/jsontests/tests/state.rs @@ -30,11 +30,13 @@ pub fn run(dir: &str) { continue; } - let file = File::open(path).expect("Open file failed"); + let file = File::open(&path).expect("Open file failed"); let reader = BufReader::new(file); - let coll = serde_json::from_reader::<_, HashMap>(reader) - .expect("Parse test cases failed"); + let coll: HashMap = serde_json::from_reader(reader) + .unwrap_or_else(|e| { + panic!("Parsing test case {:?} failed: {:?}", path, e); + }); for (name, test) in coll { statetests::test(&name, test);