Skip to content
Open
Show file tree
Hide file tree
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
5 changes: 5 additions & 0 deletions crates/context/interface/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ pub trait Host {
fn load_account_code(&mut self, address: Address) -> Option<StateLoad<Bytes>>;
/// Load account code hash, calls `ContextTr::journal_mut().code_hash(address)`
fn load_account_code_hash(&mut self, address: Address) -> Option<StateLoad<B256>>;

/// Record gas refund, calls `ContextTr::journal_mut().record_refund(refund)`
fn record_refund(&mut self, refund: i64);
}

/// Dummy host that implements [`Host`] trait and returns all default values.
Expand Down Expand Up @@ -194,4 +197,6 @@ impl Host for DummyHost {
fn load_account_code_hash(&mut self, _address: Address) -> Option<StateLoad<B256>> {
None
}

fn record_refund(&mut self, _refund: i64) {}
}
9 changes: 9 additions & 0 deletions crates/context/interface/src/journaled_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ pub trait JournalTr {
self.set_code_with_hash(address, code, hash);
}

/// Records a gas refund for the current transaction.
///
/// This is used to track gas refunds globally at the journal level
/// instead of locally at the call frame level.
fn record_refund(&mut self, refund: i64);

/// Returns the current accumulated gas refund for the transaction.
fn refund(&self) -> i64;

/// Returns account code bytes and if address is cold loaded.
#[inline]
fn code(
Expand Down
5 changes: 5 additions & 0 deletions crates/context/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,4 +625,9 @@ impl<
})
.ok()
}

/// Records a gas refund for the current transaction.
fn record_refund(&mut self, refund: i64) {
self.journal_mut().record_refund(refund);
}
}
10 changes: 10 additions & 0 deletions crates/context/src/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,14 @@ impl<DB: Database, ENTRY: JournalEntryTr> JournalTr for Journal<DB, ENTRY> {
fn finalize(&mut self) -> Self::State {
self.inner.finalize()
}

#[inline]
fn record_refund(&mut self, refund: i64) {
self.inner.record_refund(refund)
}

#[inline]
fn refund(&self) -> i64 {
self.inner.refund()
}
}
29 changes: 29 additions & 0 deletions crates/context/src/journal/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ pub trait JournalEntryTr {
/// Creates a journal entry for when an account's code is modified
fn code_changed(address: Address) -> Self;

/// Creates a journal entry for when gas refund is recorded
fn refund_changed(old_refund: i64) -> Self;

/// Returns the old refund value if this is a RefundChanged entry
fn get_refund_revert(&self) -> Option<i64>;

/// Reverts the state change recorded by this journal entry
///
/// More information on what is reverted can be found in [`JournalEntry`] enum.
Expand Down Expand Up @@ -213,6 +219,13 @@ pub enum JournalEntry {
/// Address of account that had its code changed.
address: Address,
},
/// Gas refund changed
/// Action: Gas refund value changed
/// Revert: Revert to previous refund value
RefundChanged {
/// Previous refund value before the change.
old_refund: i64,
},
}
impl JournalEntryTr for JournalEntry {
fn account_warmed(address: Address) -> Self {
Expand Down Expand Up @@ -287,6 +300,17 @@ impl JournalEntryTr for JournalEntry {
JournalEntry::CodeChange { address }
}

fn refund_changed(old_refund: i64) -> Self {
JournalEntry::RefundChanged { old_refund }
}

fn get_refund_revert(&self) -> Option<i64> {
match self {
JournalEntry::RefundChanged { old_refund } => Some(*old_refund),
_ => None,
}
}

fn revert(
self,
state: &mut EvmState,
Expand Down Expand Up @@ -405,6 +429,11 @@ impl JournalEntryTr for JournalEntry {
acc.info.code_hash = KECCAK_EMPTY;
acc.info.code = None;
}
JournalEntry::RefundChanged { old_refund: _ } => {
// RefundChanged entries are handled specially in JournalInner
// This revert method cannot access the journal's refund field
// So RefundChanged entries should be filtered out before calling this
}
}
}
}
78 changes: 67 additions & 11 deletions crates/context/src/journal/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ pub struct JournalInner<ENTRY> {
pub spec: SpecId,
/// Warm addresses containing both coinbase and current precompiles.
pub warm_addresses: WarmAddresses,
/// Global gas refund accumulated for the current transaction.
///
/// This tracks refunds (such as from SSTORE operations) at the global journal level
/// instead of locally at each call frame, which helps avoid negative refund values
/// being visible during tracing.
pub refund: i64,
}

