Skip to main content

eth_valkyoth_protocol/transaction/
dynamic_fee.rs

1use eth_valkyoth_codec::{DecodeError, DecodeLimits};
2use eth_valkyoth_primitives::{ChainId, Gas, Nonce, Wei};
3
4use super::access_list::{AccessListDecodeError, decode_access_list};
5use super::{
6    AccessList, AccessListTransactionTo, SignatureYParity, TransactionEnvelope,
7    decode_transaction_envelope,
8};
9use crate::transaction::fields::{
10    decode_chain_id as decode_shared_chain_id, decode_to as decode_shared_to,
11    decode_u64_field as decode_shared_u64_field, decode_u256_field as decode_shared_u256_field,
12    next_list as next_shared_list, next_scalar as next_shared_scalar,
13};
14
15mod error;
16
17pub use error::{DynamicFeeTransactionDecodeError, DynamicFeeTransactionDecodeErrorCategory};
18
19/// EIP-1559 dynamic-fee transaction type byte.
20pub const DYNAMIC_FEE_TRANSACTION_TYPE: u8 = 0x02;
21/// Number of fields in an EIP-1559 dynamic-fee transaction payload.
22pub const DYNAMIC_FEE_TRANSACTION_FIELD_COUNT: usize = 12;
23
24/// EIP-1559 transaction call/create target.
25pub type DynamicFeeTransactionTo = AccessListTransactionTo;
26
27/// Borrowed EIP-1559 transaction decoded only into field domains.
28///
29/// This type is intentionally unvalidated: no sender recovery, signature
30/// validity, gas accounting, account-state check, fee-order check, duplicate
31/// access-list policy, or fork validity is performed.
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct UnvalidatedDynamicFeeTransaction<'a> {
34    /// Chain ID encoded in the signed transaction domain.
35    pub chain_id: ChainId,
36    /// Account nonce.
37    pub nonce: Nonce,
38    /// Maximum priority fee per gas in wei.
39    pub max_priority_fee_per_gas: Wei,
40    /// Maximum total fee per gas in wei.
41    pub max_fee_per_gas: Wei,
42    /// Gas limit.
43    pub gas_limit: Gas,
44    /// Call or contract-creation target.
45    pub to: DynamicFeeTransactionTo,
46    /// Transferred value in wei.
47    pub value: Wei,
48    /// Borrowed transaction input data.
49    pub input: &'a [u8],
50    /// Borrowed access list.
51    pub access_list: AccessList<'a>,
52    /// Signature y parity.
53    pub y_parity: SignatureYParity,
54    /// Raw canonical U256 signature `r` value.
55    ///
56    /// This is not checked for secp256k1 scalar validity.
57    pub r: [u8; 32],
58    /// Raw canonical U256 signature `s` value.
59    ///
60    /// This is not checked against the EIP-2 low-s bound or secp256k1 scalar
61    /// validity.
62    pub s: [u8; 32],
63}
64
65/// EIP-1559 transaction field identifier.
66#[non_exhaustive]
67#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68pub enum DynamicFeeTransactionField {
69    /// Whole typed-transaction payload.
70    Payload,
71    /// `chain_id`.
72    ChainId,
73    /// `nonce`.
74    Nonce,
75    /// `max_priority_fee_per_gas`.
76    MaxPriorityFeePerGas,
77    /// `max_fee_per_gas`.
78    MaxFeePerGas,
79    /// `gas_limit`.
80    GasLimit,
81    /// `destination`.
82    To,
83    /// `amount`.
84    Value,
85    /// `data`.
86    Data,
87    /// `access_list`.
88    AccessList,
89    /// `signature_y_parity`.
90    SignatureYParity,
91    /// `signature_r`.
92    SignatureR,
93    /// `signature_s`.
94    SignatureS,
95}
96
97/// Decodes an EIP-1559 dynamic-fee transaction into unvalidated field domains.
98pub fn decode_dynamic_fee_transaction<'a>(
99    input: &'a [u8],
100    limits: DecodeLimits,
101) -> Result<UnvalidatedDynamicFeeTransaction<'a>, DynamicFeeTransactionDecodeError> {
102    match decode_transaction_envelope(input, limits)
103        .map_err(DynamicFeeTransactionDecodeError::Envelope)?
104    {
105        TransactionEnvelope::Typed(typed)
106            if typed.transaction_type.get() == DYNAMIC_FEE_TRANSACTION_TYPE =>
107        {
108            decode_dynamic_fee_payload(typed.payload, limits)
109        }
110        TransactionEnvelope::Typed(typed) => {
111            Err(DynamicFeeTransactionDecodeError::WrongTransactionType {
112                type_byte: typed.transaction_type.get(),
113            })
114        }
115        TransactionEnvelope::Legacy(_) => {
116            Err(DynamicFeeTransactionDecodeError::WrongTransactionType { type_byte: 0 })
117        }
118    }
119}
120
121fn decode_dynamic_fee_payload<'a>(
122    payload: &'a [u8],
123    limits: DecodeLimits,
124) -> Result<UnvalidatedDynamicFeeTransaction<'a>, DynamicFeeTransactionDecodeError> {
125    let list = eth_valkyoth_codec::decode_rlp_list(payload, limits)
126        .map_err(|source| field_error(DynamicFeeTransactionField::Payload, source))?;
127    if list.item_count() != DYNAMIC_FEE_TRANSACTION_FIELD_COUNT {
128        return Err(DynamicFeeTransactionDecodeError::WrongFieldCount {
129            expected: DYNAMIC_FEE_TRANSACTION_FIELD_COUNT,
130            found: list.item_count(),
131        });
132    }
133
134    let mut fields = list.items();
135    let chain_id = decode_shared_chain_id(
136        &mut fields,
137        DynamicFeeTransactionField::ChainId,
138        field_error,
139    )?;
140    let nonce = Nonce::new(decode_shared_u64_field(
141        &mut fields,
142        DynamicFeeTransactionField::Nonce,
143        field_error,
144    )?);
145    let max_priority_fee_per_gas = Wei::from_be_bytes(decode_shared_u256_field(
146        &mut fields,
147        DynamicFeeTransactionField::MaxPriorityFeePerGas,
148        field_error,
149    )?);
150    let max_fee_per_gas = Wei::from_be_bytes(decode_shared_u256_field(
151        &mut fields,
152        DynamicFeeTransactionField::MaxFeePerGas,
153        field_error,
154    )?);
155    let gas_limit = Gas::new(decode_shared_u64_field(
156        &mut fields,
157        DynamicFeeTransactionField::GasLimit,
158        field_error,
159    )?);
160    let to = decode_shared_to(
161        next_shared_scalar(&mut fields, DynamicFeeTransactionField::To, field_error)?,
162        |found| DynamicFeeTransactionDecodeError::InvalidToLength { found },
163    )?;
164    let value = Wei::from_be_bytes(decode_shared_u256_field(
165        &mut fields,
166        DynamicFeeTransactionField::Value,
167        field_error,
168    )?);
169    let input =
170        next_shared_scalar(&mut fields, DynamicFeeTransactionField::Data, field_error)?.payload();
171    limits
172        .check_single_allocation_limit(input.len())
173        .map_err(|source| field_error(DynamicFeeTransactionField::Data, source))?;
174    let access_list = decode_access_list(next_shared_list(
175        &mut fields,
176        DynamicFeeTransactionField::AccessList,
177        field_error,
178    )?)
179    .map_err(map_access_list_error)?;
180    let y_parity = SignatureYParity::try_new(decode_shared_u64_field(
181        &mut fields,
182        DynamicFeeTransactionField::SignatureYParity,
183        field_error,
184    )?)
185    .map_err(|error| DynamicFeeTransactionDecodeError::InvalidYParity {
186        value: error.value(),
187    })?;
188    let r = decode_shared_u256_field(
189        &mut fields,
190        DynamicFeeTransactionField::SignatureR,
191        field_error,
192    )?;
193    let s = decode_shared_u256_field(
194        &mut fields,
195        DynamicFeeTransactionField::SignatureS,
196        field_error,
197    )?;
198
199    Ok(UnvalidatedDynamicFeeTransaction {
200        chain_id,
201        nonce,
202        max_priority_fee_per_gas,
203        max_fee_per_gas,
204        gas_limit,
205        to,
206        value,
207        input,
208        access_list,
209        y_parity,
210        r,
211        s,
212    })
213}
214
215const fn field_error(
216    field: DynamicFeeTransactionField,
217    source: DecodeError,
218) -> DynamicFeeTransactionDecodeError {
219    DynamicFeeTransactionDecodeError::FieldDecode { field, source }
220}
221
222const fn map_access_list_error(error: AccessListDecodeError) -> DynamicFeeTransactionDecodeError {
223    match error {
224        AccessListDecodeError::FieldDecode(source) => {
225            field_error(DynamicFeeTransactionField::AccessList, source)
226        }
227        AccessListDecodeError::InvalidAccessListEntryFieldCount { found } => {
228            DynamicFeeTransactionDecodeError::InvalidAccessListEntryFieldCount { found }
229        }
230        AccessListDecodeError::InvalidAccessListAddressLength { found } => {
231            DynamicFeeTransactionDecodeError::InvalidAccessListAddressLength { found }
232        }
233        AccessListDecodeError::InvalidStorageKeyLength { found } => {
234            DynamicFeeTransactionDecodeError::InvalidStorageKeyLength { found }
235        }
236    }
237}