#[cfg(not(feature = "std"))]
use alloc as std;
use std::{boxed::Box, string::ToString};
use alloy_evm::{
block::{BlockExecutionError, BlockValidationError},
Evm,
};
use alloy_primitives::{Address, B256, U256};
use revm::{
context_interface::result::ResultAndState,
database::State,
state::{Account, EvmState},
Database, Inspector,
};
use crate::{
block::hardfork::MegaHardforks, ExternalEnvTypes, MegaContext, MegaEvm, MegaHaltReason,
};
#[inline]
pub(crate) fn transact_blockhashes_contract_call<H, DB, INSP, ExtEnvs>(
spec: H,
parent_block_hash: B256,
evm: &mut MegaEvm<DB, INSP, ExtEnvs>,
) -> Result<Option<ResultAndState<MegaHaltReason>>, BlockExecutionError>
where
H: MegaHardforks,
DB: alloy_evm::Database,
ExtEnvs: ExternalEnvTypes,
INSP: Inspector<MegaContext<DB, ExtEnvs>>,
{
let block_timestamp: u64 = evm.block().timestamp.saturating_to();
if !spec.is_prague_active_at_timestamp(block_timestamp) {
return Ok(None);
}
if evm.block().number.is_zero() {
return Ok(None);
}
let res = if spec.is_rex_5_active_at_timestamp(block_timestamp) {
let gas_limit =
evm.block().gas_limit.max(crate::constants::rex5::SYSTEM_CALL_GAS_LIMIT_FLOOR);
evm.transact_system_call_with_gas_limit(
alloy_eips::eip4788::SYSTEM_ADDRESS,
alloy_eips::eip2935::HISTORY_STORAGE_ADDRESS,
parent_block_hash.0.into(),
gas_limit,
)
} else {
evm.transact_system_call(
alloy_eips::eip4788::SYSTEM_ADDRESS,
alloy_eips::eip2935::HISTORY_STORAGE_ADDRESS,
parent_block_hash.0.into(),
)
};
match res {
Ok(res) => Ok(Some(res)),
Err(e) => {
Err(BlockValidationError::BlockHashContractCall { message: e.to_string() }.into())
}
}
}
#[inline]
pub(crate) fn transact_beacon_root_contract_call<H, DB, INSP, ExtEnvs>(
spec: H,
parent_beacon_block_root: Option<B256>,
evm: &mut MegaEvm<DB, INSP, ExtEnvs>,
) -> Result<Option<ResultAndState<MegaHaltReason>>, BlockExecutionError>
where
H: MegaHardforks,
DB: alloy_evm::Database,
ExtEnvs: ExternalEnvTypes,
INSP: Inspector<MegaContext<DB, ExtEnvs>>,
{
let block_timestamp: u64 = evm.block().timestamp.saturating_to();
if !spec.is_cancun_active_at_timestamp(block_timestamp) {
return Ok(None);
}
let parent_beacon_block_root =
parent_beacon_block_root.ok_or(BlockValidationError::MissingParentBeaconBlockRoot)?;
if evm.block().number.is_zero() {
if !parent_beacon_block_root.is_zero() {
return Err(BlockValidationError::CancunGenesisParentBeaconBlockRootNotZero {
parent_beacon_block_root,
}
.into());
}
return Ok(None);
}
let res = if spec.is_rex_5_active_at_timestamp(block_timestamp) {
let gas_limit =
evm.block().gas_limit.max(crate::constants::rex5::SYSTEM_CALL_GAS_LIMIT_FLOOR);
evm.transact_system_call_with_gas_limit(
alloy_eips::eip4788::SYSTEM_ADDRESS,
alloy_eips::eip4788::BEACON_ROOTS_ADDRESS,
parent_beacon_block_root.0.into(),
gas_limit,
)
} else {
evm.transact_system_call(
alloy_eips::eip4788::SYSTEM_ADDRESS,
alloy_eips::eip4788::BEACON_ROOTS_ADDRESS,
parent_beacon_block_root.0.into(),
)
};
match res {
Ok(res) => Ok(Some(res)),
Err(e) => Err(BlockValidationError::BeaconRootContractCall {
parent_beacon_block_root: Box::new(parent_beacon_block_root),
message: e.to_string(),
}
.into()),
}
}
pub(crate) fn transact_balance_increments<DB: Database>(
balances: impl IntoIterator<Item = (Address, u128)>,
db: &mut State<DB>,
) -> Result<Option<EvmState>, DB::Error> {
let balances = balances.into_iter();
let mut state = EvmState::default();
for (address, balance_increment) in balances {
if balance_increment == 0 {
continue;
}
let cache_account = db.load_cache_account(address)?;
let account_info = cache_account.account_info().unwrap_or_default();
let mut account = Account::default().with_info(account_info);
account.info.balance += U256::from(balance_increment);
account.mark_touch();
state.insert(address, account);
}
Ok(Some(state))
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::address;
use revm::{database::InMemoryDB, state::AccountInfo, DatabaseCommit};
#[test]
fn test_balance_increment_commit_equivalence() {
let addr1 = address!("0x1000000000000000000000000000000000000001");
let addr2 = address!("0x2000000000000000000000000000000000000002");
let addr3 = address!("0x3000000000000000000000000000000000000003");
let setup_db = |db: &mut InMemoryDB| {
for (addr, balance, nonce) in [(addr1, 1000u64, 5u64), (addr2, 2000u64, 10u64)] {
db.insert_account_info(
addr,
AccountInfo {
balance: U256::from(balance),
nonce,
code_hash: alloy_primitives::B256::ZERO,
code: None,
},
);
}
};
let mut db1 = InMemoryDB::default();
setup_db(&mut db1);
let mut state1 = State::builder().with_database(&mut db1).build();
let mut db2 = InMemoryDB::default();
setup_db(&mut db2);
let mut state2 = State::builder().with_database(&mut db2).build();
let balance_increments = vec![(addr1, 100u128), (addr2, 200u128), (addr3, 300u128)];
state1
.increment_balances(balance_increments.clone())
.expect("increment_balances should succeed");
let result_state = transact_balance_increments(balance_increments.clone(), &mut state2)
.expect("transact_balance_increments should succeed")
.expect("Should return state");
state2.commit(result_state);
for (addr, _expected_increment) in balance_increments {
let account1 = state1.load_cache_account(addr).expect("Should load from state1");
let account2 = state2.load_cache_account(addr).expect("Should load from state2");
let info1 = account1.account_info().expect("Should have account info");
let info2 = account2.account_info().expect("Should have account info");
assert_eq!(
info1.balance, info2.balance,
"Balance for {:?} should be identical after both methods",
addr
);
assert_eq!(info1.nonce, info2.nonce, "Nonce for {:?} should be identical", addr);
assert_eq!(
info1.code_hash, info2.code_hash,
"Code hash for {:?} should be identical",
addr
);
assert_eq!(
account1.status, account2.status,
"Account status for {:?} should be identical after both methods",
addr
);
}
}
}