From ae007756a6049abf11281da8c70a91bf3e645006 Mon Sep 17 00:00:00 2001 From: Lucas Delgado <128101590+LucasUTNFRD@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:15:00 -0300 Subject: [PATCH] fix(levm): pre_merge_EFTest (#1663) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Motivation** This pull request exists to implement and refine the modular exponentiation (modexp) precompile functionality as specified in EIP-198. The goal is to ensure compatibility with Ethereum Foundation (EF) tests across various forks and pass pre-merge validation for the functionality. **Description** To implement EIP-198, the following changes were made: • EIP-198: Added support for the ModExp precompile to pass tests from the eip198_modexp_precompile test directory. Current progress:140/144 (97.22%). • EIP-2028: Adjusted calldata gas cost, reducing the gas per non-zero byte from 68 to 16 while keeping the gas cost of zero bytes unchanged. This change was necessary to align with the requirements of the precompile implementation. • EIP-2929: Updated gas cost for CALL opcode to 700 to address compatibility issues for Istanbul tests. This hardcoded adjustment was required to ensure the precompile functions correctly in forks such as Istanbul. To monitor test progress, run the following command: ```Bash cargo test --release -p ef_tests-levm --test ef_tests_levm -- --tests modexp.json ``` --------- Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> --- crates/vm/levm/src/gas_cost.rs | 182 ++++++++++++++----- crates/vm/levm/src/opcode_handlers/system.rs | 1 + crates/vm/levm/src/precompiles.rs | 53 ++++-- crates/vm/levm/src/vm.rs | 6 +- crates/vm/levm/tests/tests.rs | 16 +- 5 files changed, 191 insertions(+), 67 deletions(-) diff --git a/crates/vm/levm/src/gas_cost.rs b/crates/vm/levm/src/gas_cost.rs index b54449c818..6059a14940 100644 --- a/crates/vm/levm/src/gas_cost.rs +++ b/crates/vm/levm/src/gas_cost.rs @@ -125,6 +125,7 @@ pub const EXTCODECOPY_WARM_DYNAMIC: u64 = DEFAULT_WARM_DYNAMIC; pub const CALL_STATIC: u64 = DEFAULT_STATIC; pub const CALL_COLD_DYNAMIC: u64 = DEFAULT_COLD_DYNAMIC; pub const CALL_WARM_DYNAMIC: u64 = DEFAULT_WARM_DYNAMIC; +pub const CALL_PRE_BERLIN: u64 = 700; pub const CALL_POSITIVE_VALUE: u64 = 9000; pub const CALL_POSITIVE_VALUE_STIPEND: u64 = 2300; pub const CALL_TO_EMPTY_ACCOUNT: u64 = 25000; @@ -158,6 +159,7 @@ pub const CREATE_BASE_COST: u64 = 32000; // Calldata costs pub const CALLDATA_COST_ZERO_BYTE: u64 = 4; pub const CALLDATA_COST_NON_ZERO_BYTE: u64 = 16; +pub const CALLDATA_COST_NON_ZERO_BYTE_PRE_ISTANBUL: u64 = 68; // Blob gas costs pub const BLOB_GAS_PER_BLOB: u64 = 131072; @@ -182,6 +184,8 @@ pub const MODEXP_STATIC_COST: u64 = 200; pub const MODEXP_DYNAMIC_BASE: u64 = 200; pub const MODEXP_DYNAMIC_QUOTIENT: u64 = 3; +pub const MODEXP_DYNAMIC_QUOTIENT_PRE_BERLIN: u64 = 20; + pub const ECADD_COST: u64 = 150; pub const ECMUL_COST: u64 = 6000; @@ -524,19 +528,26 @@ pub fn selfdestruct( Ok(gas_cost) } -pub fn tx_calldata(calldata: &Bytes) -> Result { +pub fn tx_calldata(calldata: &Bytes, spec_id: SpecId) -> Result { // This cost applies both for call and create // 4 gas for each zero byte in the transaction data 16 gas for each non-zero byte in the transaction. let mut calldata_cost: u64 = 0; for byte in calldata { - if *byte != 0 { - calldata_cost = calldata_cost - .checked_add(CALLDATA_COST_NON_ZERO_BYTE) - .ok_or(OutOfGasError::GasUsedOverflow)?; + calldata_cost = if *byte != 0 { + if spec_id >= SpecId::ISTANBUL { + calldata_cost + .checked_add(CALLDATA_COST_NON_ZERO_BYTE) + .ok_or(OutOfGasError::GasUsedOverflow)? + } else { + // EIP-2028 + calldata_cost + .checked_add(CALLDATA_COST_NON_ZERO_BYTE_PRE_ISTANBUL) + .ok_or(OutOfGasError::GasUsedOverflow)? + } } else { - calldata_cost = calldata_cost + calldata_cost .checked_add(CALLDATA_COST_ZERO_BYTE) - .ok_or(OutOfGasError::GasUsedOverflow)?; + .ok_or(OutOfGasError::GasUsedOverflow)? } } Ok(calldata_cost) @@ -629,6 +640,7 @@ pub fn extcodehash(address_was_cold: bool) -> Result { ) } +#[allow(clippy::too_many_arguments)] pub fn call( new_memory_size: usize, current_memory_size: usize, @@ -637,6 +649,7 @@ pub fn call( value_to_transfer: U256, gas_from_stack: U256, gas_left: u64, + spec_id: SpecId, ) -> Result<(u64, u64), VMError> { let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?; @@ -644,7 +657,12 @@ pub fn call( address_was_cold, CALL_STATIC, CALL_COLD_DYNAMIC, - CALL_WARM_DYNAMIC, + if spec_id >= SpecId::BERLIN { + CALL_WARM_DYNAMIC + } else { + //https://eips.ethereum.org/EIPS/eip-2929 + CALL_PRE_BERLIN + }, )?; let positive_value_cost = if !value_to_transfer.is_zero() { CALL_POSITIVE_VALUE @@ -803,11 +821,120 @@ pub fn identity(data_size: usize) -> Result { precompile(data_size, IDENTITY_STATIC_COST, IDENTITY_DYNAMIC_BASE) } +//https://eips.ethereum.org/EIPS/eip-2565 +pub fn modexp_eip2565( + max_length: u64, + exponent_first_32_bytes: &BigUint, + exponent_size: u64, +) -> Result { + let words = (max_length + .checked_add(7) + .ok_or(OutOfGasError::GasCostOverflow)?) + .checked_div(8) + .ok_or(InternalError::DivisionError)?; + let multiplication_complexity = words.checked_pow(2).ok_or(OutOfGasError::GasCostOverflow)?; + + let calculate_iteration_count = + if exponent_size <= 32 && *exponent_first_32_bytes != BigUint::ZERO { + exponent_first_32_bytes + .bits() + .checked_sub(1) + .ok_or(InternalError::ArithmeticOperationUnderflow)? + } else if exponent_size > 32 { + let extra_size = (exponent_size + .checked_sub(32) + .ok_or(InternalError::ArithmeticOperationUnderflow)?) + .checked_mul(8) + .ok_or(OutOfGasError::GasCostOverflow)?; + extra_size + .checked_add(exponent_first_32_bytes.bits().max(1)) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_sub(1) + .ok_or(InternalError::ArithmeticOperationUnderflow)? + } else { + 0 + } + .max(1); + + let cost = MODEXP_STATIC_COST.max( + multiplication_complexity + .checked_mul(calculate_iteration_count) + .ok_or(OutOfGasError::GasCostOverflow)? + / MODEXP_DYNAMIC_QUOTIENT, + ); + Ok(cost) +} + +//https://eips.ethereum.org/EIPS/eip-198 +pub fn modexp_eip198( + max_length: u64, + exponent_first_32_bytes: &BigUint, + exponent_size: u64, +) -> Result { + let multiplication_complexity = if max_length <= 64 { + max_length + .checked_pow(2) + .ok_or(OutOfGasError::GasCostOverflow)? + } else if max_length <= 1024 { + max_length + .checked_pow(2) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_div(4) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_add( + max_length + .checked_mul(96) + .ok_or(OutOfGasError::GasCostOverflow)?, + ) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_sub(3072) + .ok_or(OutOfGasError::GasCostOverflow)? + } else { + max_length + .checked_pow(2) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_div(16) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_add( + max_length + .checked_mul(480) + .ok_or(OutOfGasError::GasCostOverflow)?, + ) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_sub(199680) + .ok_or(OutOfGasError::GasCostOverflow)? + }; + + let calculate_iteration_count = if exponent_size < 32 { + exponent_first_32_bytes.bits().saturating_sub(1) + } else { + let extra_size = (exponent_size + .checked_sub(32) + .ok_or(InternalError::ArithmeticOperationUnderflow)?) + .checked_mul(8) + .ok_or(OutOfGasError::GasCostOverflow)?; + + let bits_part = exponent_first_32_bytes.bits().saturating_sub(1); + + extra_size + .checked_add(bits_part) + .ok_or(OutOfGasError::GasCostOverflow)? + } + .max(1); + + let cost = multiplication_complexity + .checked_mul(calculate_iteration_count) + .ok_or(OutOfGasError::GasCostOverflow)? + / MODEXP_DYNAMIC_QUOTIENT_PRE_BERLIN; + Ok(cost) +} + pub fn modexp( exponent_first_32_bytes: &BigUint, base_size: usize, exponent_size: usize, modulus_size: usize, + spec_id: SpecId, ) -> Result { let base_size: u64 = base_size .try_into() @@ -820,43 +947,12 @@ pub fn modexp( .map_err(|_| PrecompileError::ParsingInputError)?; let max_length = base_size.max(modulus_size); - let words = (max_length - .checked_add(7) - .ok_or(OutOfGasError::GasCostOverflow)?) - .checked_div(8) - .ok_or(InternalError::DivisionError)?; - let multiplication_complexity = words.checked_pow(2).ok_or(OutOfGasError::GasCostOverflow)?; - - let iteration_count = if exponent_size <= 32 && *exponent_first_32_bytes != BigUint::ZERO { - exponent_first_32_bytes - .bits() - .checked_sub(1) - .ok_or(InternalError::ArithmeticOperationUnderflow)? - } else if exponent_size > 32 { - let extra_size = (exponent_size - .checked_sub(32) - .ok_or(InternalError::ArithmeticOperationUnderflow)?) - .checked_mul(8) - .ok_or(OutOfGasError::GasCostOverflow)?; - extra_size - .checked_add(exponent_first_32_bytes.bits().max(1)) - .ok_or(OutOfGasError::GasCostOverflow)? - .checked_sub(1) - .ok_or(InternalError::ArithmeticOperationUnderflow)? + if spec_id >= SpecId::BERLIN { + modexp_eip2565(max_length, exponent_first_32_bytes, exponent_size) } else { - 0 - }; - let calculate_iteration_count = iteration_count.max(1); - - let cost = MODEXP_STATIC_COST.max( - multiplication_complexity - .checked_mul(calculate_iteration_count) - .ok_or(OutOfGasError::GasCostOverflow)? - / MODEXP_DYNAMIC_QUOTIENT, - ); - - Ok(cost) + modexp_eip198(max_length, exponent_first_32_bytes, exponent_size) + } } fn precompile(data_size: usize, static_cost: u64, dynamic_base: u64) -> Result { diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index bcf66fb89e..884cfff1e2 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -64,6 +64,7 @@ impl VM { value_to_transfer, gas, gas_left, + self.env.spec_id, )?; self.increase_consumed_gas(current_call_frame, cost)?; diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index fd0361eba9..e9a36c6041 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -105,7 +105,10 @@ pub fn is_precompile(callee_address: &Address, spec_id: SpecId) -> bool { PRECOMPILES.contains(callee_address) } -pub fn execute_precompile(current_call_frame: &mut CallFrame) -> Result { +pub fn execute_precompile( + current_call_frame: &mut CallFrame, + spec_id: SpecId, +) -> Result { let callee_address = current_call_frame.code_address; let calldata = current_call_frame.calldata.clone(); let gas_for_call = current_call_frame @@ -123,7 +126,9 @@ pub fn execute_precompile(current_call_frame: &mut CallFrame) -> Result { ripemd_160(&calldata, gas_for_call, consumed_gas)? } - address if address == MODEXP_ADDRESS => modexp(&calldata, gas_for_call, consumed_gas)?, + address if address == MODEXP_ADDRESS => { + modexp(&calldata, gas_for_call, consumed_gas, spec_id)? + } address if address == ECADD_ADDRESS => ecadd(&calldata, gas_for_call, consumed_gas)?, address if address == ECMUL_ADDRESS => ecmul(&calldata, gas_for_call, consumed_gas)?, address if address == ECPAIRING_ADDRESS => { @@ -277,73 +282,83 @@ pub fn modexp( calldata: &Bytes, gas_for_call: u64, consumed_gas: &mut u64, + spec_id: SpecId, ) -> Result { // If calldata does not reach the required length, we should fill the rest with zeros let calldata = fill_with_zeros(calldata, 96)?; - let b_size = U256::from_big_endian( + let base_size = U256::from_big_endian( calldata .get(0..32) .ok_or(PrecompileError::ParsingInputError)?, ); - let e_size = U256::from_big_endian( + let exponent_size = U256::from_big_endian( calldata .get(32..64) .ok_or(PrecompileError::ParsingInputError)?, ); - let m_size = U256::from_big_endian( + let modulus_size = U256::from_big_endian( calldata .get(64..96) .ok_or(PrecompileError::ParsingInputError)?, ); - if b_size == U256::zero() && m_size == U256::zero() { + if base_size == U256::zero() && modulus_size == U256::zero() { increase_precompile_consumed_gas(gas_for_call, MODEXP_STATIC_COST, consumed_gas)?; return Ok(Bytes::new()); } // Because on some cases conversions to usize exploded before the check of the zero value could be done - let b_size = usize::try_from(b_size).map_err(|_| PrecompileError::ParsingInputError)?; - let e_size = usize::try_from(e_size).map_err(|_| PrecompileError::ParsingInputError)?; - let m_size = usize::try_from(m_size).map_err(|_| PrecompileError::ParsingInputError)?; + let base_size = usize::try_from(base_size).map_err(|_| PrecompileError::ParsingInputError)?; + let exponent_size = + usize::try_from(exponent_size).map_err(|_| PrecompileError::ParsingInputError)?; + let modulus_size = + usize::try_from(modulus_size).map_err(|_| PrecompileError::ParsingInputError)?; - let base_limit = b_size + let base_limit = base_size .checked_add(96) .ok_or(InternalError::ArithmeticOperationOverflow)?; - let exponent_limit = e_size + let exponent_limit = exponent_size .checked_add(base_limit) .ok_or(InternalError::ArithmeticOperationOverflow)?; - let modulus_limit = m_size + let modulus_limit = modulus_size .checked_add(exponent_limit) .ok_or(InternalError::ArithmeticOperationOverflow)?; - let b = get_slice_or_default(&calldata, 96, base_limit, b_size)?; + let b = get_slice_or_default(&calldata, 96, base_limit, base_size)?; let base = BigUint::from_bytes_be(&b); - let e = get_slice_or_default(&calldata, base_limit, exponent_limit, e_size)?; + let e = get_slice_or_default(&calldata, base_limit, exponent_limit, exponent_size)?; let exponent = BigUint::from_bytes_be(&e); - let m = get_slice_or_default(&calldata, exponent_limit, modulus_limit, m_size)?; + let m = get_slice_or_default(&calldata, exponent_limit, modulus_limit, modulus_size)?; let modulus = BigUint::from_bytes_be(&m); // First 32 bytes of exponent or exponent if e_size < 32 - let bytes_to_take = 32.min(e_size); + let bytes_to_take = 32.min(exponent_size); // Use of unwrap_or_default because if e == 0 get_slice_or_default returns an empty vec let exp_first_32 = BigUint::from_bytes_be(e.get(0..bytes_to_take).unwrap_or_default()); - let gas_cost = gas_cost::modexp(&exp_first_32, b_size, e_size, m_size)?; + let gas_cost = gas_cost::modexp( + &exp_first_32, + base_size, + exponent_size, + modulus_size, + spec_id, + )?; + increase_precompile_consumed_gas(gas_for_call, gas_cost, consumed_gas)?; let result = mod_exp(base, exponent, modulus); let res_bytes = result.to_bytes_be(); - let res_bytes = increase_left_pad(&Bytes::from(res_bytes), m_size)?; + let res_bytes = increase_left_pad(&Bytes::from(res_bytes), modulus_size)?; - Ok(res_bytes.slice(..m_size)) + Ok(res_bytes.slice(..modulus_size)) } /// This function returns the slice between the lower and upper limit of the calldata (as a vector), diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index de7053f3a9..7c1b39a396 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -254,7 +254,7 @@ impl VM { ); if is_precompile(¤t_call_frame.code_address, self.env.spec_id) { - let precompile_result = execute_precompile(current_call_frame); + let precompile_result = execute_precompile(current_call_frame, self.env.spec_id); match precompile_result { Ok(output) => { @@ -547,8 +547,8 @@ impl VM { // Calldata Cost // 4 gas for each zero byte in the transaction data 16 gas for each non-zero byte in the transaction. - let calldata_cost = - gas_cost::tx_calldata(&initial_call_frame.calldata).map_err(VMError::OutOfGas)?; + let calldata_cost = gas_cost::tx_calldata(&initial_call_frame.calldata, self.env.spec_id) + .map_err(VMError::OutOfGas)?; intrinsic_gas = intrinsic_gas .checked_add(calldata_cost) diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index 028f5a44f7..5a5840d756 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -4549,7 +4549,13 @@ fn modexp_test() { let calldata = Bytes::from(calldata); let mut consumed_gas = 0; - let result = modexp(&calldata, 10000, &mut consumed_gas).unwrap(); + let result = modexp( + &calldata, + 10000, + &mut consumed_gas, + ethrex_levm::SpecId::CANCUN, + ) + .unwrap(); let expected_result = Bytes::from(hex::decode("08").unwrap()); @@ -4564,7 +4570,13 @@ fn modexp_test_2() { let calldata = Bytes::from(calldata); let mut consumed_gas = 0; - let result = modexp(&calldata, 10000, &mut consumed_gas).unwrap(); + let result = modexp( + &calldata, + 10000, + &mut consumed_gas, + ethrex_levm::SpecId::CANCUN, + ) + .unwrap(); let expected_result = Bytes::from( hex::decode("3b01b01ac41f2d6e917c6d6a221ce793802469026d9ab7578fa2e79e4da6aaab").unwrap(),