Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 58 additions & 18 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,11 @@ pub struct Cheatcodes {
pub wallets: Option<Wallets>,
/// Signatures identifier for decoding events and functions
pub signatures_identifier: Option<SignaturesIdentifier>,
/// Used to determine whether the broadcasted call has non-fixed gas limit.
/// Holds values for (seen opcode GAS, seen opcode CALL) pair.
/// If GAS opcode is followed by CALL opcode then both flags are marked true and call
/// has non-fixed gas limit, otherwise the call is considered to have fixed gas limit.
pub dynamic_gas_limit_sequence: Option<(bool, bool)>,
}

// This is not derived because calling this in `fn new` with `..Default::default()` creates a second
Expand Down Expand Up @@ -554,6 +559,7 @@ impl Cheatcodes {
deprecated: Default::default(),
wallets: Default::default(),
signatures_identifier: SignaturesIdentifier::new(true).ok(),
dynamic_gas_limit_sequence: Default::default(),
}
}

Expand Down Expand Up @@ -852,8 +858,15 @@ impl Cheatcodes {
});
}

let is_fixed_gas_limit = check_if_fixed_gas_limit(&ecx, call.gas_limit);

let (gas_seen, call_seen) =
self.dynamic_gas_limit_sequence.take().unwrap_or_default();
// Transaction has fixed gas limit if no GAS opcode seen before CALL opcode.
let mut is_fixed_gas_limit = !(gas_seen && call_seen);
// Additional check as transfers in forge scripts seem to be estimated at 2300
// by revm leading to "Intrinsic gas too low" failure when simulated on chain.
if call.gas_limit < 21_000 {
is_fixed_gas_limit = false;
}
let input = TransactionInput::new(call.input.bytes(ecx));
// Ensure account is touched.
ecx.journaled_state.touch(broadcast.new_origin);
Expand Down Expand Up @@ -1077,6 +1090,10 @@ impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for Cheatcodes {
fn step(&mut self, interpreter: &mut Interpreter, ecx: Ecx) {
self.pc = interpreter.bytecode.pc();

if self.broadcast.is_some() {
self.record_gas_limit_opcode(interpreter);
}

// `pauseGasMetering`: pause / resume interpreter gas.
if self.gas_metering.paused {
self.meter_gas(interpreter);
Expand Down Expand Up @@ -1117,6 +1134,10 @@ impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for Cheatcodes {
}

fn step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) {
if self.broadcast.is_some() {
self.set_gas_limit_type(interpreter);
}

if self.gas_metering.paused {
self.meter_gas_end(interpreter);
}
Expand Down Expand Up @@ -1614,7 +1635,7 @@ impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for Cheatcodes {

if curr_depth == broadcast.depth {
input.set_caller(broadcast.new_origin);
let is_fixed_gas_limit = check_if_fixed_gas_limit(&ecx, input.gas_limit());

// Ensure account is touched.
ecx.journaled_state.touch(broadcast.new_origin);

Expand All @@ -1627,7 +1648,6 @@ impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for Cheatcodes {
value: Some(input.value()),
input: TransactionInput::new(input.init_code()),
nonce: Some(account.info.nonce),
gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None },
..Default::default()
}
.into(),
Expand Down Expand Up @@ -2296,6 +2316,40 @@ impl Cheatcodes {
(REVERT, 0, 1, false),
);
}

#[cold]
fn record_gas_limit_opcode(&mut self, interpreter: &mut Interpreter) {
match interpreter.bytecode.opcode() {
// If current opcode is CREATE2 then set non-fixed gas limit.
op::CREATE2 => self.dynamic_gas_limit_sequence = Some((true, true)),
op::GAS => {
if self.dynamic_gas_limit_sequence.is_none() {
// If current opcode is GAS then mark as seen.
self.dynamic_gas_limit_sequence = Some((true, false));
}
}
_ => {}
}
}

#[cold]
fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) {
// Early exit in case we already determined is non-fixed gas limit.
if matches!(self.dynamic_gas_limit_sequence, Some((true, true))) {
return;
}

// Record CALL opcode if GAS opcode was seen.
if matches!(self.dynamic_gas_limit_sequence, Some((true, false)))
&& interpreter.bytecode.opcode() == op::CALL
{
self.dynamic_gas_limit_sequence = Some((true, true));
return;
}

// Reset dynamic gas limit sequence if GAS opcode was not followed by a CALL opcode.
self.dynamic_gas_limit_sequence = None;
}
}

/// Helper that expands memory, stores a revert string pertaining to a disallowed memory write,
Expand Down Expand Up @@ -2323,20 +2377,6 @@ fn disallowed_mem_write(
));
}

// Determines if the gas limit on a given call was manually set in the script and should therefore
// not be overwritten by later estimations
fn check_if_fixed_gas_limit(ecx: &Ecx, call_gas_limit: u64) -> bool {
// If the gas limit was not set in the source code it is set to the estimated gas left at the
// time of the call, which should be rather close to configured gas limit.
// TODO: Find a way to reliably make this determination.
// For example by generating it in the compilation or EVM simulation process
ecx.tx.gas_limit > ecx.block.gas_limit &&
call_gas_limit <= ecx.block.gas_limit
// Transfers in forge scripts seem to be estimated at 2300 by revm leading to "Intrinsic
// gas too low" failure when simulated on chain
&& call_gas_limit > 2300
}

/// Returns true if the kind of account access is a call.
fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool {
matches!(
Expand Down
Loading