Skip to content

Commit ad627ae

Browse files
authored
fix(forge): fix dynamic gas limit check (#12267)
* fix(forge): fix dynamic gas limit check * add repro
1 parent bfde973 commit ad627ae

File tree

3 files changed

+224
-48
lines changed

3 files changed

+224
-48
lines changed

crates/cheatcodes/src/inspector.rs

Lines changed: 17 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -506,11 +506,8 @@ pub struct Cheatcodes {
506506
pub wallets: Option<Wallets>,
507507
/// Signatures identifier for decoding events and functions
508508
signatures_identifier: OnceLock<Option<SignaturesIdentifier>>,
509-
/// Used to determine whether the broadcasted call has non-fixed gas limit.
510-
/// Holds values for (seen opcode GAS, seen opcode CALL) pair.
511-
/// If GAS opcode is followed by CALL opcode then both flags are marked true and call
512-
/// has non-fixed gas limit, otherwise the call is considered to have fixed gas limit.
513-
pub dynamic_gas_limit_sequence: Option<(bool, bool)>,
509+
/// Used to determine whether the broadcasted call has dynamic gas limit.
510+
pub dynamic_gas_limit: bool,
514511
// Custom execution evm version.
515512
pub execution_evm_version: Option<SpecId>,
516513
}
@@ -569,7 +566,7 @@ impl Cheatcodes {
569566
deprecated: Default::default(),
570567
wallets: Default::default(),
571568
signatures_identifier: Default::default(),
572-
dynamic_gas_limit_sequence: Default::default(),
569+
dynamic_gas_limit: Default::default(),
573570
execution_evm_version: None,
574571
}
575572
}
@@ -859,6 +856,11 @@ impl Cheatcodes {
859856

860857
// Apply our broadcast
861858
if let Some(broadcast) = &self.broadcast {
859+
// Additional check as transfers in forge scripts seem to be estimated at 2300
860+
// by revm leading to "Intrinsic gas too low" failure when simulated on chain.
861+
let is_fixed_gas_limit = call.gas_limit >= 21_000 && !self.dynamic_gas_limit;
862+
self.dynamic_gas_limit = false;
863+
862864
// We only apply a broadcast *to a specific depth*.
863865
//
864866
// We do this because any subsequent contract calls *must* exist on chain and
@@ -886,15 +888,6 @@ impl Cheatcodes {
886888
});
887889
}
888890

889-
let (gas_seen, call_seen) =
890-
self.dynamic_gas_limit_sequence.take().unwrap_or_default();
891-
// Transaction has fixed gas limit if no GAS opcode seen before CALL opcode.
892-
let mut is_fixed_gas_limit = !(gas_seen && call_seen);
893-
// Additional check as transfers in forge scripts seem to be estimated at 2300
894-
// by revm leading to "Intrinsic gas too low" failure when simulated on chain.
895-
if call.gas_limit < 21_000 {
896-
is_fixed_gas_limit = false;
897-
}
898891
let input = TransactionInput::new(call.input.bytes(ecx));
899892

900893
let account =
@@ -1122,7 +1115,7 @@ impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for Cheatcodes {
11221115
self.pc = interpreter.bytecode.pc();
11231116

11241117
if self.broadcast.is_some() {
1125-
self.record_gas_limit_opcode(interpreter);
1118+
self.set_gas_limit_type(interpreter);
11261119
}
11271120

11281121
// `pauseGasMetering`: pause / resume interpreter gas.
@@ -1165,10 +1158,6 @@ impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for Cheatcodes {
11651158
}
11661159

11671160
fn step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) {
1168-
if self.broadcast.is_some() {
1169-
self.set_gas_limit_type(interpreter);
1170-
}
1171-
11721161
if self.gas_metering.paused {
11731162
self.meter_gas_end(interpreter);
11741163
}
@@ -2362,38 +2351,18 @@ impl Cheatcodes {
23622351
}
23632352

23642353
#[cold]
2365-
fn record_gas_limit_opcode(&mut self, interpreter: &mut Interpreter) {
2354+
fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) {
23662355
match interpreter.bytecode.opcode() {
2367-
// If current opcode is CREATE2 then set non-fixed gas limit.
2368-
op::CREATE2 => self.dynamic_gas_limit_sequence = Some((true, true)),
2369-
op::GAS => {
2370-
if self.dynamic_gas_limit_sequence.is_none() {
2371-
// If current opcode is GAS then mark as seen.
2372-
self.dynamic_gas_limit_sequence = Some((true, false));
2373-
}
2356+
op::CREATE2 => self.dynamic_gas_limit = true,
2357+
op::CALL => {
2358+
// If first element of the stack is close to current remaining gas then assume
2359+
// dynamic gas limit.
2360+
self.dynamic_gas_limit =
2361+
try_or_return!(interpreter.stack.peek(0)) >= interpreter.gas.remaining() - 100
23742362
}
2375-
_ => {}
2363+
_ => self.dynamic_gas_limit = false,
23762364
}
23772365
}
2378-
2379-
#[cold]
2380-
fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) {
2381-
// Early exit in case we already determined is non-fixed gas limit.
2382-
if matches!(self.dynamic_gas_limit_sequence, Some((true, true))) {
2383-
return;
2384-
}
2385-
2386-
// Record CALL opcode if GAS opcode was seen.
2387-
if matches!(self.dynamic_gas_limit_sequence, Some((true, false)))
2388-
&& interpreter.bytecode.opcode() == op::CALL
2389-
{
2390-
self.dynamic_gas_limit_sequence = Some((true, true));
2391-
return;
2392-
}
2393-
2394-
// Reset dynamic gas limit sequence if GAS opcode was not followed by a CALL opcode.
2395-
self.dynamic_gas_limit_sequence = None;
2396-
}
23972366
}
23982367

23992368
/// Helper that expands memory, stores a revert string pertaining to a disallowed memory write,

0 commit comments

Comments
 (0)