use crate::InMemorySize;
use alloy_consensus::constants::KECCAK_EMPTY;
use alloy_genesis::GenesisAccount;
use alloy_primitives::{keccak256, Bytes, B256, U256};
use alloy_trie::TrieAccount;
use derive_more::Deref;
use revm_bytecode::{Bytecode as RevmBytecode, BytecodeDecodeError};
use revm_state::AccountInfo;
#[cfg(feature = "reth-codec")]
pub mod compact_ids {
pub const LEGACY_RAW_BYTECODE_ID: u8 = 0;
pub const REMOVED_BYTECODE_ID: u8 = 1;
pub const LEGACY_ANALYZED_BYTECODE_ID: u8 = 2;
pub const EIP7702_BYTECODE_ID: u8 = 4;
}
#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
#[cfg_attr(feature = "reth-codec", reth_codecs::add_arbitrary_tests(compact))]
pub struct Account {
pub nonce: u64,
pub balance: U256,
pub bytecode_hash: Option<B256>,
}
impl Account {
pub const fn has_bytecode(&self) -> bool {
self.bytecode_hash.is_some()
}
pub fn is_empty(&self) -> bool {
self.nonce == 0 &&
self.balance.is_zero() &&
self.bytecode_hash.is_none_or(|hash| hash == KECCAK_EMPTY)
}
pub fn get_bytecode_hash(&self) -> B256 {
self.bytecode_hash.unwrap_or(KECCAK_EMPTY)
}
pub fn into_trie_account(self, storage_root: B256) -> TrieAccount {
let Self { nonce, balance, bytecode_hash } = self;
TrieAccount {
nonce,
balance,
storage_root,
code_hash: bytecode_hash.unwrap_or(KECCAK_EMPTY),
}
}
pub fn from_revm_account(revm_account: &revm_state::Account) -> Self {
Self {
balance: revm_account.info.balance,
nonce: revm_account.info.nonce,
bytecode_hash: if revm_account.info.code_hash == revm_primitives::KECCAK_EMPTY {
None
} else {
Some(revm_account.info.code_hash)
},
}
}
}
impl From<revm_state::Account> for Account {
fn from(value: revm_state::Account) -> Self {
Self::from_revm_account(&value)
}
}
impl From<TrieAccount> for Account {
fn from(value: TrieAccount) -> Self {
Self {
balance: value.balance,
nonce: value.nonce,
bytecode_hash: (value.code_hash != KECCAK_EMPTY).then_some(value.code_hash),
}
}
}
impl InMemorySize for Account {
fn size(&self) -> usize {
size_of::<Self>()
}
}
#[cfg(feature = "reth-codec")]
reth_codecs::impl_compression_for_compact!(Account);
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default, PartialEq, Eq, Deref)]
pub struct Bytecode(pub RevmBytecode);
impl Bytecode {
pub fn new_raw(bytes: Bytes) -> Self {
Self(RevmBytecode::new_raw(bytes))
}
#[inline]
pub fn new_raw_checked(bytecode: Bytes) -> Result<Self, BytecodeDecodeError> {
RevmBytecode::new_raw_checked(bytecode).map(Self)
}
}
#[cfg(feature = "reth-codec")]
impl reth_codecs::Compact for Bytecode {
fn to_compact<B>(&self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
use compact_ids::{EIP7702_BYTECODE_ID, LEGACY_ANALYZED_BYTECODE_ID};
let bytecode = self.0.bytes_ref();
buf.put_u32(bytecode.len() as u32);
buf.put_slice(bytecode.as_ref());
let len = if self.0.is_legacy() {
if let Some(jump_table) = self.0.legacy_jump_table() {
buf.put_u8(LEGACY_ANALYZED_BYTECODE_ID);
buf.put_u64(self.0.len() as u64);
let map = jump_table.as_slice();
buf.put_slice(map);
1 + 8 + map.len()
} else {
unreachable!("legacy bytecode must contain a jump table")
}
} else {
buf.put_u8(EIP7702_BYTECODE_ID);
1
};
len + bytecode.len() + 4
}
fn from_compact(mut buf: &[u8], _: usize) -> (Self, &[u8]) {
use byteorder::ReadBytesExt;
use bytes::Buf;
use compact_ids::*;
let len = buf.read_u32::<byteorder::BigEndian>().expect("could not read bytecode length")
as usize;
let bytes = Bytes::from(buf.copy_to_bytes(len));
let variant = buf.read_u8().expect("could not read bytecode variant");
let decoded = match variant {
LEGACY_RAW_BYTECODE_ID => Self(RevmBytecode::new_raw(bytes)),
REMOVED_BYTECODE_ID => {
unreachable!("Junk data in database: checked Bytecode variant was removed")
}
LEGACY_ANALYZED_BYTECODE_ID => {
let original_len = buf.read_u64::<byteorder::BigEndian>().unwrap() as usize;
let jump_table_len = if buf.len() * 8 >= bytes.len() {
bytes.len()
} else {
original_len
};
Self(RevmBytecode::new_analyzed(
bytes,
original_len,
revm_bytecode::JumpTable::from_slice(buf, jump_table_len),
))
}
EIP7702_BYTECODE_ID => {
Self(RevmBytecode::new_raw(bytes))
}
_ => unreachable!("Junk data in database: unknown Bytecode variant"),
};
(decoded, &[])
}
}
#[cfg(feature = "reth-codec")]
reth_codecs::impl_compression_for_compact!(Bytecode);
impl From<&GenesisAccount> for Account {
fn from(value: &GenesisAccount) -> Self {
Self {
nonce: value.nonce.unwrap_or_default(),
balance: value.balance,
bytecode_hash: value.code.as_ref().map(keccak256),
}
}
}
impl From<AccountInfo> for Account {
fn from(revm_acc: AccountInfo) -> Self {
Self {
balance: revm_acc.balance,
nonce: revm_acc.nonce,
bytecode_hash: (!revm_acc.is_empty_code_hash()).then_some(revm_acc.code_hash),
}
}
}
impl From<&AccountInfo> for Account {
fn from(revm_acc: &AccountInfo) -> Self {
Self {
balance: revm_acc.balance,
nonce: revm_acc.nonce,
bytecode_hash: (!revm_acc.is_empty_code_hash()).then_some(revm_acc.code_hash),
}
}
}
impl From<Account> for AccountInfo {
fn from(reth_acc: Account) -> Self {
Self {
balance: reth_acc.balance,
nonce: reth_acc.nonce,
code_hash: reth_acc.bytecode_hash.unwrap_or(KECCAK_EMPTY),
code: None,
account_id: None,
}
}
}
#[cfg(all(test, feature = "std", feature = "reth-codec"))]
mod tests {
use super::*;
use alloy_primitives::{hex_literal::hex, B256, U256};
use reth_codecs::Compact;
use revm_bytecode::JumpTable;
#[test]
fn test_account() {
let mut buf = vec![];
let mut acc = Account::default();
let len = acc.to_compact(&mut buf);
assert_eq!(len, 2);
acc.balance = U256::from(2);
let len = acc.to_compact(&mut buf);
assert_eq!(len, 3);
acc.nonce = 2;
let len = acc.to_compact(&mut buf);
assert_eq!(len, 4);
}
#[test]
fn test_empty_account() {
let mut acc = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None };
assert!(acc.is_empty());
acc.bytecode_hash = Some(KECCAK_EMPTY);
assert!(acc.is_empty());
acc.balance = U256::from(2);
assert!(!acc.is_empty());
acc.balance = U256::ZERO;
acc.nonce = 10;
assert!(!acc.is_empty());
acc.nonce = 0;
acc.bytecode_hash = Some(B256::from(U256::ZERO));
assert!(!acc.is_empty());
}
#[test]
#[ignore]
fn test_bytecode() {
let mut buf = vec![];
let bytecode = Bytecode::new_raw(Bytes::default());
let len = bytecode.to_compact(&mut buf);
assert_eq!(len, 14);
let mut buf = vec![];
let bytecode = Bytecode::new_raw(Bytes::from(&hex!("ffff")));
let len = bytecode.to_compact(&mut buf);
assert_eq!(len, 17);
let mut buf = vec![];
let bytecode = Bytecode(RevmBytecode::new_analyzed(
Bytes::from(&hex!("ff00")),
2,
JumpTable::from_slice(&[0], 2),
));
let len = bytecode.to_compact(&mut buf);
assert_eq!(len, 16);
let (decoded, remainder) = Bytecode::from_compact(&buf, len);
assert_eq!(decoded, bytecode);
assert!(remainder.is_empty());
}
#[test]
fn test_account_has_bytecode() {
let acc_no_bytecode = Account { nonce: 1, balance: U256::from(1000), bytecode_hash: None };
assert!(!acc_no_bytecode.has_bytecode(), "Account should not have bytecode");
let acc_empty_bytecode =
Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(KECCAK_EMPTY) };
assert!(acc_empty_bytecode.has_bytecode(), "Account should have bytecode");
let acc_with_bytecode = Account {
nonce: 1,
balance: U256::from(1000),
bytecode_hash: Some(B256::from_slice(&[0x11u8; 32])),
};
assert!(acc_with_bytecode.has_bytecode(), "Account should have bytecode");
}
#[test]
fn test_account_get_bytecode_hash() {
let acc_no_bytecode = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None };
assert_eq!(acc_no_bytecode.get_bytecode_hash(), KECCAK_EMPTY, "Should return KECCAK_EMPTY");
let acc_empty_bytecode =
Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(KECCAK_EMPTY) };
assert_eq!(
acc_empty_bytecode.get_bytecode_hash(),
KECCAK_EMPTY,
"Should return KECCAK_EMPTY"
);
let bytecode_hash = B256::from_slice(&[0x11u8; 32]);
let acc_with_bytecode =
Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(bytecode_hash) };
assert_eq!(
acc_with_bytecode.get_bytecode_hash(),
bytecode_hash,
"Should return the bytecode hash"
);
}
}