Incorrect gas-cost calculation for MCOPY
out-of-gas test cases
Description
In zkevm-circuits/src/evm_circuit/execution/error_oog_memory_copy.rs
, the tests::TestingData::new_for_mcopy
function is used to generate test cases for MCOPY
, generating some bytecode to perform an MCOPY
and calculating the gas costs.
pub fn new_for_mcopy(
src_offset: u64,
dst_offset: u64,
copy_size: u64,
gas_cost: Option<u64>,
) -> Self {
let bytecode = bytecode! {
PUSH32(copy_size)
PUSH32(src_offset)
PUSH32(dst_offset)
MCOPY
};
let gas_cost = gas_cost.unwrap_or_else(|| {
let cur_memory_word_size = (src_offset + 31) / 32;
let memory_word_size = (dst_offset + copy_size + 31) / 32;
OpcodeId::PUSH32.constant_gas_cost().0 * 3
+ memory_copier_gas_cost(
cur_memory_word_size,
memory_word_size,
copy_size,
GasCost::COPY.as_u64(),
)
});
Self { bytecode, gas_cost }
}
The function memory_copier_gas_cost
expects as arguments the current memory size in words, the memory size in words after expansion, the number of bytes to copy, and the amount of gas used per word copied. The first two arguments are incorrect, however.
Impact
Test cases using this function can behave in unintended ways (for example, panic) in some instances due to the incorrect gas calculation.
Recommendations
For the shown bytecode, no writes to memory have been performed before MCOPY
, so the current memory size should be zero words. The new memory size will then be the maximum of (src_offset + copy_size + 31) / 32
and (dst_offset + copy_size + 31) / 32
if copy_size
is nonzero and zero if copy_size
is zero.
Remediation
This issue has been acknowledged by Scroll, and fixes were implemented in the following commits: