use eth_valkyoth_codec::{DecodeError, DecodeLimits, RlpInteger, RlpItem, RlpList, RlpScalar};
use eth_valkyoth_primitives::{Address, ChainId, Gas, Nonce, Wei};
use super::access_list::{AccessListDecodeError, decode_access_list};
use super::{AccessList, SignatureYParity, TransactionEnvelope, decode_transaction_envelope};
use crate::transaction::fields::{
ADDRESS_BYTES, decode_chain_id as decode_shared_chain_id, decode_required_address,
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;
mod validity;
pub use error::{
SetCodeAuthorizationField, SetCodeTransactionDecodeError, SetCodeTransactionDecodeErrorCategory,
};
pub use validity::*;
pub const SET_CODE_TRANSACTION_TYPE: u8 = 0x04;
pub const SET_CODE_TRANSACTION_FIELD_COUNT: usize = 13;
pub const SET_CODE_AUTHORIZATION_FIELD_COUNT: usize = 6;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct UnvalidatedSetCodeTransaction<'a> {
pub chain_id: ChainId,
pub nonce: Nonce,
pub max_priority_fee_per_gas: Wei,
pub max_fee_per_gas: Wei,
pub gas_limit: Gas,
pub to: Address,
pub value: Wei,
pub input: &'a [u8],
pub access_list: AccessList<'a>,
pub authorization_list: SetCodeAuthorizationList<'a>,
pub y_parity: SignatureYParity,
pub r: [u8; 32],
pub s: [u8; 32],
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SetCodeAuthorizationChainId {
bytes: [u8; 32],
}
impl SetCodeAuthorizationChainId {
#[must_use]
pub const fn from_be_bytes(bytes: [u8; 32]) -> Self {
Self { bytes }
}
#[must_use]
pub const fn to_be_bytes(self) -> [u8; 32] {
self.bytes
}
#[must_use]
pub fn is_universal(self) -> bool {
self.bytes.iter().all(|byte| *byte == 0)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SetCodeAuthorizationList<'a> {
list: RlpList<'a>,
}
impl<'a> SetCodeAuthorizationList<'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 authorizations(self) -> SetCodeAuthorizationItems<'a> {
SetCodeAuthorizationItems {
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 SetCodeAuthorization {
pub chain_id: SetCodeAuthorizationChainId,
pub address: Address,
pub nonce: Nonce,
pub y_parity: SignatureYParity,
pub r: [u8; 32],
pub s: [u8; 32],
}
#[derive(Clone, Debug)]
pub struct SetCodeAuthorizationItems<'a> {
items: eth_valkyoth_codec::RlpListItems<'a>,
}
impl Iterator for SetCodeAuthorizationItems<'_> {
type Item = Result<SetCodeAuthorization, SetCodeTransactionDecodeError>;
fn next(&mut self) -> Option<Self::Item> {
self.items
.next()
.map(|item| decode_authorization_item(item).map_err(map_authorization_error))
}
}
impl core::iter::FusedIterator for SetCodeAuthorizationItems<'_> {}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SetCodeTransactionField {
Payload,
ChainId,
Nonce,
MaxPriorityFeePerGas,
MaxFeePerGas,
GasLimit,
To,
Value,
Data,
AccessList,
AuthorizationList,
SignatureYParity,
SignatureR,
SignatureS,
}
pub fn decode_set_code_transaction<'a>(
input: &'a [u8],
limits: DecodeLimits,
) -> Result<UnvalidatedSetCodeTransaction<'a>, SetCodeTransactionDecodeError> {
match decode_transaction_envelope(input, limits)
.map_err(SetCodeTransactionDecodeError::Envelope)?
{
TransactionEnvelope::Typed(typed)
if typed.transaction_type.get() == SET_CODE_TRANSACTION_TYPE =>
{
decode_set_code_payload(typed.payload, limits)
}
TransactionEnvelope::Typed(typed) => {
Err(SetCodeTransactionDecodeError::WrongTransactionType {
type_byte: typed.transaction_type.get(),
})
}
TransactionEnvelope::Legacy(_) => {
Err(SetCodeTransactionDecodeError::WrongTransactionType { type_byte: 0 })
}
}
}
fn decode_set_code_payload<'a>(
payload: &'a [u8],
limits: DecodeLimits,
) -> Result<UnvalidatedSetCodeTransaction<'a>, SetCodeTransactionDecodeError> {
let list = eth_valkyoth_codec::decode_rlp_list(payload, limits)
.map_err(|source| field_error(SetCodeTransactionField::Payload, source))?;
if list.item_count() != SET_CODE_TRANSACTION_FIELD_COUNT {
return Err(SetCodeTransactionDecodeError::WrongFieldCount {
expected: SET_CODE_TRANSACTION_FIELD_COUNT,
found: list.item_count(),
});
}
let mut fields = list.items();
let chain_id =
decode_shared_chain_id(&mut fields, SetCodeTransactionField::ChainId, field_error)?;
let nonce = Nonce::new(decode_shared_u64_field(
&mut fields,
SetCodeTransactionField::Nonce,
field_error,
)?);
let max_priority_fee_per_gas = Wei::from_be_bytes(decode_shared_u256_field(
&mut fields,
SetCodeTransactionField::MaxPriorityFeePerGas,
field_error,
)?);
let max_fee_per_gas = Wei::from_be_bytes(decode_shared_u256_field(
&mut fields,
SetCodeTransactionField::MaxFeePerGas,
field_error,
)?);
let gas_limit = Gas::new(decode_shared_u64_field(
&mut fields,
SetCodeTransactionField::GasLimit,
field_error,
)?);
let to = decode_required_address(
next_shared_scalar(&mut fields, SetCodeTransactionField::To, field_error)?,
|found| SetCodeTransactionDecodeError::InvalidToLength { found },
)?;
let value = Wei::from_be_bytes(decode_shared_u256_field(
&mut fields,
SetCodeTransactionField::Value,
field_error,
)?);
let input =
next_shared_scalar(&mut fields, SetCodeTransactionField::Data, field_error)?.payload();
limits
.check_single_allocation_limit(input.len())
.map_err(|source| field_error(SetCodeTransactionField::Data, source))?;
let access_list = decode_access_list(next_shared_list(
&mut fields,
SetCodeTransactionField::AccessList,
field_error,
)?)
.map_err(map_access_list_error)?;
let authorization_list = decode_authorization_list(next_shared_list(
&mut fields,
SetCodeTransactionField::AuthorizationList,
field_error,
)?)
.map_err(map_authorization_error)?;
let y_parity = SignatureYParity::try_new(decode_shared_u64_field(
&mut fields,
SetCodeTransactionField::SignatureYParity,
field_error,
)?)
.map_err(|error| SetCodeTransactionDecodeError::InvalidYParity {
value: error.value(),
})?;
let r = decode_shared_u256_field(
&mut fields,
SetCodeTransactionField::SignatureR,
field_error,
)?;
let s = decode_shared_u256_field(
&mut fields,
SetCodeTransactionField::SignatureS,
field_error,
)?;
Ok(UnvalidatedSetCodeTransaction {
chain_id,
nonce,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
to,
value,
input,
access_list,
authorization_list,
y_parity,
r,
s,
})
}
fn decode_authorization_list(
list: RlpList<'_>,
) -> Result<SetCodeAuthorizationList<'_>, SetCodeAuthorizationDecodeError> {
for item in list.items() {
let _ = decode_authorization_item(item)?;
}
Ok(SetCodeAuthorizationList { list })
}
fn decode_authorization_item(
item: Result<RlpItem<'_>, DecodeError>,
) -> Result<SetCodeAuthorization, SetCodeAuthorizationDecodeError> {
let item = item.map_err(SetCodeAuthorizationDecodeError::TupleDecode)?;
let RlpItem::List(list) = item else {
return Err(SetCodeAuthorizationDecodeError::TupleDecode(
DecodeError::UnexpectedScalar,
));
};
if list.item_count() != SET_CODE_AUTHORIZATION_FIELD_COUNT {
return Err(
SetCodeAuthorizationDecodeError::InvalidAuthorizationFieldCount {
found: list.item_count(),
},
);
}
let mut fields = list.items();
let chain_id = decode_authorization_chain_id(next_shared_scalar(
&mut fields,
SetCodeTransactionField::AuthorizationList,
|_, source| auth_field_error(SetCodeAuthorizationField::ChainId, source),
)?)?;
let address = decode_authorization_address(next_shared_scalar(
&mut fields,
SetCodeTransactionField::AuthorizationList,
|_, source| auth_field_error(SetCodeAuthorizationField::Address, source),
)?)?;
let nonce = Nonce::new(decode_shared_u64_field(
&mut fields,
SetCodeTransactionField::AuthorizationList,
|_, source| auth_field_error(SetCodeAuthorizationField::Nonce, source),
)?);
let y_parity = SignatureYParity::try_new(decode_shared_u64_field(
&mut fields,
SetCodeTransactionField::AuthorizationList,
|_, source| auth_field_error(SetCodeAuthorizationField::YParity, source),
)?)
.map_err(
|error| SetCodeAuthorizationDecodeError::InvalidAuthorizationYParity {
value: error.value(),
},
)?;
let r = decode_shared_u256_field(
&mut fields,
SetCodeTransactionField::AuthorizationList,
|_, source| auth_field_error(SetCodeAuthorizationField::R, source),
)?;
let s = decode_shared_u256_field(
&mut fields,
SetCodeTransactionField::AuthorizationList,
|_, source| auth_field_error(SetCodeAuthorizationField::S, source),
)?;
Ok(SetCodeAuthorization {
chain_id,
address,
nonce,
y_parity,
r,
s,
})
}
fn decode_authorization_chain_id(
scalar: RlpScalar<'_>,
) -> Result<SetCodeAuthorizationChainId, SetCodeAuthorizationDecodeError> {
let bytes = RlpInteger::try_from_scalar(scalar)
.and_then(RlpInteger::to_be_bytes32)
.map_err(|source| auth_field_error(SetCodeAuthorizationField::ChainId, source))?;
Ok(SetCodeAuthorizationChainId::from_be_bytes(bytes))
}
fn decode_authorization_address(
scalar: RlpScalar<'_>,
) -> Result<Address, SetCodeAuthorizationDecodeError> {
let found = scalar.payload().len();
let bytes: [u8; ADDRESS_BYTES] = scalar.payload().try_into().map_err(|_| {
SetCodeAuthorizationDecodeError::InvalidAuthorizationAddressLength { found }
})?;
Ok(Address::from_bytes(bytes))
}
const fn auth_field_error(
field: SetCodeAuthorizationField,
source: DecodeError,
) -> SetCodeAuthorizationDecodeError {
SetCodeAuthorizationDecodeError::FieldDecode { field, source }
}
const fn field_error(
field: SetCodeTransactionField,
source: DecodeError,
) -> SetCodeTransactionDecodeError {
SetCodeTransactionDecodeError::FieldDecode { field, source }
}
const fn map_access_list_error(error: AccessListDecodeError) -> SetCodeTransactionDecodeError {
match error {
AccessListDecodeError::FieldDecode(source) => {
field_error(SetCodeTransactionField::AccessList, source)
}
AccessListDecodeError::InvalidAccessListEntryFieldCount { found } => {
SetCodeTransactionDecodeError::InvalidAccessListEntryFieldCount { found }
}
AccessListDecodeError::InvalidAccessListAddressLength { found } => {
SetCodeTransactionDecodeError::InvalidAccessListAddressLength { found }
}
AccessListDecodeError::InvalidStorageKeyLength { found } => {
SetCodeTransactionDecodeError::InvalidStorageKeyLength { found }
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum SetCodeAuthorizationDecodeError {
TupleDecode(DecodeError),
FieldDecode {
field: SetCodeAuthorizationField,
source: DecodeError,
},
InvalidAuthorizationFieldCount {
found: usize,
},
InvalidAuthorizationAddressLength {
found: usize,
},
InvalidAuthorizationYParity {
value: u64,
},
}
const fn map_authorization_error(
error: SetCodeAuthorizationDecodeError,
) -> SetCodeTransactionDecodeError {
match error {
SetCodeAuthorizationDecodeError::TupleDecode(source) => {
field_error(SetCodeTransactionField::AuthorizationList, source)
}
SetCodeAuthorizationDecodeError::FieldDecode { field, source } => {
SetCodeTransactionDecodeError::AuthorizationFieldDecode { field, source }
}
SetCodeAuthorizationDecodeError::InvalidAuthorizationFieldCount { found } => {
SetCodeTransactionDecodeError::InvalidAuthorizationFieldCount { found }
}
SetCodeAuthorizationDecodeError::InvalidAuthorizationAddressLength { found } => {
SetCodeTransactionDecodeError::InvalidAuthorizationAddressLength { found }
}
SetCodeAuthorizationDecodeError::InvalidAuthorizationYParity { value } => {
SetCodeTransactionDecodeError::InvalidAuthorizationYParity { value }
}
}
}