use eth_valkyoth_codec::{DecodeError, DecodeLimits, RlpItem, RlpList, RlpScalar};
use eth_valkyoth_primitives::{Address, B256, ChainId, Gas, Nonce, Wei};
use super::{TransactionEnvelope, decode_transaction_envelope};
use crate::transaction::fields::{
ADDRESS_BYTES, decode_chain_id as decode_shared_chain_id, decode_to as decode_shared_to,
decode_u64_field as decode_shared_u64_field, decode_u256_field as decode_shared_u256_field,
next_list as next_shared_list, next_scalar as next_shared_scalar,
};
mod error;
pub use error::{AccessListTransactionDecodeError, AccessListTransactionDecodeErrorCategory};
pub const ACCESS_LIST_TRANSACTION_TYPE: u8 = 0x01;
pub const ACCESS_LIST_TRANSACTION_FIELD_COUNT: usize = 11;
const ACCESS_LIST_ENTRY_FIELD_COUNT: usize = 2;
const B256_BYTES: usize = 32;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct UnvalidatedAccessListTransaction<'a> {
pub chain_id: ChainId,
pub nonce: Nonce,
pub gas_price: Wei,
pub gas_limit: Gas,
pub to: AccessListTransactionTo,
pub value: Wei,
pub input: &'a [u8],
pub access_list: AccessList<'a>,
pub y_parity: SignatureYParity,
pub r: [u8; 32],
pub s: [u8; 32],
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AccessListTransactionTo {
Create,
Call(Address),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SignatureYParity {
Even,
Odd,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct InvalidSignatureYParity {
value: u64,
}
impl InvalidSignatureYParity {
#[must_use]
pub const fn value(self) -> u64 {
self.value
}
}
impl SignatureYParity {
pub const fn try_new(value: u64) -> Result<Self, InvalidSignatureYParity> {
match value {
0 => Ok(Self::Even),
1 => Ok(Self::Odd),
_ => Err(InvalidSignatureYParity { value }),
}
}
#[must_use]
pub const fn get(self) -> u8 {
match self {
Self::Even => 0,
Self::Odd => 1,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AccessList<'a> {
list: RlpList<'a>,
storage_key_count: usize,
}
impl<'a> AccessList<'a> {
#[must_use]
pub const fn address_count(self) -> usize {
self.list.item_count()
}
#[must_use]
pub const fn storage_key_count(self) -> usize {
self.storage_key_count
}
#[must_use]
pub const fn entries(self) -> AccessListEntries<'a> {
AccessListEntries {
items: self.list.items(),
}
}
pub(crate) const fn encoded_rlp_len(self) -> usize {
self.list.encoded_len()
}
pub(crate) fn encode_rlp(self, output: &mut [u8]) -> Result<usize, DecodeError> {
eth_valkyoth_codec::encode_decoded_list(self.list, output)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AccessListEntry<'a> {
pub address: Address,
pub storage_keys: AccessListStorageKeys<'a>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AccessListStorageKeys<'a> {
list: RlpList<'a>,
}
impl<'a> AccessListStorageKeys<'a> {
#[must_use]
pub const fn len(self) -> usize {
self.list.item_count()
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.list.is_empty()
}
#[must_use]
pub const fn keys(self) -> AccessListStorageKeyItems<'a> {
AccessListStorageKeyItems {
items: self.list.items(),
}
}
}
#[derive(Clone, Debug)]
pub struct AccessListEntries<'a> {
items: eth_valkyoth_codec::RlpListItems<'a>,
}
impl<'a> Iterator for AccessListEntries<'a> {
type Item = Result<AccessListEntry<'a>, AccessListTransactionDecodeError>;
fn next(&mut self) -> Option<Self::Item> {
self.items
.next()
.map(|item| decode_access_list_entry_item(item).map_err(map_access_list_decode_error))
}
}
impl core::iter::FusedIterator for AccessListEntries<'_> {}
#[derive(Clone, Debug)]
pub struct AccessListStorageKeyItems<'a> {
items: eth_valkyoth_codec::RlpListItems<'a>,
}
impl Iterator for AccessListStorageKeyItems<'_> {
type Item = Result<B256, AccessListTransactionDecodeError>;
fn next(&mut self) -> Option<Self::Item> {
self.items
.next()
.map(|item| decode_storage_key_item(item).map_err(map_access_list_decode_error))
}
}
impl core::iter::FusedIterator for AccessListStorageKeyItems<'_> {}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AccessListTransactionField {
Payload,
ChainId,
Nonce,
GasPrice,
GasLimit,
To,
Value,
Data,
AccessList,
SignatureYParity,
SignatureR,
SignatureS,
}
pub fn decode_access_list_transaction<'a>(
input: &'a [u8],
limits: DecodeLimits,
) -> Result<UnvalidatedAccessListTransaction<'a>, AccessListTransactionDecodeError> {
match decode_transaction_envelope(input, limits)
.map_err(AccessListTransactionDecodeError::Envelope)?
{
TransactionEnvelope::Typed(typed)
if typed.transaction_type.get() == ACCESS_LIST_TRANSACTION_TYPE =>
{
decode_access_list_payload(typed.payload, limits)
}
TransactionEnvelope::Typed(typed) => {
Err(AccessListTransactionDecodeError::WrongTransactionType {
type_byte: typed.transaction_type.get(),
})
}
TransactionEnvelope::Legacy(_) => {
Err(AccessListTransactionDecodeError::WrongTransactionType { type_byte: 0 })
}
}
}
fn decode_access_list_payload<'a>(
payload: &'a [u8],
limits: DecodeLimits,
) -> Result<UnvalidatedAccessListTransaction<'a>, AccessListTransactionDecodeError> {
let list = eth_valkyoth_codec::decode_rlp_list(payload, limits)
.map_err(|source| field_error(AccessListTransactionField::Payload, source))?;
if list.item_count() != ACCESS_LIST_TRANSACTION_FIELD_COUNT {
return Err(AccessListTransactionDecodeError::WrongFieldCount {
expected: ACCESS_LIST_TRANSACTION_FIELD_COUNT,
found: list.item_count(),
});
}
let mut fields = list.items();
let chain_id = decode_shared_chain_id(
&mut fields,
AccessListTransactionField::ChainId,
field_error,
)?;
let nonce = Nonce::new(decode_shared_u64_field(
&mut fields,
AccessListTransactionField::Nonce,
field_error,
)?);
let gas_price = Wei::from_be_bytes(decode_shared_u256_field(
&mut fields,
AccessListTransactionField::GasPrice,
field_error,
)?);
let gas_limit = Gas::new(decode_shared_u64_field(
&mut fields,
AccessListTransactionField::GasLimit,
field_error,
)?);
let to = decode_shared_to(
next_shared_scalar(&mut fields, AccessListTransactionField::To, field_error)?,
|found| AccessListTransactionDecodeError::InvalidToLength { found },
)?;
let value = Wei::from_be_bytes(decode_shared_u256_field(
&mut fields,
AccessListTransactionField::Value,
field_error,
)?);
let input =
next_shared_scalar(&mut fields, AccessListTransactionField::Data, field_error)?.payload();
limits
.check_single_allocation_limit(input.len())
.map_err(|source| field_error(AccessListTransactionField::Data, source))?;
let access_list = decode_access_list(next_shared_list(
&mut fields,
AccessListTransactionField::AccessList,
field_error,
)?)
.map_err(map_access_list_decode_error)?;
let y_parity = SignatureYParity::try_new(decode_shared_u64_field(
&mut fields,
AccessListTransactionField::SignatureYParity,
field_error,
)?)
.map_err(|error| AccessListTransactionDecodeError::InvalidYParity {
value: error.value(),
})?;
let r = decode_shared_u256_field(
&mut fields,
AccessListTransactionField::SignatureR,
field_error,
)?;
let s = decode_shared_u256_field(
&mut fields,
AccessListTransactionField::SignatureS,
field_error,
)?;
Ok(UnvalidatedAccessListTransaction {
chain_id,
nonce,
gas_price,
gas_limit,
to,
value,
input,
access_list,
y_parity,
r,
s,
})
}
pub(crate) fn decode_access_list(
list: RlpList<'_>,
) -> Result<AccessList<'_>, AccessListDecodeError> {
let mut storage_key_count = 0usize;
for item in list.items() {
let entry = decode_access_list_entry_item(item)?;
storage_key_count =
eth_valkyoth_codec::checked_len_add(storage_key_count, entry.storage_keys.len())
.map_err(AccessListDecodeError::FieldDecode)?;
}
Ok(AccessList {
list,
storage_key_count,
})
}
fn decode_access_list_entry_item(
item: Result<RlpItem<'_>, DecodeError>,
) -> Result<AccessListEntry<'_>, AccessListDecodeError> {
let item = item.map_err(AccessListDecodeError::FieldDecode)?;
let RlpItem::List(list) = item else {
return Err(AccessListDecodeError::FieldDecode(
DecodeError::UnexpectedScalar,
));
};
if list.item_count() != ACCESS_LIST_ENTRY_FIELD_COUNT {
return Err(AccessListDecodeError::InvalidAccessListEntryFieldCount {
found: list.item_count(),
});
}
let mut fields = list.items();
let address = decode_access_list_address(next_shared_scalar(
&mut fields,
AccessListTransactionField::AccessList,
|_, source| AccessListDecodeError::FieldDecode(source),
)?)?;
let storage_keys = AccessListStorageKeys {
list: next_shared_list(
&mut fields,
AccessListTransactionField::AccessList,
|_, source| AccessListDecodeError::FieldDecode(source),
)?,
};
for key in storage_keys.list.items() {
let _ = decode_storage_key_item(key)?;
}
Ok(AccessListEntry {
address,
storage_keys,
})
}
fn decode_storage_key_item(
item: Result<RlpItem<'_>, DecodeError>,
) -> Result<B256, AccessListDecodeError> {
let item = item.map_err(AccessListDecodeError::FieldDecode)?;
let RlpItem::Scalar(scalar) = item else {
return Err(AccessListDecodeError::FieldDecode(
DecodeError::UnexpectedList,
));
};
let found = scalar.payload().len();
let bytes: [u8; B256_BYTES] = scalar
.payload()
.try_into()
.map_err(|_| AccessListDecodeError::InvalidStorageKeyLength { found })?;
Ok(B256::from_bytes(bytes))
}
fn decode_access_list_address(scalar: RlpScalar<'_>) -> Result<Address, AccessListDecodeError> {
let found = scalar.payload().len();
let bytes: [u8; ADDRESS_BYTES] = scalar
.payload()
.try_into()
.map_err(|_| AccessListDecodeError::InvalidAccessListAddressLength { found })?;
Ok(Address::from_bytes(bytes))
}
const fn field_error(
field: AccessListTransactionField,
source: DecodeError,
) -> AccessListTransactionDecodeError {
AccessListTransactionDecodeError::FieldDecode { field, source }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum AccessListDecodeError {
FieldDecode(DecodeError),
InvalidAccessListEntryFieldCount { found: usize },
InvalidAccessListAddressLength { found: usize },
InvalidStorageKeyLength { found: usize },
}
pub(crate) const fn map_access_list_decode_error(
error: AccessListDecodeError,
) -> AccessListTransactionDecodeError {
match error {
AccessListDecodeError::FieldDecode(source) => {
field_error(AccessListTransactionField::AccessList, source)
}
AccessListDecodeError::InvalidAccessListEntryFieldCount { found } => {
AccessListTransactionDecodeError::InvalidAccessListEntryFieldCount { found }
}
AccessListDecodeError::InvalidAccessListAddressLength { found } => {
AccessListTransactionDecodeError::InvalidAccessListAddressLength { found }
}
AccessListDecodeError::InvalidStorageKeyLength { found } => {
AccessListTransactionDecodeError::InvalidStorageKeyLength { found }
}
}
}