mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: eip-7702 (#9214)
Co-authored-by: Matthew Smith <m@lattejed.com> Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de> Co-authored-by: joshieDo <93316087+joshieDo@users.noreply.github.com> Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com>
This commit is contained in:
@ -386,7 +386,7 @@ revm-primitives = { version = "6.0.0", features = [
|
||||
revm-inspectors = "0.4"
|
||||
|
||||
# eth
|
||||
alloy-chains = "0.1.15"
|
||||
alloy-chains = "0.1.18"
|
||||
alloy-primitives = "0.7.2"
|
||||
alloy-dyn-abi = "0.7.2"
|
||||
alloy-sol-types = "0.7.2"
|
||||
|
||||
@ -948,6 +948,13 @@ impl ChainSpecBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable Prague at genesis.
|
||||
pub fn prague_activated(mut self) -> Self {
|
||||
self = self.cancun_activated();
|
||||
self.hardforks.insert(EthereumHardfork::Prague, ForkCondition::Timestamp(0));
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable Bedrock at genesis
|
||||
#[cfg(feature = "optimism")]
|
||||
pub fn bedrock_activated(mut self) -> Self {
|
||||
|
||||
@ -342,6 +342,9 @@ pub struct AnnouncedTxTypesMetrics {
|
||||
|
||||
/// Histogram for tracking frequency of EIP-4844 transaction type
|
||||
pub(crate) eip4844: Histogram,
|
||||
|
||||
/// Histogram for tracking frequency of EIP-7702 transaction type
|
||||
pub(crate) eip7702: Histogram,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@ -350,6 +353,7 @@ pub struct TxTypesCounter {
|
||||
pub(crate) eip2930: usize,
|
||||
pub(crate) eip1559: usize,
|
||||
pub(crate) eip4844: usize,
|
||||
pub(crate) eip7702: usize,
|
||||
}
|
||||
|
||||
impl TxTypesCounter {
|
||||
@ -368,6 +372,9 @@ impl TxTypesCounter {
|
||||
TxType::Eip4844 => {
|
||||
self.eip4844 += 1;
|
||||
}
|
||||
TxType::Eip7702 => {
|
||||
self.eip7702 += 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -381,5 +388,6 @@ impl AnnouncedTxTypesMetrics {
|
||||
self.eip2930.record(tx_types_counter.eip2930 as f64);
|
||||
self.eip1559.record(tx_types_counter.eip1559 as f64);
|
||||
self.eip4844.record(tx_types_counter.eip4844 as f64);
|
||||
self.eip7702.record(tx_types_counter.eip7702 as f64);
|
||||
}
|
||||
}
|
||||
|
||||
@ -331,6 +331,7 @@ impl FilterAnnouncement for EthMessageFilter {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(eip7702): update tests as needed
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
@ -181,6 +181,34 @@ impl TryFrom<alloy_rpc_types::Transaction> for Transaction {
|
||||
.ok_or(ConversionError::MissingMaxFeePerBlobGas)?,
|
||||
}))
|
||||
}
|
||||
Some(TxType::Eip7702) => {
|
||||
// this is currently unsupported as it is not present in alloy due to missing rpc
|
||||
// specs
|
||||
Err(ConversionError::Custom("Unimplemented".to_string()))
|
||||
/*
|
||||
// EIP-7702
|
||||
Ok(Transaction::Eip7702(TxEip7702 {
|
||||
chain_id: tx.chain_id.ok_or(ConversionError::MissingChainId)?,
|
||||
nonce: tx.nonce,
|
||||
max_priority_fee_per_gas: tx
|
||||
.max_priority_fee_per_gas
|
||||
.ok_or(ConversionError::MissingMaxPriorityFeePerGas)?,
|
||||
max_fee_per_gas: tx
|
||||
.max_fee_per_gas
|
||||
.ok_or(ConversionError::MissingMaxFeePerGas)?,
|
||||
gas_limit: tx
|
||||
.gas
|
||||
.try_into()
|
||||
.map_err(|_| ConversionError::Eip2718Error(RlpError::Overflow.into()))?,
|
||||
to: tx.to.map_or(TxKind::Create, TxKind::Call),
|
||||
value: tx.value,
|
||||
access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?,
|
||||
authorization_list: tx
|
||||
.authorization_list
|
||||
.ok_or(ConversionError::MissingAuthorizationList)?,
|
||||
input: tx.input,
|
||||
}))*/
|
||||
}
|
||||
#[cfg(feature = "optimism")]
|
||||
Some(TxType::Deposit) => {
|
||||
let fields = tx
|
||||
|
||||
@ -68,8 +68,8 @@ pub use transaction::{
|
||||
AccessList, AccessListItem, IntoRecoveredTransaction, InvalidTransactionError, Signature,
|
||||
Transaction, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered,
|
||||
TransactionSignedNoHash, TryFromRecoveredTransaction, TxEip1559, TxEip2930, TxEip4844,
|
||||
TxHashOrNumber, TxLegacy, TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID,
|
||||
LEGACY_TX_TYPE_ID,
|
||||
TxEip7702, TxHashOrNumber, TxLegacy, TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID,
|
||||
EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
|
||||
};
|
||||
|
||||
// Re-exports
|
||||
|
||||
@ -339,6 +339,10 @@ impl Decodable for ReceiptWithBloom {
|
||||
buf.advance(1);
|
||||
Self::decode_receipt(buf, TxType::Eip4844)
|
||||
}
|
||||
0x04 => {
|
||||
buf.advance(1);
|
||||
Self::decode_receipt(buf, TxType::Eip7702)
|
||||
}
|
||||
#[cfg(feature = "optimism")]
|
||||
0x7E => {
|
||||
buf.advance(1);
|
||||
@ -471,6 +475,9 @@ impl<'a> ReceiptWithBloomEncoder<'a> {
|
||||
TxType::Eip4844 => {
|
||||
out.put_u8(0x03);
|
||||
}
|
||||
TxType::Eip7702 => {
|
||||
out.put_u8(0x04);
|
||||
}
|
||||
#[cfg(feature = "optimism")]
|
||||
TxType::Deposit => {
|
||||
out.put_u8(0x7E);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::{Address, Transaction, TransactionSigned, TxKind, U256};
|
||||
use revm_primitives::TxEnv;
|
||||
use revm_primitives::{AuthorizationList, TxEnv};
|
||||
|
||||
/// Implements behaviour to fill a [`TxEnv`] from another transaction.
|
||||
pub trait FillTxEnv {
|
||||
@ -70,6 +70,21 @@ impl FillTxEnv for TransactionSigned {
|
||||
tx_env.blob_hashes.clone_from(&tx.blob_versioned_hashes);
|
||||
tx_env.max_fee_per_blob_gas = Some(U256::from(tx.max_fee_per_blob_gas));
|
||||
}
|
||||
Transaction::Eip7702(tx) => {
|
||||
tx_env.gas_limit = tx.gas_limit;
|
||||
tx_env.gas_price = U256::from(tx.max_fee_per_gas);
|
||||
tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas));
|
||||
tx_env.transact_to = tx.to;
|
||||
tx_env.value = tx.value;
|
||||
tx_env.data = tx.input.clone();
|
||||
tx_env.chain_id = Some(tx.chain_id);
|
||||
tx_env.nonce = Some(tx.nonce);
|
||||
tx_env.access_list = tx.access_list.0.clone();
|
||||
tx_env.blob_hashes.clear();
|
||||
tx_env.max_fee_per_blob_gas.take();
|
||||
tx_env.authorization_list =
|
||||
Some(AuthorizationList::Signed(tx.authorization_list.clone()));
|
||||
}
|
||||
#[cfg(feature = "optimism")]
|
||||
Transaction::Deposit(tx) => {
|
||||
tx_env.access_list.clear();
|
||||
|
||||
388
crates/primitives/src/transaction/eip7702.rs
Normal file
388
crates/primitives/src/transaction/eip7702.rs
Normal file
@ -0,0 +1,388 @@
|
||||
use super::access_list::AccessList;
|
||||
use crate::{keccak256, Bytes, ChainId, Signature, TxKind, TxType, B256, U256};
|
||||
use alloy_eips::eip7702::SignedAuthorization;
|
||||
use alloy_rlp::{length_of_length, Decodable, Encodable, Header};
|
||||
use bytes::BytesMut;
|
||||
use reth_codecs::{main_codec, Compact};
|
||||
use std::mem;
|
||||
|
||||
/// [EIP-7702 Set Code Transaction](https://eips.ethereum.org/EIPS/eip-7702)
|
||||
///
|
||||
/// Set EOA account code for one transaction
|
||||
#[main_codec(no_arbitrary, add_arbitrary_tests)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct TxEip7702 {
|
||||
/// Added as EIP-155: Simple replay attack protection
|
||||
pub chain_id: ChainId,
|
||||
/// A scalar value equal to the number of transactions sent by the sender; formally Tn.
|
||||
pub nonce: u64,
|
||||
/// A scalar value equal to the number of
|
||||
/// Wei to be paid per unit of gas for all computation
|
||||
/// costs incurred as a result of the execution of this transaction; formally Tp.
|
||||
///
|
||||
/// As ethereum circulation is around 120mil eth as of 2022 that is around
|
||||
/// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
|
||||
/// 340282366920938463463374607431768211455
|
||||
pub gas_limit: u64,
|
||||
/// A scalar value equal to the maximum
|
||||
/// amount of gas that should be used in executing
|
||||
/// this transaction. This is paid up-front, before any
|
||||
/// computation is done and may not be increased
|
||||
/// later; formally Tg.
|
||||
///
|
||||
/// As ethereum circulation is around 120mil eth as of 2022 that is around
|
||||
/// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
|
||||
/// 340282366920938463463374607431768211455
|
||||
///
|
||||
/// This is also known as `GasFeeCap`
|
||||
pub max_fee_per_gas: u128,
|
||||
/// Max Priority fee that transaction is paying
|
||||
///
|
||||
/// As ethereum circulation is around 120mil eth as of 2022 that is around
|
||||
/// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
|
||||
/// 340282366920938463463374607431768211455
|
||||
///
|
||||
/// This is also known as `GasTipCap`
|
||||
pub max_priority_fee_per_gas: u128,
|
||||
/// The 160-bit address of the message call’s recipient or, for a contract creation
|
||||
/// transaction, ∅, used here to denote the only member of B0 ; formally Tt.
|
||||
pub to: TxKind,
|
||||
/// A scalar value equal to the number of Wei to
|
||||
/// be transferred to the message call’s recipient or,
|
||||
/// in the case of contract creation, as an endowment
|
||||
/// to the newly created account; formally Tv.
|
||||
pub value: U256,
|
||||
/// The accessList specifies a list of addresses and storage keys;
|
||||
/// these addresses and storage keys are added into the `accessed_addresses`
|
||||
/// and `accessed_storage_keys` global sets (introduced in EIP-2929).
|
||||
/// A gas cost is charged, though at a discount relative to the cost of
|
||||
/// accessing outside the list.
|
||||
pub access_list: AccessList,
|
||||
/// Authorizations are used to temporarily set the code of its signer to
|
||||
/// the code referenced by `address`. These also include a `chain_id` (which
|
||||
/// can be set to zero and not evaluated) as well as an optional `nonce`.
|
||||
pub authorization_list: Vec<SignedAuthorization<alloy_primitives::Signature>>,
|
||||
/// Input has two uses depending if the transaction `to` field is [`TxKind::Create`] or
|
||||
/// [`TxKind::Call`].
|
||||
///
|
||||
/// Input as init code, or if `to` is [`TxKind::Create`]: An unlimited size byte array
|
||||
/// specifying the EVM-code for the account initialisation procedure `CREATE`
|
||||
///
|
||||
/// Input as data, or if `to` is [`TxKind::Call`]: An unlimited size byte array specifying the
|
||||
/// input data of the message call, formally Td.
|
||||
pub input: Bytes,
|
||||
}
|
||||
|
||||
impl TxEip7702 {
|
||||
/// Returns the effective gas price for the given `base_fee`.
|
||||
pub const fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
|
||||
match base_fee {
|
||||
None => self.max_fee_per_gas,
|
||||
Some(base_fee) => {
|
||||
// if the tip is greater than the max priority fee per gas, set it to the max
|
||||
// priority fee per gas + base fee
|
||||
let tip = self.max_fee_per_gas.saturating_sub(base_fee as u128);
|
||||
if tip > self.max_priority_fee_per_gas {
|
||||
self.max_priority_fee_per_gas + base_fee as u128
|
||||
} else {
|
||||
// otherwise return the max fee per gas
|
||||
self.max_fee_per_gas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates a heuristic for the in-memory size of the [`TxEip7702`] transaction.
|
||||
#[inline]
|
||||
pub fn size(&self) -> usize {
|
||||
mem::size_of::<ChainId>() + // chain_id
|
||||
mem::size_of::<u64>() + // nonce
|
||||
mem::size_of::<u128>() + // gas_price
|
||||
mem::size_of::<u64>() + // gas_limit
|
||||
self.to.size() + // to
|
||||
mem::size_of::<U256>() + // value
|
||||
self.access_list.size() + // access_list
|
||||
mem::size_of::<SignedAuthorization<alloy_primitives::Signature>>()
|
||||
* self.authorization_list.capacity() + // authorization_list
|
||||
self.input.len() // input
|
||||
}
|
||||
|
||||
/// Decodes the inner [`TxEip7702`] fields from RLP bytes.
|
||||
///
|
||||
/// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following
|
||||
/// RLP fields in the following order:
|
||||
///
|
||||
/// - `chain_id`
|
||||
/// - `nonce`
|
||||
/// - `gas_price`
|
||||
/// - `gas_limit`
|
||||
/// - `to`
|
||||
/// - `value`
|
||||
/// - `data` (`input`)
|
||||
/// - `access_list`
|
||||
/// - `authorization_list`
|
||||
pub(crate) fn decode_inner(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
|
||||
Ok(Self {
|
||||
chain_id: Decodable::decode(buf)?,
|
||||
nonce: Decodable::decode(buf)?,
|
||||
max_priority_fee_per_gas: Decodable::decode(buf)?,
|
||||
max_fee_per_gas: Decodable::decode(buf)?,
|
||||
gas_limit: Decodable::decode(buf)?,
|
||||
to: Decodable::decode(buf)?,
|
||||
value: Decodable::decode(buf)?,
|
||||
input: Decodable::decode(buf)?,
|
||||
access_list: Decodable::decode(buf)?,
|
||||
authorization_list: Decodable::decode(buf)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Outputs the length of the transaction's fields, without a RLP header.
|
||||
pub(crate) fn fields_len(&self) -> usize {
|
||||
self.chain_id.length() +
|
||||
self.nonce.length() +
|
||||
self.max_priority_fee_per_gas.length() +
|
||||
self.max_fee_per_gas.length() +
|
||||
self.gas_limit.length() +
|
||||
self.to.length() +
|
||||
self.value.length() +
|
||||
self.input.0.length() +
|
||||
self.access_list.length() +
|
||||
self.authorization_list.length()
|
||||
}
|
||||
|
||||
/// Encodes only the transaction's fields into the desired buffer, without a RLP header.
|
||||
pub(crate) fn encode_fields(&self, out: &mut dyn bytes::BufMut) {
|
||||
self.chain_id.encode(out);
|
||||
self.nonce.encode(out);
|
||||
self.max_priority_fee_per_gas.encode(out);
|
||||
self.max_fee_per_gas.encode(out);
|
||||
self.gas_limit.encode(out);
|
||||
self.to.encode(out);
|
||||
self.value.encode(out);
|
||||
self.input.0.encode(out);
|
||||
self.access_list.encode(out);
|
||||
self.authorization_list.encode(out);
|
||||
}
|
||||
|
||||
/// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating
|
||||
/// hash that for eip2718 does not require rlp header
|
||||
///
|
||||
/// This encodes the transaction as:
|
||||
/// `rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination,
|
||||
/// value, data, access_list, authorization_list, signature_y_parity, signature_r,
|
||||
/// signature_s])`
|
||||
pub(crate) fn encode_with_signature(
|
||||
&self,
|
||||
signature: &Signature,
|
||||
out: &mut dyn bytes::BufMut,
|
||||
with_header: bool,
|
||||
) {
|
||||
let payload_length = self.fields_len() + signature.payload_len();
|
||||
if with_header {
|
||||
Header {
|
||||
list: false,
|
||||
payload_length: 1 + length_of_length(payload_length) + payload_length,
|
||||
}
|
||||
.encode(out);
|
||||
}
|
||||
out.put_u8(self.tx_type() as u8);
|
||||
let header = Header { list: true, payload_length };
|
||||
header.encode(out);
|
||||
self.encode_fields(out);
|
||||
signature.encode(out);
|
||||
}
|
||||
|
||||
/// Output the length of the RLP signed transaction encoding, _without_ a RLP string header.
|
||||
pub(crate) fn payload_len_with_signature_without_header(&self, signature: &Signature) -> usize {
|
||||
let payload_length = self.fields_len() + signature.payload_len();
|
||||
// 'transaction type byte length' + 'header length' + 'payload length'
|
||||
1 + length_of_length(payload_length) + payload_length
|
||||
}
|
||||
|
||||
/// Output the length of the RLP signed transaction encoding. This encodes with a RLP header.
|
||||
pub(crate) fn payload_len_with_signature(&self, signature: &Signature) -> usize {
|
||||
let len = self.payload_len_with_signature_without_header(signature);
|
||||
length_of_length(len) + len
|
||||
}
|
||||
|
||||
/// Get transaction type
|
||||
pub(crate) const fn tx_type(&self) -> TxType {
|
||||
TxType::Eip7702
|
||||
}
|
||||
|
||||
/// Encodes the EIP-7702 transaction in RLP for signing.
|
||||
///
|
||||
/// This encodes the transaction as:
|
||||
/// `tx_type || rlp(chain_id, nonce, gas_price, gas_limit, to, value, input, access_list,
|
||||
/// authorization_list)`
|
||||
///
|
||||
/// Note that there is no rlp header before the transaction type byte.
|
||||
pub(crate) fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) {
|
||||
out.put_u8(self.tx_type() as u8);
|
||||
Header { list: true, payload_length: self.fields_len() }.encode(out);
|
||||
self.encode_fields(out);
|
||||
}
|
||||
|
||||
/// Outputs the length of the signature RLP encoding for the transaction.
|
||||
pub(crate) fn payload_len_for_signature(&self) -> usize {
|
||||
let payload_length = self.fields_len();
|
||||
// 'transaction type byte length' + 'header length' + 'payload length'
|
||||
1 + length_of_length(payload_length) + payload_length
|
||||
}
|
||||
|
||||
/// Outputs the signature hash of the transaction by first encoding without a signature, then
|
||||
/// hashing.
|
||||
pub(crate) fn signature_hash(&self) -> B256 {
|
||||
let mut buf = BytesMut::with_capacity(self.payload_len_for_signature());
|
||||
self.encode_for_signing(&mut buf);
|
||||
keccak256(&buf)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(onbjerg): This is temporary until we upstream `Arbitrary` to EIP-7702 types and `Signature`
|
||||
// in alloy
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
impl<'a> arbitrary::Arbitrary<'a> for TxEip7702 {
|
||||
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
use arbitrary::Arbitrary;
|
||||
#[derive(Arbitrary)]
|
||||
struct ArbitrarySignedAuth {
|
||||
chain_id: ChainId,
|
||||
address: alloy_primitives::Address,
|
||||
nonce: Option<u64>,
|
||||
parity: bool,
|
||||
r: U256,
|
||||
s: U256,
|
||||
}
|
||||
|
||||
let iter = u.arbitrary_iter::<ArbitrarySignedAuth>()?;
|
||||
let mut authorization_list = Vec::new();
|
||||
for auth in iter {
|
||||
let auth = auth?;
|
||||
|
||||
let sig = alloy_primitives::Signature::from_rs_and_parity(
|
||||
auth.r,
|
||||
auth.s,
|
||||
alloy_primitives::Parity::Parity(auth.parity),
|
||||
)
|
||||
.unwrap_or_else(|_| {
|
||||
// Give a default one if the randomly generated one failed
|
||||
alloy_primitives::Signature::from_rs_and_parity(
|
||||
alloy_primitives::b256!(
|
||||
"1fd474b1f9404c0c5df43b7620119ffbc3a1c3f942c73b6e14e9f55255ed9b1d"
|
||||
)
|
||||
.into(),
|
||||
alloy_primitives::b256!(
|
||||
"29aca24813279a901ec13b5f7bb53385fa1fc627b946592221417ff74a49600d"
|
||||
)
|
||||
.into(),
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
authorization_list.push(
|
||||
alloy_eips::eip7702::Authorization {
|
||||
chain_id: auth.chain_id,
|
||||
address: auth.address,
|
||||
nonce: auth.nonce.into(),
|
||||
}
|
||||
.into_signed(sig),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
chain_id: Arbitrary::arbitrary(u)?,
|
||||
nonce: Arbitrary::arbitrary(u)?,
|
||||
gas_limit: Arbitrary::arbitrary(u)?,
|
||||
max_fee_per_gas: Arbitrary::arbitrary(u)?,
|
||||
max_priority_fee_per_gas: Arbitrary::arbitrary(u)?,
|
||||
to: Arbitrary::arbitrary(u)?,
|
||||
value: Arbitrary::arbitrary(u)?,
|
||||
access_list: Arbitrary::arbitrary(u)?,
|
||||
authorization_list,
|
||||
input: Arbitrary::arbitrary(u)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(onbjerg): This is temporary until we upstream `Hash` for EIP-7702 types in alloy
|
||||
impl std::hash::Hash for TxEip7702 {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.chain_id.hash(state);
|
||||
self.nonce.hash(state);
|
||||
self.gas_limit.hash(state);
|
||||
self.max_fee_per_gas.hash(state);
|
||||
self.max_priority_fee_per_gas.hash(state);
|
||||
self.to.hash(state);
|
||||
self.value.hash(state);
|
||||
self.access_list.hash(state);
|
||||
for auth in &self.authorization_list {
|
||||
auth.signature_hash().hash(state);
|
||||
}
|
||||
self.input.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::TxEip7702;
|
||||
use crate::{
|
||||
transaction::{signature::Signature, TxKind},
|
||||
Address, Bytes, Transaction, TransactionSigned, U256,
|
||||
};
|
||||
use alloy_rlp::{Decodable, Encodable};
|
||||
|
||||
#[test]
|
||||
fn test_decode_create() {
|
||||
// tests that a contract creation tx encodes and decodes properly
|
||||
let request = Transaction::Eip7702(TxEip7702 {
|
||||
chain_id: 1u64,
|
||||
nonce: 0,
|
||||
max_fee_per_gas: 0x4a817c800,
|
||||
max_priority_fee_per_gas: 0x3b9aca00,
|
||||
gas_limit: 2,
|
||||
to: TxKind::Create,
|
||||
value: U256::ZERO,
|
||||
input: Bytes::from(vec![1, 2]),
|
||||
access_list: Default::default(),
|
||||
authorization_list: Default::default(),
|
||||
});
|
||||
let signature = Signature { odd_y_parity: true, r: U256::default(), s: U256::default() };
|
||||
let tx = TransactionSigned::from_transaction_and_signature(request, signature);
|
||||
|
||||
let mut encoded = Vec::new();
|
||||
tx.encode(&mut encoded);
|
||||
assert_eq!(encoded.len(), tx.length());
|
||||
|
||||
let decoded = TransactionSigned::decode(&mut &*encoded).unwrap();
|
||||
assert_eq!(decoded, tx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_call() {
|
||||
let request = Transaction::Eip7702(TxEip7702 {
|
||||
chain_id: 1u64,
|
||||
nonce: 0,
|
||||
max_fee_per_gas: 0x4a817c800,
|
||||
max_priority_fee_per_gas: 0x3b9aca00,
|
||||
gas_limit: 2,
|
||||
to: Address::default().into(),
|
||||
value: U256::ZERO,
|
||||
input: Bytes::from(vec![1, 2]),
|
||||
access_list: Default::default(),
|
||||
authorization_list: Default::default(),
|
||||
});
|
||||
|
||||
let signature = Signature { odd_y_parity: true, r: U256::default(), s: U256::default() };
|
||||
|
||||
let tx = TransactionSigned::from_transaction_and_signature(request, signature);
|
||||
|
||||
let mut encoded = Vec::new();
|
||||
tx.encode(&mut encoded);
|
||||
assert_eq!(encoded.len(), tx.length());
|
||||
|
||||
let decoded = TransactionSigned::decode(&mut &*encoded).unwrap();
|
||||
assert_eq!(decoded, tx);
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,9 @@ pub enum InvalidTransactionError {
|
||||
/// The transaction requires EIP-4844 which is not enabled currently.
|
||||
#[error("EIP-4844 transactions are disabled")]
|
||||
Eip4844Disabled,
|
||||
/// The transaction requires EIP-7702 which is not enabled currently.
|
||||
#[error("EIP-7702 transactions are disabled")]
|
||||
Eip7702Disabled,
|
||||
/// Thrown if a transaction is not supported in the current network configuration.
|
||||
#[error("transaction type not supported")]
|
||||
TxTypeNotSupported,
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
use crate::compression::{TRANSACTION_COMPRESSOR, TRANSACTION_DECOMPRESSOR};
|
||||
use crate::{keccak256, Address, BlockHashOrNumber, Bytes, TxHash, TxKind, B256, U256};
|
||||
|
||||
use alloy_eips::eip7702::SignedAuthorization;
|
||||
use alloy_rlp::{
|
||||
Decodable, Encodable, Error as RlpError, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE,
|
||||
};
|
||||
@ -19,6 +20,7 @@ pub use access_list::{AccessList, AccessListItem};
|
||||
pub use eip1559::TxEip1559;
|
||||
pub use eip2930::TxEip2930;
|
||||
pub use eip4844::TxEip4844;
|
||||
pub use eip7702::TxEip7702;
|
||||
|
||||
pub use error::{
|
||||
InvalidTransactionError, TransactionConversionError, TryFromRecoveredTransactionError,
|
||||
@ -35,7 +37,8 @@ pub use sidecar::{BlobTransaction, BlobTransactionSidecar};
|
||||
pub use compat::FillTxEnv;
|
||||
pub use signature::{extract_chain_id, Signature};
|
||||
pub use tx_type::{
|
||||
TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
|
||||
TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
|
||||
LEGACY_TX_TYPE_ID,
|
||||
};
|
||||
pub use variant::TransactionSignedVariant;
|
||||
|
||||
@ -44,6 +47,7 @@ mod compat;
|
||||
mod eip1559;
|
||||
mod eip2930;
|
||||
mod eip4844;
|
||||
mod eip7702;
|
||||
mod error;
|
||||
mod legacy;
|
||||
mod meta;
|
||||
@ -122,6 +126,12 @@ pub enum Transaction {
|
||||
/// EIP-4844, also known as proto-danksharding, implements the framework and logic of
|
||||
/// danksharding, introducing new transaction formats and verification rules.
|
||||
Eip4844(TxEip4844),
|
||||
/// EOA Set Code Transactions ([EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)), type `0x4`.
|
||||
///
|
||||
/// EOA Set Code Transactions give the ability to temporarily set contract code for an
|
||||
/// EOA for a single transaction. This allows for temporarily adding smart contract
|
||||
/// functionality to the EOA.
|
||||
Eip7702(TxEip7702),
|
||||
/// Optimism deposit transaction.
|
||||
#[cfg(feature = "optimism")]
|
||||
Deposit(TxDeposit),
|
||||
@ -138,6 +148,7 @@ impl Transaction {
|
||||
Self::Eip2930(tx) => tx.signature_hash(),
|
||||
Self::Eip1559(tx) => tx.signature_hash(),
|
||||
Self::Eip4844(tx) => tx.signature_hash(),
|
||||
Self::Eip7702(tx) => tx.signature_hash(),
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(_) => B256::ZERO,
|
||||
}
|
||||
@ -149,7 +160,8 @@ impl Transaction {
|
||||
Self::Legacy(TxLegacy { chain_id, .. }) => *chain_id,
|
||||
Self::Eip2930(TxEip2930 { chain_id, .. }) |
|
||||
Self::Eip1559(TxEip1559 { chain_id, .. }) |
|
||||
Self::Eip4844(TxEip4844 { chain_id, .. }) => Some(*chain_id),
|
||||
Self::Eip4844(TxEip4844 { chain_id, .. }) |
|
||||
Self::Eip7702(TxEip7702 { chain_id, .. }) => Some(*chain_id),
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(_) => None,
|
||||
}
|
||||
@ -161,7 +173,8 @@ impl Transaction {
|
||||
Self::Legacy(TxLegacy { chain_id: ref mut c, .. }) => *c = Some(chain_id),
|
||||
Self::Eip2930(TxEip2930 { chain_id: ref mut c, .. }) |
|
||||
Self::Eip1559(TxEip1559 { chain_id: ref mut c, .. }) |
|
||||
Self::Eip4844(TxEip4844 { chain_id: ref mut c, .. }) => *c = chain_id,
|
||||
Self::Eip4844(TxEip4844 { chain_id: ref mut c, .. }) |
|
||||
Self::Eip7702(TxEip7702 { chain_id: ref mut c, .. }) => *c = chain_id,
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(_) => { /* noop */ }
|
||||
}
|
||||
@ -173,7 +186,8 @@ impl Transaction {
|
||||
match self {
|
||||
Self::Legacy(TxLegacy { to, .. }) |
|
||||
Self::Eip2930(TxEip2930 { to, .. }) |
|
||||
Self::Eip1559(TxEip1559 { to, .. }) => *to,
|
||||
Self::Eip1559(TxEip1559 { to, .. }) |
|
||||
Self::Eip7702(TxEip7702 { to, .. }) => *to,
|
||||
Self::Eip4844(TxEip4844 { to, .. }) => TxKind::Call(*to),
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(TxDeposit { to, .. }) => *to,
|
||||
@ -195,6 +209,7 @@ impl Transaction {
|
||||
Self::Eip2930(access_list_tx) => access_list_tx.tx_type(),
|
||||
Self::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.tx_type(),
|
||||
Self::Eip4844(blob_tx) => blob_tx.tx_type(),
|
||||
Self::Eip7702(set_code_tx) => set_code_tx.tx_type(),
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(deposit_tx) => deposit_tx.tx_type(),
|
||||
}
|
||||
@ -206,7 +221,8 @@ impl Transaction {
|
||||
Self::Legacy(TxLegacy { value, .. }) |
|
||||
Self::Eip2930(TxEip2930 { value, .. }) |
|
||||
Self::Eip1559(TxEip1559 { value, .. }) |
|
||||
Self::Eip4844(TxEip4844 { value, .. }) => value,
|
||||
Self::Eip4844(TxEip4844 { value, .. }) |
|
||||
Self::Eip7702(TxEip7702 { value, .. }) => value,
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(TxDeposit { value, .. }) => value,
|
||||
}
|
||||
@ -218,7 +234,8 @@ impl Transaction {
|
||||
Self::Legacy(TxLegacy { nonce, .. }) |
|
||||
Self::Eip2930(TxEip2930 { nonce, .. }) |
|
||||
Self::Eip1559(TxEip1559 { nonce, .. }) |
|
||||
Self::Eip4844(TxEip4844 { nonce, .. }) => *nonce,
|
||||
Self::Eip4844(TxEip4844 { nonce, .. }) |
|
||||
Self::Eip7702(TxEip7702 { nonce, .. }) => *nonce,
|
||||
// Deposit transactions do not have nonces.
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(_) => 0,
|
||||
@ -234,18 +251,32 @@ impl Transaction {
|
||||
Self::Eip2930(tx) => Some(&tx.access_list),
|
||||
Self::Eip1559(tx) => Some(&tx.access_list),
|
||||
Self::Eip4844(tx) => Some(&tx.access_list),
|
||||
Self::Eip7702(tx) => Some(&tx.access_list),
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`SignedAuthorization`] list of the transaction.
|
||||
///
|
||||
/// Returns `None` if this transaction is not EIP-7702.
|
||||
pub fn authorization_list(
|
||||
&self,
|
||||
) -> Option<&[SignedAuthorization<alloy_primitives::Signature>]> {
|
||||
match self {
|
||||
Self::Eip7702(tx) => Some(&tx.authorization_list),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the gas limit of the transaction.
|
||||
pub const fn gas_limit(&self) -> u64 {
|
||||
match self {
|
||||
Self::Legacy(TxLegacy { gas_limit, .. }) |
|
||||
Self::Eip2930(TxEip2930 { gas_limit, .. }) |
|
||||
Self::Eip1559(TxEip1559 { gas_limit, .. }) |
|
||||
Self::Eip4844(TxEip4844 { gas_limit, .. }) => *gas_limit,
|
||||
Self::Eip4844(TxEip4844 { gas_limit, .. }) |
|
||||
Self::Eip7702(TxEip7702 { gas_limit, .. }) => *gas_limit,
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(TxDeposit { gas_limit, .. }) => *gas_limit,
|
||||
}
|
||||
@ -255,7 +286,7 @@ impl Transaction {
|
||||
pub const fn is_dynamic_fee(&self) -> bool {
|
||||
match self {
|
||||
Self::Legacy(_) | Self::Eip2930(_) => false,
|
||||
Self::Eip1559(_) | Self::Eip4844(_) => true,
|
||||
Self::Eip1559(_) | Self::Eip4844(_) | Self::Eip7702(_) => true,
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(_) => false,
|
||||
}
|
||||
@ -269,7 +300,8 @@ impl Transaction {
|
||||
Self::Legacy(TxLegacy { gas_price, .. }) |
|
||||
Self::Eip2930(TxEip2930 { gas_price, .. }) => *gas_price,
|
||||
Self::Eip1559(TxEip1559 { max_fee_per_gas, .. }) |
|
||||
Self::Eip4844(TxEip4844 { max_fee_per_gas, .. }) => *max_fee_per_gas,
|
||||
Self::Eip4844(TxEip4844 { max_fee_per_gas, .. }) |
|
||||
Self::Eip7702(TxEip7702 { max_fee_per_gas, .. }) => *max_fee_per_gas,
|
||||
// Deposit transactions buy their L2 gas on L1 and, as such, the L2 gas is not
|
||||
// refundable.
|
||||
#[cfg(feature = "optimism")]
|
||||
@ -285,7 +317,8 @@ impl Transaction {
|
||||
match self {
|
||||
Self::Legacy(_) | Self::Eip2930(_) => None,
|
||||
Self::Eip1559(TxEip1559 { max_priority_fee_per_gas, .. }) |
|
||||
Self::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) => {
|
||||
Self::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) |
|
||||
Self::Eip7702(TxEip7702 { max_priority_fee_per_gas, .. }) => {
|
||||
Some(*max_priority_fee_per_gas)
|
||||
}
|
||||
#[cfg(feature = "optimism")]
|
||||
@ -293,13 +326,13 @@ impl Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Blob versioned hashes for eip4844 transaction, for legacy,eip1559 and eip2930 transactions
|
||||
/// this is `None`
|
||||
/// Blob versioned hashes for eip4844 transaction, for legacy, eip1559, eip2930 and eip7702
|
||||
/// transactions this is `None`
|
||||
///
|
||||
/// This is also commonly referred to as the "blob versioned hashes" (`BlobVersionedHashes`).
|
||||
pub fn blob_versioned_hashes(&self) -> Option<Vec<B256>> {
|
||||
match self {
|
||||
Self::Legacy(_) | Self::Eip2930(_) | Self::Eip1559(_) => None,
|
||||
Self::Legacy(_) | Self::Eip2930(_) | Self::Eip1559(_) | Self::Eip7702(_) => None,
|
||||
Self::Eip4844(TxEip4844 { blob_versioned_hashes, .. }) => {
|
||||
Some(blob_versioned_hashes.to_vec())
|
||||
}
|
||||
@ -341,7 +374,8 @@ impl Transaction {
|
||||
Self::Legacy(TxLegacy { gas_price, .. }) |
|
||||
Self::Eip2930(TxEip2930 { gas_price, .. }) => *gas_price,
|
||||
Self::Eip1559(TxEip1559 { max_priority_fee_per_gas, .. }) |
|
||||
Self::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) => *max_priority_fee_per_gas,
|
||||
Self::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) |
|
||||
Self::Eip7702(TxEip7702 { max_priority_fee_per_gas, .. }) => *max_priority_fee_per_gas,
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(_) => 0,
|
||||
}
|
||||
@ -356,6 +390,7 @@ impl Transaction {
|
||||
Self::Eip2930(tx) => tx.gas_price,
|
||||
Self::Eip1559(dynamic_tx) => dynamic_tx.effective_gas_price(base_fee),
|
||||
Self::Eip4844(dynamic_tx) => dynamic_tx.effective_gas_price(base_fee),
|
||||
Self::Eip7702(dynamic_tx) => dynamic_tx.effective_gas_price(base_fee),
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(_) => 0,
|
||||
}
|
||||
@ -398,7 +433,8 @@ impl Transaction {
|
||||
Self::Legacy(TxLegacy { input, .. }) |
|
||||
Self::Eip2930(TxEip2930 { input, .. }) |
|
||||
Self::Eip1559(TxEip1559 { input, .. }) |
|
||||
Self::Eip4844(TxEip4844 { input, .. }) => input,
|
||||
Self::Eip4844(TxEip4844 { input, .. }) |
|
||||
Self::Eip7702(TxEip7702 { input, .. }) => input,
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(TxDeposit { input, .. }) => input,
|
||||
}
|
||||
@ -466,6 +502,9 @@ impl Transaction {
|
||||
dynamic_fee_tx.encode_with_signature(signature, out, with_header)
|
||||
}
|
||||
Self::Eip4844(blob_tx) => blob_tx.encode_with_signature(signature, out, with_header),
|
||||
Self::Eip7702(set_code_tx) => {
|
||||
set_code_tx.encode_with_signature(signature, out, with_header)
|
||||
}
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(deposit_tx) => deposit_tx.encode(out, with_header),
|
||||
}
|
||||
@ -478,6 +517,7 @@ impl Transaction {
|
||||
Self::Eip2930(tx) => tx.gas_limit = gas_limit,
|
||||
Self::Eip1559(tx) => tx.gas_limit = gas_limit,
|
||||
Self::Eip4844(tx) => tx.gas_limit = gas_limit,
|
||||
Self::Eip7702(tx) => tx.gas_limit = gas_limit,
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(tx) => tx.gas_limit = gas_limit,
|
||||
}
|
||||
@ -490,6 +530,7 @@ impl Transaction {
|
||||
Self::Eip2930(tx) => tx.nonce = nonce,
|
||||
Self::Eip1559(tx) => tx.nonce = nonce,
|
||||
Self::Eip4844(tx) => tx.nonce = nonce,
|
||||
Self::Eip7702(tx) => tx.nonce = nonce,
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(_) => { /* noop */ }
|
||||
}
|
||||
@ -502,6 +543,7 @@ impl Transaction {
|
||||
Self::Eip2930(tx) => tx.value = value,
|
||||
Self::Eip1559(tx) => tx.value = value,
|
||||
Self::Eip4844(tx) => tx.value = value,
|
||||
Self::Eip7702(tx) => tx.value = value,
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(tx) => tx.value = value,
|
||||
}
|
||||
@ -514,6 +556,7 @@ impl Transaction {
|
||||
Self::Eip2930(tx) => tx.input = input,
|
||||
Self::Eip1559(tx) => tx.input = input,
|
||||
Self::Eip4844(tx) => tx.input = input,
|
||||
Self::Eip7702(tx) => tx.input = input,
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(tx) => tx.input = input,
|
||||
}
|
||||
@ -527,6 +570,7 @@ impl Transaction {
|
||||
Self::Eip2930(tx) => tx.size(),
|
||||
Self::Eip1559(tx) => tx.size(),
|
||||
Self::Eip4844(tx) => tx.size(),
|
||||
Self::Eip7702(tx) => tx.size(),
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(tx) => tx.size(),
|
||||
}
|
||||
@ -556,6 +600,12 @@ impl Transaction {
|
||||
matches!(self, Self::Eip4844(_))
|
||||
}
|
||||
|
||||
/// Returns true if the transaction is an EIP-7702 transaction.
|
||||
#[inline]
|
||||
pub const fn is_eip7702(&self) -> bool {
|
||||
matches!(self, Self::Eip7702(_))
|
||||
}
|
||||
|
||||
/// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
|
||||
pub const fn as_legacy(&self) -> Option<&TxLegacy> {
|
||||
match self {
|
||||
@ -587,6 +637,14 @@ impl Transaction {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
|
||||
pub const fn as_eip7702(&self) -> Option<&TxEip7702> {
|
||||
match self {
|
||||
Self::Eip7702(tx) => Some(tx),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TxLegacy> for Transaction {
|
||||
@ -613,6 +671,12 @@ impl From<TxEip4844> for Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TxEip7702> for Transaction {
|
||||
fn from(tx: TxEip7702) -> Self {
|
||||
Self::Eip7702(tx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Compact for Transaction {
|
||||
// Serializes the TxType to the buffer if necessary, returning 2 bits of the type as an
|
||||
// identifier instead of the length.
|
||||
@ -634,6 +698,9 @@ impl Compact for Transaction {
|
||||
Self::Eip4844(tx) => {
|
||||
tx.to_compact(buf);
|
||||
}
|
||||
Self::Eip7702(tx) => {
|
||||
tx.to_compact(buf);
|
||||
}
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(tx) => {
|
||||
tx.to_compact(buf);
|
||||
@ -676,6 +743,10 @@ impl Compact for Transaction {
|
||||
let (tx, buf) = TxEip4844::from_compact(buf, buf.len());
|
||||
(Self::Eip4844(tx), buf)
|
||||
}
|
||||
4 => {
|
||||
let (tx, buf) = TxEip7702::from_compact(buf, buf.len());
|
||||
(Self::Eip7702(tx), buf)
|
||||
}
|
||||
#[cfg(feature = "optimism")]
|
||||
126 => {
|
||||
let (tx, buf) = TxDeposit::from_compact(buf, buf.len());
|
||||
@ -712,6 +783,9 @@ impl Encodable for Transaction {
|
||||
Self::Eip4844(blob_tx) => {
|
||||
blob_tx.encode_for_signing(out);
|
||||
}
|
||||
Self::Eip7702(set_code_tx) => {
|
||||
set_code_tx.encode_for_signing(out);
|
||||
}
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(deposit_tx) => {
|
||||
deposit_tx.encode(out, true);
|
||||
@ -725,6 +799,7 @@ impl Encodable for Transaction {
|
||||
Self::Eip2930(access_list_tx) => access_list_tx.payload_len_for_signature(),
|
||||
Self::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.payload_len_for_signature(),
|
||||
Self::Eip4844(blob_tx) => blob_tx.payload_len_for_signature(),
|
||||
Self::Eip7702(set_code_tx) => set_code_tx.payload_len_for_signature(),
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit(deposit_tx) => deposit_tx.payload_len(),
|
||||
}
|
||||
@ -1161,6 +1236,9 @@ impl TransactionSigned {
|
||||
dynamic_fee_tx.payload_len_with_signature(&self.signature)
|
||||
}
|
||||
Transaction::Eip4844(blob_tx) => blob_tx.payload_len_with_signature(&self.signature),
|
||||
Transaction::Eip7702(set_code_tx) => {
|
||||
set_code_tx.payload_len_with_signature(&self.signature)
|
||||
}
|
||||
#[cfg(feature = "optimism")]
|
||||
Transaction::Deposit(deposit_tx) => deposit_tx.payload_len(),
|
||||
}
|
||||
@ -1250,7 +1328,7 @@ impl TransactionSigned {
|
||||
Ok(signed)
|
||||
}
|
||||
|
||||
/// Decodes en enveloped EIP-2718 typed transaction.
|
||||
/// Decodes an enveloped EIP-2718 typed transaction.
|
||||
///
|
||||
/// This should _only_ be used internally in general transaction decoding methods,
|
||||
/// which have already ensured that the input is a typed transaction with the following format:
|
||||
@ -1287,6 +1365,7 @@ impl TransactionSigned {
|
||||
TxType::Eip2930 => Transaction::Eip2930(TxEip2930::decode_inner(data)?),
|
||||
TxType::Eip1559 => Transaction::Eip1559(TxEip1559::decode_inner(data)?),
|
||||
TxType::Eip4844 => Transaction::Eip4844(TxEip4844::decode_inner(data)?),
|
||||
TxType::Eip7702 => Transaction::Eip7702(TxEip7702::decode_inner(data)?),
|
||||
#[cfg(feature = "optimism")]
|
||||
TxType::Deposit => Transaction::Deposit(TxDeposit::decode_inner(data)?),
|
||||
TxType::Legacy => return Err(RlpError::Custom("unexpected legacy tx type")),
|
||||
@ -1363,6 +1442,9 @@ impl TransactionSigned {
|
||||
Transaction::Eip4844(blob_tx) => {
|
||||
blob_tx.payload_len_with_signature_without_header(&self.signature)
|
||||
}
|
||||
Transaction::Eip7702(set_code_tx) => {
|
||||
set_code_tx.payload_len_with_signature_without_header(&self.signature)
|
||||
}
|
||||
#[cfg(feature = "optimism")]
|
||||
Transaction::Deposit(deposit_tx) => deposit_tx.payload_len_without_header(),
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
//! Defines the types for blob transactions, legacy, and other EIP-2718 transactions included in a
|
||||
//! response to `GetPooledTransactions`.
|
||||
|
||||
use super::error::TransactionConversionError;
|
||||
use super::{error::TransactionConversionError, TxEip7702};
|
||||
use crate::{
|
||||
Address, BlobTransaction, BlobTransactionSidecar, Bytes, Signature, Transaction,
|
||||
TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxHash,
|
||||
@ -48,6 +48,15 @@ pub enum PooledTransactionsElement {
|
||||
/// The hash of the transaction
|
||||
hash: TxHash,
|
||||
},
|
||||
/// An EIP-7702 typed transaction
|
||||
Eip7702 {
|
||||
/// The inner transaction
|
||||
transaction: TxEip7702,
|
||||
/// The signature
|
||||
signature: Signature,
|
||||
/// The hash of the transaction
|
||||
hash: TxHash,
|
||||
},
|
||||
/// A blob transaction, which includes the transaction, blob data, commitments, and proofs.
|
||||
BlobTransaction(BlobTransaction),
|
||||
}
|
||||
@ -69,6 +78,9 @@ impl PooledTransactionsElement {
|
||||
TransactionSigned { transaction: Transaction::Eip1559(tx), signature, hash } => {
|
||||
Ok(Self::Eip1559 { transaction: tx, signature, hash })
|
||||
}
|
||||
TransactionSigned { transaction: Transaction::Eip7702(tx), signature, hash } => {
|
||||
Ok(Self::Eip7702 { transaction: tx, signature, hash })
|
||||
}
|
||||
// Not supported because missing blob sidecar
|
||||
tx @ TransactionSigned { transaction: Transaction::Eip4844(_), .. } => Err(tx),
|
||||
#[cfg(feature = "optimism")]
|
||||
@ -105,6 +117,7 @@ impl PooledTransactionsElement {
|
||||
Self::Legacy { transaction, .. } => transaction.signature_hash(),
|
||||
Self::Eip2930 { transaction, .. } => transaction.signature_hash(),
|
||||
Self::Eip1559 { transaction, .. } => transaction.signature_hash(),
|
||||
Self::Eip7702 { transaction, .. } => transaction.signature_hash(),
|
||||
Self::BlobTransaction(blob_tx) => blob_tx.transaction.signature_hash(),
|
||||
}
|
||||
}
|
||||
@ -112,9 +125,10 @@ impl PooledTransactionsElement {
|
||||
/// Reference to transaction hash. Used to identify transaction.
|
||||
pub const fn hash(&self) -> &TxHash {
|
||||
match self {
|
||||
Self::Legacy { hash, .. } | Self::Eip2930 { hash, .. } | Self::Eip1559 { hash, .. } => {
|
||||
hash
|
||||
}
|
||||
Self::Legacy { hash, .. } |
|
||||
Self::Eip2930 { hash, .. } |
|
||||
Self::Eip1559 { hash, .. } |
|
||||
Self::Eip7702 { hash, .. } => hash,
|
||||
Self::BlobTransaction(tx) => &tx.hash,
|
||||
}
|
||||
}
|
||||
@ -124,7 +138,8 @@ impl PooledTransactionsElement {
|
||||
match self {
|
||||
Self::Legacy { signature, .. } |
|
||||
Self::Eip2930 { signature, .. } |
|
||||
Self::Eip1559 { signature, .. } => signature,
|
||||
Self::Eip1559 { signature, .. } |
|
||||
Self::Eip7702 { signature, .. } => signature,
|
||||
Self::BlobTransaction(blob_tx) => &blob_tx.signature,
|
||||
}
|
||||
}
|
||||
@ -135,6 +150,7 @@ impl PooledTransactionsElement {
|
||||
Self::Legacy { transaction, .. } => transaction.nonce,
|
||||
Self::Eip2930 { transaction, .. } => transaction.nonce,
|
||||
Self::Eip1559 { transaction, .. } => transaction.nonce,
|
||||
Self::Eip7702 { transaction, .. } => transaction.nonce,
|
||||
Self::BlobTransaction(blob_tx) => blob_tx.transaction.nonce,
|
||||
}
|
||||
}
|
||||
@ -238,6 +254,11 @@ impl PooledTransactionsElement {
|
||||
signature: typed_tx.signature,
|
||||
hash: typed_tx.hash,
|
||||
}),
|
||||
Transaction::Eip7702(tx) => {Ok(Self::Eip7702 {
|
||||
transaction: tx,
|
||||
signature: typed_tx.signature,
|
||||
hash: typed_tx.hash,
|
||||
})},
|
||||
#[cfg(feature = "optimism")]
|
||||
Transaction::Deposit(_) => Err(RlpError::Custom("Optimism deposit transaction cannot be decoded to PooledTransactionsElement"))
|
||||
}
|
||||
@ -267,6 +288,11 @@ impl PooledTransactionsElement {
|
||||
signature,
|
||||
hash,
|
||||
},
|
||||
Self::Eip7702 { transaction, signature, hash } => TransactionSigned {
|
||||
transaction: Transaction::Eip7702(transaction),
|
||||
signature,
|
||||
hash,
|
||||
},
|
||||
Self::BlobTransaction(blob_tx) => blob_tx.into_parts().0,
|
||||
}
|
||||
}
|
||||
@ -286,6 +312,10 @@ impl PooledTransactionsElement {
|
||||
// method computes the payload len without a RLP header
|
||||
transaction.payload_len_with_signature_without_header(signature)
|
||||
}
|
||||
Self::Eip7702 { transaction, signature, .. } => {
|
||||
// method computes the payload len without a RLP header
|
||||
transaction.payload_len_with_signature_without_header(signature)
|
||||
}
|
||||
Self::BlobTransaction(blob_tx) => {
|
||||
// the encoding does not use a header, so we set `with_header` to false
|
||||
blob_tx.payload_len_with_type(false)
|
||||
@ -316,6 +346,7 @@ impl PooledTransactionsElement {
|
||||
// - EIP-2930: TxEip2930::encode_with_signature
|
||||
// - EIP-1559: TxEip1559::encode_with_signature
|
||||
// - EIP-4844: BlobTransaction::encode_with_type_inner
|
||||
// - EIP-7702: TxEip7702::encode_with_signature
|
||||
match self {
|
||||
Self::Legacy { transaction, signature, .. } => {
|
||||
transaction.encode_with_signature(signature, out)
|
||||
@ -326,6 +357,9 @@ impl PooledTransactionsElement {
|
||||
Self::Eip1559 { transaction, signature, .. } => {
|
||||
transaction.encode_with_signature(signature, out, false)
|
||||
}
|
||||
Self::Eip7702 { transaction, signature, .. } => {
|
||||
transaction.encode_with_signature(signature, out, false)
|
||||
}
|
||||
Self::BlobTransaction(blob_tx) => {
|
||||
// The inner encoding is used with `with_header` set to true, making the final
|
||||
// encoding:
|
||||
@ -373,6 +407,14 @@ impl PooledTransactionsElement {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
|
||||
pub const fn as_eip7702(&self) -> Option<&TxEip7702> {
|
||||
match self {
|
||||
Self::Eip7702 { transaction, .. } => Some(transaction),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the blob gas used for all blobs of the EIP-4844 transaction if it is an EIP-4844
|
||||
/// transaction.
|
||||
///
|
||||
@ -402,6 +444,7 @@ impl PooledTransactionsElement {
|
||||
match self {
|
||||
Self::Legacy { .. } | Self::Eip2930 { .. } => None,
|
||||
Self::Eip1559 { transaction, .. } => Some(transaction.max_priority_fee_per_gas),
|
||||
Self::Eip7702 { transaction, .. } => Some(transaction.max_priority_fee_per_gas),
|
||||
Self::BlobTransaction(tx) => Some(tx.transaction.max_priority_fee_per_gas),
|
||||
}
|
||||
}
|
||||
@ -414,6 +457,7 @@ impl PooledTransactionsElement {
|
||||
Self::Legacy { transaction, .. } => transaction.gas_price,
|
||||
Self::Eip2930 { transaction, .. } => transaction.gas_price,
|
||||
Self::Eip1559 { transaction, .. } => transaction.max_fee_per_gas,
|
||||
Self::Eip7702 { transaction, .. } => transaction.max_fee_per_gas,
|
||||
Self::BlobTransaction(tx) => tx.transaction.max_fee_per_gas,
|
||||
}
|
||||
}
|
||||
@ -433,6 +477,7 @@ impl Encodable for PooledTransactionsElement {
|
||||
// - EIP-2930: TxEip2930::encode_with_signature
|
||||
// - EIP-1559: TxEip1559::encode_with_signature
|
||||
// - EIP-4844: BlobTransaction::encode_with_type_inner
|
||||
// - EIP-7702: TxEip7702::encode_with_signature
|
||||
match self {
|
||||
Self::Legacy { transaction, signature, .. } => {
|
||||
transaction.encode_with_signature(signature, out)
|
||||
@ -445,6 +490,10 @@ impl Encodable for PooledTransactionsElement {
|
||||
// encodes with string header
|
||||
transaction.encode_with_signature(signature, out, true)
|
||||
}
|
||||
Self::Eip7702 { transaction, signature, .. } => {
|
||||
// encodes with string header
|
||||
transaction.encode_with_signature(signature, out, true)
|
||||
}
|
||||
Self::BlobTransaction(blob_tx) => {
|
||||
// The inner encoding is used with `with_header` set to true, making the final
|
||||
// encoding:
|
||||
@ -468,6 +517,10 @@ impl Encodable for PooledTransactionsElement {
|
||||
// method computes the payload len with a RLP header
|
||||
transaction.payload_len_with_signature(signature)
|
||||
}
|
||||
Self::Eip7702 { transaction, signature, .. } => {
|
||||
// method computes the payload len with a RLP header
|
||||
transaction.payload_len_with_signature(signature)
|
||||
}
|
||||
Self::BlobTransaction(blob_tx) => {
|
||||
// the encoding uses a header, so we set `with_header` to true
|
||||
blob_tx.payload_len_with_type(true)
|
||||
@ -573,6 +626,11 @@ impl Decodable for PooledTransactionsElement {
|
||||
signature: typed_tx.signature,
|
||||
hash: typed_tx.hash,
|
||||
}),
|
||||
Transaction::Eip7702(tx) => Ok(Self::Eip7702 {
|
||||
transaction: tx,
|
||||
signature: typed_tx.signature,
|
||||
hash: typed_tx.hash,
|
||||
}),
|
||||
#[cfg(feature = "optimism")]
|
||||
Transaction::Deposit(_) => Err(RlpError::Custom("Optimism deposit transaction cannot be decoded to PooledTransactionsElement"))
|
||||
}
|
||||
|
||||
@ -25,6 +25,9 @@ pub struct Signature {
|
||||
/// The S field of the signature; the point on the curve.
|
||||
pub s: U256,
|
||||
/// yParity: Signature Y parity; formally Ty
|
||||
///
|
||||
/// WARNING: if it's deprecated in favor of `alloy_primitives::Signature` be sure that parity
|
||||
/// storage deser matches.
|
||||
pub odd_y_parity: bool,
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,9 @@ pub const EIP1559_TX_TYPE_ID: u8 = 2;
|
||||
/// Identifier for [`TxEip4844`](crate::TxEip4844) transaction.
|
||||
pub const EIP4844_TX_TYPE_ID: u8 = 3;
|
||||
|
||||
/// Identifier for [`TxEip7702`](crate::TxEip7702) transaction.
|
||||
pub const EIP7702_TX_TYPE_ID: u8 = 4;
|
||||
|
||||
/// Identifier for [`TxDeposit`](crate::TxDeposit) transaction.
|
||||
#[cfg(feature = "optimism")]
|
||||
pub const DEPOSIT_TX_TYPE_ID: u8 = 126;
|
||||
@ -42,6 +45,8 @@ pub enum TxType {
|
||||
Eip1559 = 2_isize,
|
||||
/// Shard Blob Transactions - EIP-4844
|
||||
Eip4844 = 3_isize,
|
||||
/// EOA Contract Code Transactions - EIP-7702
|
||||
Eip7702 = 4_isize,
|
||||
/// Optimism Deposit transaction.
|
||||
#[cfg(feature = "optimism")]
|
||||
Deposit = 126_isize,
|
||||
@ -49,13 +54,13 @@ pub enum TxType {
|
||||
|
||||
impl TxType {
|
||||
/// The max type reserved by an EIP.
|
||||
pub const MAX_RESERVED_EIP: Self = Self::Eip4844;
|
||||
pub const MAX_RESERVED_EIP: Self = Self::Eip7702;
|
||||
|
||||
/// Check if the transaction type has an access list.
|
||||
pub const fn has_access_list(&self) -> bool {
|
||||
match self {
|
||||
Self::Legacy => false,
|
||||
Self::Eip2930 | Self::Eip1559 | Self::Eip4844 => true,
|
||||
Self::Eip2930 | Self::Eip1559 | Self::Eip4844 | Self::Eip7702 => true,
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit => false,
|
||||
}
|
||||
@ -69,6 +74,7 @@ impl From<TxType> for u8 {
|
||||
TxType::Eip2930 => EIP2930_TX_TYPE_ID,
|
||||
TxType::Eip1559 => EIP1559_TX_TYPE_ID,
|
||||
TxType::Eip4844 => EIP4844_TX_TYPE_ID,
|
||||
TxType::Eip7702 => EIP7702_TX_TYPE_ID,
|
||||
#[cfg(feature = "optimism")]
|
||||
TxType::Deposit => DEPOSIT_TX_TYPE_ID,
|
||||
}
|
||||
@ -98,6 +104,8 @@ impl TryFrom<u8> for TxType {
|
||||
return Ok(Self::Eip1559)
|
||||
} else if value == Self::Eip4844 {
|
||||
return Ok(Self::Eip4844)
|
||||
} else if value == Self::Eip7702 {
|
||||
return Ok(Self::Eip7702)
|
||||
}
|
||||
|
||||
Err("invalid tx type")
|
||||
@ -137,6 +145,10 @@ impl Compact for TxType {
|
||||
buf.put_u8(self as u8);
|
||||
3
|
||||
}
|
||||
Self::Eip7702 => {
|
||||
buf.put_u8(self as u8);
|
||||
3
|
||||
}
|
||||
#[cfg(feature = "optimism")]
|
||||
Self::Deposit => {
|
||||
buf.put_u8(self as u8);
|
||||
@ -158,6 +170,7 @@ impl Compact for TxType {
|
||||
let extended_identifier = buf.get_u8();
|
||||
match extended_identifier {
|
||||
EIP4844_TX_TYPE_ID => Self::Eip4844,
|
||||
EIP7702_TX_TYPE_ID => Self::Eip7702,
|
||||
#[cfg(feature = "optimism")]
|
||||
DEPOSIT_TX_TYPE_ID => Self::Deposit,
|
||||
_ => panic!("Unsupported TxType identifier: {extended_identifier}"),
|
||||
@ -222,12 +235,15 @@ mod tests {
|
||||
// Test for EIP4844 transaction
|
||||
assert_eq!(TxType::try_from(U64::from(3)).unwrap(), TxType::Eip4844);
|
||||
|
||||
// Test for EIP7702 transaction
|
||||
assert_eq!(TxType::try_from(U64::from(4)).unwrap(), TxType::Eip7702);
|
||||
|
||||
// Test for Deposit transaction
|
||||
#[cfg(feature = "optimism")]
|
||||
assert_eq!(TxType::try_from(U64::from(126)).unwrap(), TxType::Deposit);
|
||||
|
||||
// For transactions with unsupported values
|
||||
assert!(TxType::try_from(U64::from(4)).is_err());
|
||||
assert!(TxType::try_from(U64::from(5)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -237,6 +253,7 @@ mod tests {
|
||||
(TxType::Eip2930, 1, vec![]),
|
||||
(TxType::Eip1559, 2, vec![]),
|
||||
(TxType::Eip4844, 3, vec![EIP4844_TX_TYPE_ID]),
|
||||
(TxType::Eip7702, 3, vec![EIP7702_TX_TYPE_ID]),
|
||||
#[cfg(feature = "optimism")]
|
||||
(TxType::Deposit, 3, vec![DEPOSIT_TX_TYPE_ID]),
|
||||
];
|
||||
@ -259,6 +276,7 @@ mod tests {
|
||||
(TxType::Eip2930, 1, vec![]),
|
||||
(TxType::Eip1559, 2, vec![]),
|
||||
(TxType::Eip4844, 3, vec![EIP4844_TX_TYPE_ID]),
|
||||
(TxType::Eip7702, 3, vec![EIP7702_TX_TYPE_ID]),
|
||||
#[cfg(feature = "optimism")]
|
||||
(TxType::Deposit, 3, vec![DEPOSIT_TX_TYPE_ID]),
|
||||
];
|
||||
@ -291,6 +309,10 @@ mod tests {
|
||||
let tx_type = TxType::decode(&mut &[3u8][..]).unwrap();
|
||||
assert_eq!(tx_type, TxType::Eip4844);
|
||||
|
||||
// Test for EIP7702 transaction
|
||||
let tx_type = TxType::decode(&mut &[4u8][..]).unwrap();
|
||||
assert_eq!(tx_type, TxType::Eip7702);
|
||||
|
||||
// Test random byte not in range
|
||||
let buf = [rand::thread_rng().gen_range(4..=u8::MAX)];
|
||||
println!("{buf:?}");
|
||||
|
||||
@ -54,4 +54,4 @@ optimism = [
|
||||
"revm/optimism",
|
||||
"reth-provider/optimism",
|
||||
"reth-rpc-eth-types/optimism"
|
||||
]
|
||||
]
|
||||
|
||||
@ -807,6 +807,7 @@ pub trait Call: LoadState + SpawnBlocking {
|
||||
chain_id,
|
||||
blob_versioned_hashes,
|
||||
max_fee_per_blob_gas,
|
||||
// authorization_list,
|
||||
..
|
||||
} = request;
|
||||
|
||||
@ -838,7 +839,9 @@ pub trait Call: LoadState + SpawnBlocking {
|
||||
// EIP-4844 fields
|
||||
blob_hashes: blob_versioned_hashes.unwrap_or_default(),
|
||||
max_fee_per_blob_gas,
|
||||
// EIP-7702 fields
|
||||
authorization_list: None,
|
||||
// authorization_list: TODO
|
||||
#[cfg(feature = "optimism")]
|
||||
optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() },
|
||||
};
|
||||
|
||||
@ -498,6 +498,7 @@ impl From<reth_primitives::InvalidTransactionError> for RpcInvalidTransactionErr
|
||||
InvalidTransactionError::Eip2930Disabled |
|
||||
InvalidTransactionError::Eip1559Disabled |
|
||||
InvalidTransactionError::Eip4844Disabled |
|
||||
InvalidTransactionError::Eip7702Disabled |
|
||||
InvalidTransactionError::TxTypeNotSupported => Self::TxTypeNotSupported,
|
||||
InvalidTransactionError::GasUintOverflow => Self::GasUintOverflow,
|
||||
InvalidTransactionError::GasTooLow => Self::GasTooLow,
|
||||
|
||||
@ -73,6 +73,7 @@ fn fill(
|
||||
let chain_id = signed_tx.chain_id();
|
||||
let blob_versioned_hashes = signed_tx.blob_versioned_hashes();
|
||||
let access_list = signed_tx.access_list().cloned();
|
||||
let _authorization_list = signed_tx.authorization_list();
|
||||
|
||||
let signature =
|
||||
from_primitive_signature(*signed_tx.signature(), signed_tx.tx_type(), signed_tx.chain_id());
|
||||
@ -124,6 +125,7 @@ pub fn transaction_to_call_request(tx: TransactionSignedEcRecovered) -> Transact
|
||||
let chain_id = tx.transaction.chain_id();
|
||||
let access_list = tx.transaction.access_list().cloned();
|
||||
let max_fee_per_blob_gas = tx.transaction.max_fee_per_blob_gas();
|
||||
let _authorization_list = tx.transaction.authorization_list();
|
||||
let blob_versioned_hashes = tx.transaction.blob_versioned_hashes();
|
||||
let tx_type = tx.transaction.tx_type();
|
||||
|
||||
|
||||
@ -31,6 +31,7 @@ alloy-eips = { workspace = true, default-features = false, features = [
|
||||
"serde",
|
||||
] }
|
||||
alloy-primitives = { workspace = true, features = ["arbitrary", "serde"] }
|
||||
alloy-consensus = { workspace = true, features = ["arbitrary"] }
|
||||
test-fuzz.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
|
||||
96
crates/storage/codecs/src/alloy/authorization_list.rs
Normal file
96
crates/storage/codecs/src/alloy/authorization_list.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use crate::Compact;
|
||||
use alloy_eips::eip7702::{Authorization as AlloyAuthorization, SignedAuthorization};
|
||||
use alloy_primitives::{Address, ChainId, U256};
|
||||
use bytes::Buf;
|
||||
use reth_codecs_derive::main_codec;
|
||||
|
||||
/// Authorization acts as bridge which simplifies Compact implementation for AlloyAuthorization.
|
||||
///
|
||||
/// Notice: Make sure this struct is 1:1 with `alloy_eips::eip7702::Authorization`
|
||||
#[main_codec]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
struct Authorization {
|
||||
chain_id: ChainId,
|
||||
address: Address,
|
||||
nonce: Option<u64>,
|
||||
}
|
||||
|
||||
impl Compact for AlloyAuthorization {
|
||||
fn to_compact<B>(self, buf: &mut B) -> usize
|
||||
where
|
||||
B: bytes::BufMut + AsMut<[u8]>,
|
||||
{
|
||||
let authorization =
|
||||
Authorization { chain_id: self.chain_id, address: self.address, nonce: self.nonce() };
|
||||
authorization.to_compact(buf)
|
||||
}
|
||||
|
||||
fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
|
||||
let (authorization, buf) = Authorization::from_compact(buf, len);
|
||||
let alloy_authorization = Self {
|
||||
chain_id: authorization.chain_id,
|
||||
address: authorization.address,
|
||||
nonce: authorization.nonce.into(),
|
||||
};
|
||||
(alloy_authorization, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Compact for SignedAuthorization<alloy_primitives::Signature> {
|
||||
fn to_compact<B>(self, buf: &mut B) -> usize
|
||||
where
|
||||
B: bytes::BufMut + AsMut<[u8]>,
|
||||
{
|
||||
let (auth, signature) = self.into_parts();
|
||||
let (v, r, s) = (signature.v(), signature.r(), signature.s());
|
||||
buf.put_u8(v.y_parity_byte());
|
||||
buf.put_slice(r.as_le_slice());
|
||||
buf.put_slice(s.as_le_slice());
|
||||
|
||||
// to_compact doesn't write the len to buffer.
|
||||
// By placing it as last, we don't need to store it either.
|
||||
1 + 32 + 32 + auth.to_compact(buf)
|
||||
}
|
||||
|
||||
fn from_compact(mut buf: &[u8], len: usize) -> (Self, &[u8]) {
|
||||
let y = alloy_primitives::Parity::Parity(buf.get_u8() == 1);
|
||||
let r = U256::from_le_slice(&buf[0..32]);
|
||||
buf.advance(32);
|
||||
let s = U256::from_le_slice(&buf[0..32]);
|
||||
buf.advance(32);
|
||||
|
||||
let signature = alloy_primitives::Signature::from_rs_and_parity(r, s, y)
|
||||
.expect("invalid authorization signature");
|
||||
let (auth, buf) = AlloyAuthorization::from_compact(buf, len);
|
||||
|
||||
(auth.into_signed(signature), buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloy_primitives::{address, b256};
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_compact_authorization_list_item() {
|
||||
let authorization = AlloyAuthorization {
|
||||
chain_id: 1,
|
||||
address: address!("dac17f958d2ee523a2206206994597c13d831ec7"),
|
||||
nonce: None.into(),
|
||||
}
|
||||
.into_signed(
|
||||
alloy_primitives::Signature::from_rs_and_parity(
|
||||
b256!("1fd474b1f9404c0c5df43b7620119ffbc3a1c3f942c73b6e14e9f55255ed9b1d").into(),
|
||||
b256!("29aca24813279a901ec13b5f7bb53385fa1fc627b946592221417ff74a49600d").into(),
|
||||
false,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let mut compacted_authorization = Vec::<u8>::new();
|
||||
let len = authorization.clone().to_compact(&mut compacted_authorization);
|
||||
let (decoded_authorization, _) =
|
||||
SignedAuthorization::from_compact(&compacted_authorization, len);
|
||||
assert_eq!(authorization, decoded_authorization);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
mod access_list;
|
||||
mod authorization_list;
|
||||
mod genesis_account;
|
||||
mod log;
|
||||
mod request;
|
||||
|
||||
@ -241,7 +241,8 @@ impl InvalidPoolTransactionError {
|
||||
}
|
||||
InvalidTransactionError::Eip2930Disabled |
|
||||
InvalidTransactionError::Eip1559Disabled |
|
||||
InvalidTransactionError::Eip4844Disabled => {
|
||||
InvalidTransactionError::Eip4844Disabled |
|
||||
InvalidTransactionError::Eip7702Disabled => {
|
||||
// settings
|
||||
false
|
||||
}
|
||||
|
||||
@ -749,6 +749,10 @@ impl EthPoolTransaction for MockTransaction {
|
||||
_ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
|
||||
}
|
||||
}
|
||||
|
||||
fn authorization_count(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromRecoveredTransaction for MockTransaction {
|
||||
|
||||
@ -14,7 +14,7 @@ use reth_primitives::{
|
||||
BlobTransactionSidecar, BlobTransactionValidationError, FromRecoveredPooledTransaction,
|
||||
IntoRecoveredTransaction, PooledTransactionsElement, PooledTransactionsElementEcRecovered,
|
||||
SealedBlock, Transaction, TransactionSignedEcRecovered, TryFromRecoveredTransaction, TxHash,
|
||||
TxKind, B256, EIP1559_TX_TYPE_ID, EIP4844_TX_TYPE_ID, U256,
|
||||
TxKind, B256, EIP1559_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, U256,
|
||||
};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -842,6 +842,11 @@ pub trait PoolTransaction:
|
||||
self.tx_type() == EIP4844_TX_TYPE_ID
|
||||
}
|
||||
|
||||
/// Returns true if the transaction is an EIP-7702 transaction.
|
||||
fn is_eip7702(&self) -> bool {
|
||||
self.tx_type() == EIP7702_TX_TYPE_ID
|
||||
}
|
||||
|
||||
/// Returns the length of the rlp encoded transaction object
|
||||
///
|
||||
/// Note: Implementations should cache this value.
|
||||
@ -866,6 +871,9 @@ pub trait EthPoolTransaction: PoolTransaction {
|
||||
blob: &BlobTransactionSidecar,
|
||||
settings: &KzgSettings,
|
||||
) -> Result<(), BlobTransactionValidationError>;
|
||||
|
||||
/// Returns the number of authorizations this transaction has.
|
||||
fn authorization_count(&self) -> usize;
|
||||
}
|
||||
|
||||
/// The default [`PoolTransaction`] for the [Pool](crate::Pool) for Ethereum.
|
||||
@ -938,6 +946,9 @@ impl EthPooledTransaction {
|
||||
blob_sidecar = EthBlobTransactionSidecar::Missing;
|
||||
U256::from(t.max_fee_per_gas).saturating_mul(U256::from(t.gas_limit))
|
||||
}
|
||||
Transaction::Eip7702(t) => {
|
||||
U256::from(t.max_fee_per_gas).saturating_mul(U256::from(t.gas_limit))
|
||||
}
|
||||
_ => U256::ZERO,
|
||||
};
|
||||
let mut cost = transaction.value();
|
||||
@ -1024,6 +1035,7 @@ impl PoolTransaction for EthPooledTransaction {
|
||||
Transaction::Eip2930(tx) => tx.gas_price,
|
||||
Transaction::Eip1559(tx) => tx.max_fee_per_gas,
|
||||
Transaction::Eip4844(tx) => tx.max_fee_per_gas,
|
||||
Transaction::Eip7702(tx) => tx.max_fee_per_gas,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
@ -1041,6 +1053,7 @@ impl PoolTransaction for EthPooledTransaction {
|
||||
Transaction::Legacy(_) | Transaction::Eip2930(_) => None,
|
||||
Transaction::Eip1559(tx) => Some(tx.max_priority_fee_per_gas),
|
||||
Transaction::Eip4844(tx) => Some(tx.max_priority_fee_per_gas),
|
||||
Transaction::Eip7702(tx) => Some(tx.max_priority_fee_per_gas),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -1120,6 +1133,13 @@ impl EthPoolTransaction for EthPooledTransaction {
|
||||
_ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
|
||||
}
|
||||
}
|
||||
|
||||
fn authorization_count(&self) -> usize {
|
||||
match &self.transaction.transaction {
|
||||
Transaction::Eip7702(tx) => tx.authorization_list.len(),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromRecoveredTransaction for EthPooledTransaction {
|
||||
@ -1130,7 +1150,7 @@ impl TryFromRecoveredTransaction for EthPooledTransaction {
|
||||
) -> Result<Self, Self::Error> {
|
||||
// ensure we can handle the transaction type and its format
|
||||
match tx.tx_type() as u8 {
|
||||
0..=EIP1559_TX_TYPE_ID => {
|
||||
0..=EIP1559_TX_TYPE_ID | EIP7702_TX_TYPE_ID => {
|
||||
// supported
|
||||
}
|
||||
EIP4844_TX_TYPE_ID => {
|
||||
|
||||
@ -12,14 +12,14 @@ use crate::{
|
||||
use reth_chainspec::{ChainSpec, EthereumHardforks};
|
||||
use reth_primitives::{
|
||||
constants::{eip4844::MAX_BLOBS_PER_BLOCK, ETHEREUM_BLOCK_GAS_LIMIT},
|
||||
GotExpected, InvalidTransactionError, SealedBlock, TxKind, EIP1559_TX_TYPE_ID,
|
||||
EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
|
||||
GotExpected, InvalidTransactionError, SealedBlock, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID,
|
||||
EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
|
||||
};
|
||||
use reth_provider::{AccountReader, BlockReaderIdExt, StateProviderFactory};
|
||||
use reth_tasks::TaskSpawner;
|
||||
use revm::{
|
||||
interpreter::gas::validate_initial_tx_gas,
|
||||
primitives::{AccessListItem, EnvKzgSettings, SpecId},
|
||||
primitives::{EnvKzgSettings, SpecId},
|
||||
};
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
@ -119,6 +119,8 @@ pub(crate) struct EthTransactionValidatorInner<Client, T> {
|
||||
eip1559: bool,
|
||||
/// Fork indicator whether we are using EIP-4844 blob transactions.
|
||||
eip4844: bool,
|
||||
/// Fork indicator whether we are using EIP-7702 type transactions.
|
||||
eip7702: bool,
|
||||
/// The current max gas limit
|
||||
block_gas_limit: u64,
|
||||
/// Minimum priority fee to enforce for acceptance into the pool.
|
||||
@ -185,6 +187,15 @@ where
|
||||
)
|
||||
}
|
||||
}
|
||||
EIP7702_TX_TYPE_ID => {
|
||||
// Reject EIP-7702 transactions.
|
||||
if !self.eip7702 {
|
||||
return TransactionValidationOutcome::Invalid(
|
||||
transaction,
|
||||
InvalidTransactionError::Eip7702Disabled.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
return TransactionValidationOutcome::Invalid(
|
||||
@ -255,9 +266,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// intrinsic gas checks
|
||||
let is_shanghai = self.fork_tracker.is_shanghai_activated();
|
||||
if let Err(err) = ensure_intrinsic_gas(&transaction, is_shanghai) {
|
||||
if transaction.is_eip7702() {
|
||||
// Cancun fork is required for 7702 txs
|
||||
if !self.fork_tracker.is_prague_activated() {
|
||||
return TransactionValidationOutcome::Invalid(
|
||||
transaction,
|
||||
InvalidTransactionError::TxTypeNotSupported.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = ensure_intrinsic_gas(&transaction, &self.fork_tracker) {
|
||||
return TransactionValidationOutcome::Invalid(transaction, err)
|
||||
}
|
||||
|
||||
@ -407,6 +426,10 @@ where
|
||||
if self.chain_spec.is_shanghai_active_at_timestamp(new_tip_block.timestamp) {
|
||||
self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
if self.chain_spec.is_prague_active_at_timestamp(new_tip_block.timestamp) {
|
||||
self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -418,12 +441,16 @@ pub struct EthTransactionValidatorBuilder {
|
||||
shanghai: bool,
|
||||
/// Fork indicator whether we are in the Cancun hardfork.
|
||||
cancun: bool,
|
||||
/// Fork indicator whether we are in the Cancun hardfork.
|
||||
prague: bool,
|
||||
/// Whether using EIP-2718 type transactions is allowed
|
||||
eip2718: bool,
|
||||
/// Whether using EIP-1559 type transactions is allowed
|
||||
eip1559: bool,
|
||||
/// Whether using EIP-4844 type transactions is allowed
|
||||
eip4844: bool,
|
||||
/// Whether using EIP-7702 type transactions is allowed
|
||||
eip7702: bool,
|
||||
/// The current max gas limit
|
||||
block_gas_limit: u64,
|
||||
/// Minimum priority fee to enforce for acceptance into the pool.
|
||||
@ -464,12 +491,16 @@ impl EthTransactionValidatorBuilder {
|
||||
eip2718: true,
|
||||
eip1559: true,
|
||||
eip4844: true,
|
||||
eip7702: true,
|
||||
|
||||
// shanghai is activated by default
|
||||
shanghai: true,
|
||||
|
||||
// cancun is activated by default
|
||||
cancun: true,
|
||||
|
||||
// prague not yet activated
|
||||
prague: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -504,6 +535,17 @@ impl EthTransactionValidatorBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Disables the Prague fork.
|
||||
pub const fn no_prague(self) -> Self {
|
||||
self.set_prague(false)
|
||||
}
|
||||
|
||||
/// Set the Prague fork.
|
||||
pub const fn set_prague(mut self, prague: bool) -> Self {
|
||||
self.prague = prague;
|
||||
self
|
||||
}
|
||||
|
||||
/// Disables the support for EIP-2718 transactions.
|
||||
pub const fn no_eip2718(self) -> Self {
|
||||
self.set_eip2718(false)
|
||||
@ -591,9 +633,11 @@ impl EthTransactionValidatorBuilder {
|
||||
chain_spec,
|
||||
shanghai,
|
||||
cancun,
|
||||
prague,
|
||||
eip2718,
|
||||
eip1559,
|
||||
eip4844,
|
||||
eip7702,
|
||||
block_gas_limit,
|
||||
minimum_priority_fee,
|
||||
kzg_settings,
|
||||
@ -602,8 +646,11 @@ impl EthTransactionValidatorBuilder {
|
||||
..
|
||||
} = self;
|
||||
|
||||
let fork_tracker =
|
||||
ForkTracker { shanghai: AtomicBool::new(shanghai), cancun: AtomicBool::new(cancun) };
|
||||
let fork_tracker = ForkTracker {
|
||||
shanghai: AtomicBool::new(shanghai),
|
||||
cancun: AtomicBool::new(cancun),
|
||||
prague: AtomicBool::new(prague),
|
||||
};
|
||||
|
||||
let inner = EthTransactionValidatorInner {
|
||||
chain_spec,
|
||||
@ -612,6 +659,7 @@ impl EthTransactionValidatorBuilder {
|
||||
eip1559,
|
||||
fork_tracker,
|
||||
eip4844,
|
||||
eip7702,
|
||||
block_gas_limit,
|
||||
minimum_priority_fee,
|
||||
blob_store: Box::new(blob_store),
|
||||
@ -670,23 +718,30 @@ impl EthTransactionValidatorBuilder {
|
||||
|
||||
/// Keeps track of whether certain forks are activated
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ForkTracker {
|
||||
pub struct ForkTracker {
|
||||
/// Tracks if shanghai is activated at the block's timestamp.
|
||||
pub(crate) shanghai: AtomicBool,
|
||||
pub shanghai: AtomicBool,
|
||||
/// Tracks if cancun is activated at the block's timestamp.
|
||||
pub(crate) cancun: AtomicBool,
|
||||
pub cancun: AtomicBool,
|
||||
/// Tracks if prague is activated at the block's timestamp.
|
||||
pub prague: AtomicBool,
|
||||
}
|
||||
|
||||
impl ForkTracker {
|
||||
/// Returns `true` if Shanghai fork is activated.
|
||||
pub(crate) fn is_shanghai_activated(&self) -> bool {
|
||||
pub fn is_shanghai_activated(&self) -> bool {
|
||||
self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Returns `true` if Cancun fork is activated.
|
||||
pub(crate) fn is_cancun_activated(&self) -> bool {
|
||||
pub fn is_cancun_activated(&self) -> bool {
|
||||
self.cancun.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Returns `true` if Prague fork is activated.
|
||||
pub fn is_prague_activated(&self) -> bool {
|
||||
self.prague.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure that the code size is not greater than `max_init_code_size`.
|
||||
@ -707,39 +762,34 @@ pub fn ensure_max_init_code_size<T: PoolTransaction>(
|
||||
|
||||
/// Ensures that gas limit of the transaction exceeds the intrinsic gas of the transaction.
|
||||
///
|
||||
/// See also [`calculate_intrinsic_gas_after_merge`]
|
||||
pub fn ensure_intrinsic_gas<T: PoolTransaction>(
|
||||
/// Caution: This only checks past the Merge hardfork.
|
||||
pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
|
||||
transaction: &T,
|
||||
is_shanghai: bool,
|
||||
fork_tracker: &ForkTracker,
|
||||
) -> Result<(), InvalidPoolTransactionError> {
|
||||
if transaction.gas_limit() <
|
||||
calculate_intrinsic_gas_after_merge(
|
||||
transaction.input(),
|
||||
&transaction.kind(),
|
||||
transaction.access_list().map(|list| list.0.as_slice()).unwrap_or(&[]),
|
||||
is_shanghai,
|
||||
)
|
||||
{
|
||||
let spec_id = if fork_tracker.is_prague_activated() {
|
||||
SpecId::PRAGUE
|
||||
} else if fork_tracker.is_shanghai_activated() {
|
||||
SpecId::SHANGHAI
|
||||
} else {
|
||||
SpecId::MERGE
|
||||
};
|
||||
|
||||
let gas_after_merge = validate_initial_tx_gas(
|
||||
spec_id,
|
||||
transaction.input(),
|
||||
transaction.kind().is_create(),
|
||||
transaction.access_list().map(|list| list.0.as_slice()).unwrap_or(&[]),
|
||||
transaction.authorization_count() as u64,
|
||||
);
|
||||
|
||||
if transaction.gas_limit() < gas_after_merge {
|
||||
Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the Intrinsic Gas usage for a Transaction
|
||||
///
|
||||
/// Caution: This only checks past the Merge hardfork.
|
||||
#[inline]
|
||||
pub fn calculate_intrinsic_gas_after_merge(
|
||||
input: &[u8],
|
||||
kind: &TxKind,
|
||||
access_list: &[AccessListItem],
|
||||
is_shanghai: bool,
|
||||
) -> u64 {
|
||||
let spec_id = if is_shanghai { SpecId::SHANGHAI } else { SpecId::MERGE };
|
||||
validate_initial_tx_gas(spec_id, input, kind.is_create(), access_list, 0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -764,10 +814,14 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn validate_transaction() {
|
||||
let transaction = get_transaction();
|
||||
let mut fork_tracker =
|
||||
ForkTracker { shanghai: false.into(), cancun: false.into(), prague: false.into() };
|
||||
|
||||
let res = ensure_intrinsic_gas(&transaction, false);
|
||||
let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
|
||||
assert!(res.is_ok());
|
||||
let res = ensure_intrinsic_gas(&transaction, true);
|
||||
|
||||
fork_tracker.shanghai = true.into();
|
||||
let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
|
||||
assert!(res.is_ok());
|
||||
|
||||
let provider = MockEthProvider::default();
|
||||
|
||||
Reference in New Issue
Block a user