Skip to content

Commit be425a5

Browse files
RealiCZflyq
andauthored
chore: bump version to 1.5.2 (#295)
Co-authored-by: liquan.mega <flyq951@gmail.com>
1 parent 08e745c commit be425a5

4 files changed

Lines changed: 62 additions & 8 deletions

File tree

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ exclude = []
1313
# https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html
1414
resolver = "2"
1515
[workspace.package]
16-
version = "1.5.1"
16+
version = "1.5.2"
1717
edition = "2021"
1818
rust-version = "1.86"
1919
license = "MIT OR Apache-2.0"

crates/mega-evm/src/evm/host.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,13 @@ impl<DB: revm::Database> JournalInspectTr for Journal<DB> {
439439
) -> Result<&EvmStorageSlot, Self::DBError> {
440440
let transaction_id = self.transaction_id;
441441
let is_rex4_enabled = spec.is_enabled(MegaSpecId::REX4);
442+
// EIP-7702 storage semantics: storage belongs to the original address (delegator),
443+
// not the delegate. So `is_created` must be checked on the original address — an
444+
// EOA delegating via 7702 is never CREATEd, so its flag is always false. Checking
445+
// the delegate's flag instead would mistakenly short-circuit storage reads when the
446+
// delegate happens to be a freshly-CREATEd contract in the same tx, corrupting
447+
// SSTORE accounting (gas / kv_updates / data_size) on the delegator's slots.
448+
let is_newly_created = inspect_account(self, address)?.is_created();
442449
// REX4+: storage belongs to the original address, not the delegate — do not follow
443450
// EIP-7702 delegation here (matching upstream revm's sload behavior).
444451
// Pre-REX4: follows delegation (original behavior).
@@ -457,9 +464,15 @@ impl<DB: revm::Database> JournalInspectTr for Journal<DB> {
457464
};
458465
return Ok(account.storage.get(&key).unwrap());
459466
}
460-
// Slot doesn't exist, load from DB and insert
461-
let slot = self.database.storage(address, key)?;
462-
let mut slot = EvmStorageSlot::new(slot, transaction_id);
467+
// Slot doesn't exist. For newly-created accounts, post-CREATE storage is
468+
// guaranteed empty (EIP-161 / EIP-6780), so return ZERO without touching the DB.
469+
// Querying here would otherwise generate a witness lookup for a slot that has no
470+
// meaningful pre-state value — which fails for stateless replay when CREATE lands
471+
// on a pre-funded address (its `Loaded` cache status bypasses revm's
472+
// `State::storage` short-circuit and exposes the call to the witness backend).
473+
let slot_value =
474+
if is_newly_created { U256::ZERO } else { self.database.storage(address, key)? };
475+
let mut slot = EvmStorageSlot::new(slot_value, transaction_id);
463476
// deliberately mark the slot as cold since we are only inspecting it, not warming it
464477
slot.mark_cold();
465478
// Load account again to bypass the borrow checker and insert the slot

crates/mega-evm/tests/mini_rex/db_error.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,47 @@ fn test_call_with_transfer_db_error_on_inspect_account() {
135135
}
136136
}
137137

138+
/// Regression: `inspect_storage` must not query `DB::storage` for accounts created in the
139+
/// current transaction. Their post-CREATE storage is guaranteed empty (EIP-161 / EIP-6780),
140+
/// so any DB read returns ZERO at best — and fails outright under stateless replay, where
141+
/// no witness exists for these slots when CREATE lands on a pre-funded address (its
142+
/// `Loaded` cache status bypasses revm's `State::storage` short-circuit).
143+
///
144+
/// The injected DB is configured to error on every `storage()` read of slot 0 at the
145+
/// to-be-created address. With the fix, `inspect_storage` short-circuits to ZERO for
146+
/// newly-created accounts and the error is never triggered, so the CREATE succeeds.
147+
/// Without the fix, the constructor's SSTORE pre-read would surface `EVMError::Custom`
148+
/// (mini-rex routes SSTORE through `additional_limit_ext::sstore`, which calls
149+
/// `inspect_storage` to compute the original/present values before writing).
150+
#[test]
151+
fn test_inspect_storage_skips_db_for_newly_created_account() {
152+
// Initcode: SSTORE slot 0 = 0x42; STOP. The SSTORE pre-read goes through
153+
// `inspect_storage` and is the path the fix targets.
154+
let initcode = BytecodeBuilder::default().sstore(U256::ZERO, U256::from(0x42)).stop().build();
155+
156+
// CALLER's nonce starts at 0, so the top-level CREATE deploys to `CALLER.create(0)`.
157+
let created = CALLER.create(0);
158+
159+
let mut inner_db = MemoryDatabase::default();
160+
inner_db.set_account_balance(CALLER, U256::from(100_000_000_000u64));
161+
// Pre-fund the future contract address so its DB cache status is `Loaded` rather than
162+
// `Vacant` — this is the scenario that exposed the original bug.
163+
inner_db.set_account_balance(created, U256::from(1));
164+
165+
let mut db = ErrorInjectingDatabase::new(inner_db);
166+
// The fix means this DB call must never happen. If it does, the test fails.
167+
db.fail_on_storage = Some((created, U256::ZERO));
168+
169+
let result = transact(MegaSpecId::MINI_REX, db, CALLER, None, initcode, U256::ZERO, 10_000_000);
170+
171+
let res = result.expect("CREATE should not surface a DB error");
172+
assert!(
173+
res.result.is_success(),
174+
"CREATE should succeed without DB::storage being queried, got: {:?}",
175+
res.result
176+
);
177+
}
178+
138179
/// When `inspect_account_delegated` fails during STATICCALL (in `wrap_call_with_storage_gas!`),
139180
/// the EVM should halt with `FatalExternalError`.
140181
/// This tests a different code path from CALL-with-transfer: STATICCALL has no value parameter

0 commit comments

Comments
 (0)