Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ We introduce several features:
- **Instruction Set:** CLOAD and CSTORE for accessing private storage.
- **Flagged Storage:** [Flagged Storage](#flagged-storage) introduces a novel mechanism where each slot is represented as a tuple `(value, is_private)` with strict access rules.
- **Precompiles:** [Precompiles](#precompiles) extend the functionality of the EVM.
- **Gas Payment:** [Registry-based gas payment](docs/gas-token-payments.md) provides strict signed asset selection, owner-registered precision/storage modes, and journaled fee-reserve settlement.
- **Semantic Tests:** [Semantic Tests](#semantic-tests) help us catch regressions and validate new features.

---
Expand Down
5 changes: 1 addition & 4 deletions bins/revme/src/cmd/blockchaintest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -980,11 +980,8 @@ fn skip_test(path: &Path) -> bool {
// Add any problematic tests here that should be skipped
matches!(
name,
// Test check if gas price overflows, we handle this correctly but does not match tests specific exception.
"CreateTransactionHighNonce.json"

// Test with some storage check.
| "RevertInCreateInInit_Paris.json"
"RevertInCreateInInit_Paris.json"
| "RevertInCreateInInit.json"
| "dynamicAccountOverwriteEmpty.json"
| "dynamicAccountOverwriteEmpty_Paris.json"
Expand Down
5 changes: 1 addition & 4 deletions bins/revme/src/cmd/statetest/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,8 @@ fn skip_test(path: &Path) -> bool {

matches!(
name,
// Test check if gas price overflows, we handle this correctly but does not match tests specific exception.
| "CreateTransactionHighNonce.json"

// Test with some storage check.
| "RevertInCreateInInit_Paris.json"
"RevertInCreateInInit_Paris.json"
| "RevertInCreateInInit.json"
| "dynamicAccountOverwriteEmpty.json"
| "dynamicAccountOverwriteEmpty_Paris.json"
Expand Down
27 changes: 26 additions & 1 deletion crates/context/interface/src/journaled_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,30 @@ pub trait JournalTr {
.map_err(JournalLoadError::unwrap_db_error)
}

/// Read current journal storage without warming either the account or slot.
/// Read-only system accesses do not touch accounts or initialize privacy flags.
fn system_storage(
&mut self,
address: Address,
key: StorageKey,
) -> Result<StateLoad<StorageValue>, <Self::Database as Database>::Error>;

/// Journal a system storage write with an explicit privacy flag, without warming.
/// Loads and conditionally touches the account so changes survive state commit.
/// Callers must validate slot visibility and skip zero-amount fee operations.
fn system_store(
&mut self,
address: Address,
key: StorageKey,
value: primitives::FlaggedStorage,
) -> Result<StateLoad<SStoreResult>, <Self::Database as Database>::Error>;

/// Load a system account without adding warmth or a touch.
fn system_load_account(
&mut self,
address: Address,
) -> Result<StateLoad<&mut Account>, <Self::Database as Database>::Error>;

/// Returns the private storage value from Journal state.
///
/// Loads the storage from database if not found in Journal state.
Expand Down Expand Up @@ -144,7 +168,8 @@ pub trait JournalTr {
balance: U256,
) -> Result<Option<TransferError>, <Self::Database as Database>::Error>;

/// Increments the balance of the account.
/// Journal caller balance/nonce changes and conditionally introduce its touch.
/// The caller must not mark itself touched before invoking this operation.
fn caller_accounting_journal_entry(
&mut self,
address: Address,
Expand Down
44 changes: 44 additions & 0 deletions crates/context/interface/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,35 @@ pub enum InvalidTransaction {
/// Balance of the sender.
balance: Box<U256>,
},
/// The signed gas-payment selector is malformed or used on a standard transaction.
InvalidGasPaymentSelector,
/// Registry length exceeds the bounded execution scan.
GasTokenRegistryTooLarge,
/// The explicitly selected token is not registered.
GasTokenNotRegistered(Address),
/// The explicitly selected token is inactive.
GasTokenInactive(Address),
/// The selected registry entry has an unsupported storage mode.
UnsupportedGasTokenMode {
/// Selected token address.
token: Address,
/// Stored mode byte.
mode: u8,
},
/// The selected registry entry has an unsupported decimal precision.
UnsupportedGasTokenDecimals {
/// Selected token address.
token: Address,
/// Stored precision byte.
decimals: u8,
},
/// A fee balance slot contradicts the registered storage mode.
GasTokenBalanceModeMismatch {
/// Selected token address.
token: Address,
/// Balance holder whose slot contradicts the mode.
account: Address,
},
/// Overflow payment in transaction.
OverflowPaymentInTransaction,
/// Nonce overflows in transaction.
Expand Down Expand Up @@ -487,6 +516,21 @@ impl fmt::Display for InvalidTransaction {
Self::LackOfFundForMaxFee { fee, balance } => {
write!(f, "lack of funds ({balance}) for max fee ({fee})")
}
Self::InvalidGasPaymentSelector => write!(f, "invalid gas payment selector"),
Self::GasTokenRegistryTooLarge => {
write!(f, "gas token registry exceeds maximum length")
}
Self::GasTokenNotRegistered(token) => write!(f, "gas token {token} is not registered"),
Self::GasTokenInactive(token) => write!(f, "gas token {token} is inactive"),
Self::UnsupportedGasTokenMode { token, mode } => {
write!(f, "gas token {token} has unsupported storage mode {mode}")
}
Self::UnsupportedGasTokenDecimals { token, decimals } => {
write!(f, "gas token {token} has unsupported decimals {decimals}")
}
Self::GasTokenBalanceModeMismatch { token, account } => {
write!(f, "gas token {token} balance mode mismatch for {account}")
}
Self::OverflowPaymentInTransaction => {
write!(f, "overflow payment in transaction")
}
Expand Down
5 changes: 4 additions & 1 deletion crates/context/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,10 @@ impl<
) -> Result<StateLoad<SStoreResult>, LoadError> {
self.journal_mut()
.cstore(address, key, value, skip_cold_load)
.map_err(|_e| LoadError::DBError)
.map_err(|e| {
*self.error() = Err(e.into());
LoadError::DBError
})
}

/// Emits a log owned by `address` with given `LogData`.
Expand Down
29 changes: 29 additions & 0 deletions crates/context/src/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
pub mod entry;
pub mod inner;
#[cfg(test)]
mod test_code_changes;
#[cfg(test)]
mod test_flagged_storage;
#[cfg(test)]
mod test_system_access;
pub mod warm_addresses;

pub use entry::{JournalEntry, JournalEntryTr};
Expand Down Expand Up @@ -108,6 +112,31 @@ impl<DB: Database, ENTRY: JournalEntryTr> JournalTr for Journal<DB, ENTRY> {
&mut self.database
}

fn system_load_account(
&mut self,
address: Address,
) -> Result<StateLoad<&mut Account>, DB::Error> {
self.inner.system_load_account(&mut self.database, address)
}

fn system_storage(
&mut self,
address: Address,
key: StorageKey,
) -> Result<StateLoad<StorageValue>, DB::Error> {
self.inner.system_storage(&mut self.database, address, key)
}

fn system_store(
&mut self,
address: Address,
key: StorageKey,
value: primitives::FlaggedStorage,
) -> Result<StateLoad<SStoreResult>, DB::Error> {
self.inner
.system_store(&mut self.database, address, key, value)
}

fn cload(
&mut self,
address: Address,
Expand Down
33 changes: 25 additions & 8 deletions crates/context/src/journal/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
//! or removal of the storage slot. Check [`JournalEntryTr`] for more details.
use primitives::alloy_primitives::FlaggedStorage;

use primitives::{Address, StorageKey, StorageValue, KECCAK_EMPTY, PRECOMPILE3, U256};
use state::{EvmState, TransientStorage};
use primitives::{Address, StorageKey, StorageValue, B256, PRECOMPILE3, U256};
use state::{Bytecode, EvmState, TransientStorage};

/// Trait for tracking and reverting state changes in the EVM.
/// Journal entry contains information about state changes that can be reverted.
Expand Down Expand Up @@ -59,7 +59,12 @@ pub trait JournalEntryTr {
) -> Self;

/// Creates a journal entry for when an account's code is modified
fn code_changed(address: Address) -> Self;
///
/// Records the previous code hash and bytecode for reverting: since
/// EIP-7702 the code of an already-delegated account can be changed (and
/// the change reverted), so the revert cannot assume the previous code was
/// empty.
fn code_changed(address: Address, had_code_hash: B256, had_code: Option<Bytecode>) -> Self;

/// Reverts the state change recorded by this journal entry
///
Expand Down Expand Up @@ -213,6 +218,10 @@ pub enum JournalEntry {
CodeChange {
/// Address of account that had its code changed.
address: Address,
/// Previous code hash of the account.
had_code_hash: B256,
/// Previous bytecode of the account (`None` if it was not loaded).
had_code: Option<Bytecode>,
},
}
impl JournalEntryTr for JournalEntry {
Expand Down Expand Up @@ -284,8 +293,12 @@ impl JournalEntryTr for JournalEntry {
}
}

fn code_changed(address: Address) -> Self {
JournalEntry::CodeChange { address }
fn code_changed(address: Address, had_code_hash: B256, had_code: Option<Bytecode>) -> Self {
JournalEntry::CodeChange {
address,
had_code_hash,
had_code,
}
}

fn revert(
Expand Down Expand Up @@ -401,10 +414,14 @@ impl JournalEntryTr for JournalEntry {
transient_storage.insert(tkey, had_value);
}
}
JournalEntry::CodeChange { address } => {
JournalEntry::CodeChange {
address,
had_code_hash,
had_code,
} => {
let acc = state.get_mut(&address).unwrap();
acc.info.code_hash = KECCAK_EMPTY;
acc.info.code = None;
acc.info.code_hash = had_code_hash;
acc.info.code = had_code;
}
}
}
Expand Down
82 changes: 79 additions & 3 deletions crates/context/src/journal/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,79 @@ impl<ENTRY: JournalEntryTr> JournalInner<ENTRY> {
self.store(db, address, key, value, skip_cold_load, true, false)
}

/// Load a system account without warming it. Normalize committed previous-
/// transaction lifecycle flags, while preserving any current-transaction warmth.
pub fn system_load_account<DB: Database>(
&mut self,
db: &mut DB,
address: Address,
) -> Result<StateLoad<&mut Account>, DB::Error> {
let account = match self.state.entry(address) {
Entry::Occupied(entry) => {
let account = entry.into_mut();
if account.transaction_id != self.transaction_id {
if account.is_selfdestructed_locally() {
account.selfdestruct();
account.unmark_selfdestructed_locally();
}
account.unmark_created_locally();
account.transaction_id = self.transaction_id;
account.mark_cold();
}
account
}
Entry::Vacant(entry) => {
let mut account = if let Some(info) = db.basic(address)? {
Account::from(info)
} else {
Account::new_not_existing(self.transaction_id)
};
account.transaction_id = self.transaction_id;
account.mark_cold();
entry.insert(account)
}
};
let is_cold = account.is_cold_transaction_id(self.transaction_id)
&& self.warm_addresses.is_cold(&address);
Ok(StateLoad::new(account, is_cold, false))
}

/// System storage read: cache values and flags, but never warm or touch.
pub fn system_storage<DB: Database>(
&mut self,
db: &mut DB,
address: Address,
key: StorageKey,
) -> Result<StateLoad<StorageValue>, DB::Error> {
self.system_load_account(db, address)?;
self.load_inner(db, address, key, false, false)
.map_err(JournalLoadError::unwrap_db_error)
}

/// Journaled system write with explicit visibility and a rollback-safe touch.
pub fn system_store<DB: Database>(
&mut self,
db: &mut DB,
address: Address,
key: StorageKey,
value: FlaggedStorage,
) -> Result<StateLoad<SStoreResult>, DB::Error> {
self.system_load_account(db, address)?;
let result = self
.store(
db,
address,
key,
value.value,
false,
value.is_private,
false,
)
.map_err(JournalLoadError::unwrap_db_error)?;
self.touch(address);
Ok(result)
}

/// Creates new [`JournalInner`].
///
/// `warm_preloaded_addresses` is used to determine if address is considered warm loaded.
Expand Down Expand Up @@ -263,7 +336,10 @@ impl<ENTRY: JournalEntryTr> JournalInner<ENTRY> {
let account = self.state.get_mut(&address).unwrap();
Self::touch_account(&mut self.journal, address, account);

self.journal.push(ENTRY::code_changed(address));
let had_code_hash = account.info.code_hash;
let had_code = account.info.code.take();
self.journal
.push(ENTRY::code_changed(address, had_code_hash, had_code));

account.info.code_hash = hash;
account.info.code = Some(code);
Expand Down Expand Up @@ -298,8 +374,8 @@ impl<ENTRY: JournalEntryTr> JournalInner<ENTRY> {
// account balance changed.
self.journal
.push(ENTRY::balance_changed(address, old_balance));
// account is touched.
self.journal.push(ENTRY::account_touched(address));
// Introduce a touch only if the account was previously untouched.
self.touch(address);

if bump_nonce {
// nonce changed.
Expand Down
Loading
Loading