impl<ENTRY: JournalEntryTr> Default for JournalInner<ENTRY> {
Expand All @@ -78,6 +84,7 @@ impl<ENTRY: JournalEntryTr> JournalInner<ENTRY> {
depth: 0,
spec: SpecId::default(),
warm_addresses: WarmAddresses::new(),
refund: 0,
}
}

Expand Down Expand Up @@ -106,6 +113,7 @@ impl<ENTRY: JournalEntryTr> JournalInner<ENTRY> {
transaction_id,
spec,
warm_addresses,
refund,
} = self;
// Spec precompiles and state are not changed. It is always set again execution.
let _ = spec;
Expand All @@ -121,6 +129,8 @@ impl<ENTRY: JournalEntryTr> JournalInner<ENTRY> {
// increment transaction id.
*transaction_id += 1;
logs.clear();
// Reset refund for next transaction
*refund = 0;
}

/// Discard the current transaction, by reverting the journal entries and incrementing the transaction id.
Expand All @@ -135,19 +145,19 @@ impl<ENTRY: JournalEntryTr> JournalInner<ENTRY> {
transaction_id,
spec,
warm_addresses,
refund,
} = self;
let is_spurious_dragon_enabled = spec.is_enabled_in(SPURIOUS_DRAGON);
// iterate over all journals entries and revert our global state
journal.drain(..).rev().for_each(|entry| {
entry.revert(state, None, is_spurious_dragon_enabled);
});
Self::revert_journal_entries_impl(journal.drain(..), refund, state, None, *spec);
transient_storage.clear();
*depth = 0;
logs.clear();
*transaction_id += 1;

// Clear coinbase address warming for next tx
warm_addresses.clear_coinbase();
// Reset refund when discarding transaction
*refund = 0;
}

/// Take the [`EvmState`] and clears the journal by resetting it to initial state.
Expand All @@ -167,11 +177,14 @@ impl<ENTRY: JournalEntryTr> JournalInner<ENTRY> {
transaction_id,
spec,
warm_addresses,
refund,
} = self;
// Spec is not changed. And it is always set again in execution.
let _ = spec;
// Clear coinbase address warming for next tx
warm_addresses.clear_coinbase();
// Reset refund for next transaction
*refund = 0;

let state = mem::take(state);
logs.clear();
Expand Down Expand Up @@ -459,19 +472,19 @@ impl<ENTRY: JournalEntryTr> JournalInner<ENTRY> {
/// Reverts all changes to state until given checkpoint.
#[inline]
pub fn checkpoint_revert(&mut self, checkpoint: JournalCheckpoint) {
let is_spurious_dragon_enabled = self.spec.is_enabled_in(SPURIOUS_DRAGON);
let state = &mut self.state;
let transient_storage = &mut self.transient_storage;
self.depth -= 1;
self.logs.truncate(checkpoint.log_i);

// iterate over last N journals sets and revert our global state
self.journal
.drain(checkpoint.journal_i..)
.rev()
.for_each(|entry| {
entry.revert(state, Some(transient_storage), is_spurious_dragon_enabled);
});
Self::revert_journal_entries_impl(
self.journal.drain(checkpoint.journal_i..),
&mut self.refund,
state,
Some(transient_storage),
self.spec,
);
}

/// Performs selfdestruct action.
Expand Down Expand Up @@ -855,3 +868,46 @@ pub fn sload_with_account<DB: Database, ENTRY: JournalEntryTr>(

Ok(StateLoad::new(value, is_cold))
}

impl<ENTRY: JournalEntryTr> JournalInner<ENTRY> {
/// Records a gas refund for the current transaction.
///
/// This accumulates refunds at the global journal level, avoiding negative
/// refund values being visible during individual call frame execution.
#[inline]
pub fn record_refund(&mut self, refund: i64) {
let old_refund = self.refund;
self.refund += refund;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Journal entry for refund should be made so refund can be reverted in case of revert/oog/stackoverflow etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case of revert/oog/stackoverflow etc.

Look like these aren't covered by test.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah will ping ef testing team

self.journal.push(ENTRY::refund_changed(old_refund));
}

/// Returns the current accumulated gas refund for the transaction.
#[inline]
pub fn refund(&self) -> i64 {
self.refund
}

/// Reverts journal entries with proper handling of RefundChanged entries.
fn revert_journal_entries_impl<I>(
entries: I,
refund: &mut i64,
state: &mut EvmState,
mut transient_storage: Option<&mut TransientStorage>,
spec: SpecId,
) where
I: Iterator<Item = ENTRY> + DoubleEndedIterator,
{
let is_spurious_dragon_enabled = spec.is_enabled_in(SPURIOUS_DRAGON);
for entry in entries.rev() {
if let Some(old_refund_value) = entry.get_refund_revert() {
*refund = old_refund_value;
} else {
entry.revert(
state,
transient_storage.as_deref_mut(),
is_spurious_dragon_enabled,
);
}
}
}
}
5 changes: 0 additions & 5 deletions crates/handler/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,10 +501,6 @@ impl EthFrame<EthInterpreter> {
.memory
.set(mem_start, &interpreter.return_data.buffer()[..target_len]);
}

if ins_result.is_ok() {
interpreter.gas.record_refund(out_gas.refunded());
}
}
FrameResult::Create(outcome) => {
let instruction_result = *outcome.instruction_result();
Expand Down Expand Up @@ -532,7 +528,6 @@ impl EthFrame<EthInterpreter> {
}

let stack_item = if instruction_result.is_ok() {
this_gas.record_refund(outcome.gas().refunded());
outcome.address.unwrap_or_default().into_word().into()
} else {
U256::ZERO
Expand Down
15 changes: 6 additions & 9 deletions crates/handler/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,11 @@ pub trait Handler {
eip7702_gas_refund: i64,
) -> Result<(), Self::Error> {
// Calculate final refund and add EIP-7702 refund to gas.
self.refund(evm, exec_result, eip7702_gas_refund);
let gas_refund = self.refund(evm, exec_result, eip7702_gas_refund);
// Ensure gas floor is met and minimum floor gas is spent.
self.eip7623_check_gas_floor(evm, exec_result, init_and_floor_gas);
// Return unused gas to caller
self.reimburse_caller(evm, exec_result)?;
self.reimburse_caller(evm, exec_result, gas_refund)?;
// Pay transaction fees to beneficiary
self.reward_beneficiary(evm, exec_result)?;
Ok(())
Expand Down Expand Up @@ -310,7 +310,6 @@ pub trait Handler {
let instruction_result = frame_result.interpreter_result().result;
let gas = frame_result.gas_mut();
let remaining = gas.remaining();
let refunded = gas.refunded();

// Spend the gas limit. Gas is reimbursed when the tx returns successfully.
*gas = Gas::new_spent(evm.ctx().tx().gas_limit());
Expand All @@ -319,9 +318,6 @@ pub trait Handler {
gas.erase_cost(remaining);
}

if instruction_result.is_ok() {
gas.record_refund(refunded);
}
Ok(())
}

Expand Down Expand Up @@ -390,9 +386,9 @@ pub trait Handler {
evm: &mut Self::Evm,
exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
eip7702_refund: i64,
) {
) -> i64 {
let spec = evm.ctx().cfg().spec().into();
post_execution::refund(spec, exec_result.gas_mut(), eip7702_refund)
post_execution::refund(evm.ctx_mut(), exec_result.gas_mut(), spec, eip7702_refund)
}

/// Returns unused gas costs to the transaction sender's account.
Expand All @@ -401,8 +397,9 @@ pub trait Handler {
&self,
evm: &mut Self::Evm,
exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
gas_refund: i64,
) -> Result<(), Self::Error> {
post_execution::reimburse_caller(evm.ctx(), exec_result.gas(), U256::ZERO)
post_execution::reimburse_caller(evm.ctx_mut(), exec_result.gas(), gas_refund, U256::ZERO)
.map_err(From::from)
}

Expand Down
Loading
Loading