Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement EIP-7702 span batch support #135

Merged
merged 6 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/protocol/src/batch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub use single::SingleBatch;
mod tx_data;
pub use tx_data::{
SpanBatchEip1559TransactionData, SpanBatchEip2930TransactionData,
SpanBatchLegacyTransactionData, SpanBatchTransactionData,
SpanBatchEip7702TransactionData, SpanBatchLegacyTransactionData, SpanBatchTransactionData,
};

mod traits;
Expand Down
25 changes: 24 additions & 1 deletion crates/protocol/src/batch/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ impl SpanBatchTransactions {
let (tx, sig) = (tx.tx(), tx.signature());
(sig, tx.to(), tx.nonce(), tx.gas_limit(), tx.chain_id())
}
TxEnvelope::Eip7702(tx) => {
let (tx, sig) = (tx.tx(), tx.signature());
(sig, tx.to(), tx.nonce(), tx.gas_limit(), tx.chain_id())
}
_ => {
return Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))
}
Expand Down Expand Up @@ -346,7 +350,7 @@ impl SpanBatchTransactions {
#[cfg(test)]
mod tests {
use super::*;
use alloy_consensus::{Signed, TxEip1559, TxEip2930};
use alloy_consensus::{Signed, TxEip1559, TxEip2930, TxEip7702};
use alloy_primitives::{address, PrimitiveSignature as Signature, TxKind};

#[test]
Expand Down Expand Up @@ -414,4 +418,23 @@ mod tests {
assert_eq!(result, Ok(()));
assert_eq!(span_batch_txs.total_block_tx_count, 1);
}

#[test]
fn test_span_batch_transactions_add_eip7702_tx() {
let sig = Signature::test_signature();
let to = address!("0123456789012345678901234567890123456789");
let tx = TxEnvelope::Eip7702(Signed::new_unchecked(
TxEip7702 { to, chain_id: 1, ..Default::default() },
sig,
Default::default(),
));
let mut span_batch_txs = SpanBatchTransactions::default();
let mut buf = vec![];
tx.encode(&mut buf);
let txs = vec![Bytes::from(buf)];
let chain_id = 1;
let result = span_batch_txs.add_txs(txs, chain_id);
assert_eq!(result, Ok(()));
assert_eq!(span_batch_txs.total_block_tx_count, 1);
}
}
99 changes: 99 additions & 0 deletions crates/protocol/src/batch/tx_data/eip7702.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//! This module contains the eip7702 transaction data type for a span batch.

use crate::{SpanBatchError, SpanDecodingError};
use alloc::vec::Vec;
use alloy_consensus::{SignableTransaction, Signed, TxEip7702};
use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization};
use alloy_primitives::{Address, PrimitiveSignature as Signature, U256};
use alloy_rlp::{Bytes, RlpDecodable, RlpEncodable};

/// The transaction data for an EIP-7702 transaction within a span batch.
#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
pub struct SpanBatchEip7702TransactionData {
/// The ETH value of the transaction.
pub value: U256,
/// Maximum priority fee per gas.
pub max_priority_fee_per_gas: U256,
/// Maximum fee per gas.
pub max_fee_per_gas: U256,
/// Transaction calldata.
pub data: Bytes,
/// Access list, used to pre-warm storage slots through static declaration.
pub access_list: AccessList,
/// Authorization list, used to allow a signer to delegate code to a contract
pub authorization_list: Vec<SignedAuthorization>,
}

impl SpanBatchEip7702TransactionData {
/// Converts [SpanBatchEip7702TransactionData] into a signed [`TxEip7702`].
pub fn to_signed_tx(
&self,
nonce: u64,
gas: u64,
to: Address,
chain_id: u64,
signature: Signature,
) -> Result<Signed<TxEip7702>, SpanBatchError> {
let eip7702_tx = TxEip7702 {
chain_id,
nonce,
max_fee_per_gas: u128::from_be_bytes(
self.max_fee_per_gas.to_be_bytes::<32>()[16..].try_into().map_err(|_| {
SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData)
})?,
),
max_priority_fee_per_gas: u128::from_be_bytes(
self.max_priority_fee_per_gas.to_be_bytes::<32>()[16..].try_into().map_err(
|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData),
)?,
),
gas_limit: gas,
to,
value: self.value,
input: self.data.clone().into(),
access_list: self.access_list.clone(),
authorization_list: self.authorization_list.clone(),
};
let signature_hash = eip7702_tx.signature_hash();
Ok(Signed::new_unchecked(eip7702_tx, signature, signature_hash))
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::SpanBatchTransactionData;
use alloc::vec::Vec;
use alloy_rlp::{Decodable, Encodable};
use revm::primitives::Authorization;

#[test]
fn encode_eip7702_tx_data_roundtrip() {
let authorization = Authorization {
chain_id: U256::from(0x01),
address: Address::left_padding_from(&[0x01, 0x02, 0x03]),
nonce: 2,
};
let signature = Signature::test_signature();
let arb_authorization: SignedAuthorization = authorization.into_signed(signature);

let variable_fee_tx = SpanBatchEip7702TransactionData {
value: U256::from(0xFF),
max_fee_per_gas: U256::from(0xEE),
max_priority_fee_per_gas: U256::from(0xDD),
data: Bytes::from(alloc::vec![0x01, 0x02, 0x03]),
access_list: AccessList::default(),
authorization_list: vec![arb_authorization],
};

let mut encoded_buf = Vec::new();
SpanBatchTransactionData::Eip7702(variable_fee_tx.clone()).encode(&mut encoded_buf);

let decoded = SpanBatchTransactionData::decode(&mut encoded_buf.as_slice()).unwrap();
let SpanBatchTransactionData::Eip7702(variable_fee_decoded) = decoded else {
panic!("Expected SpanBatchEip7702TransactionData, got {:?}", decoded);
};

assert_eq!(variable_fee_tx, variable_fee_decoded);
}
}
3 changes: 3 additions & 0 deletions crates/protocol/src/batch/tx_data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ pub use eip1559::SpanBatchEip1559TransactionData;

mod eip2930;
pub use eip2930::SpanBatchEip2930TransactionData;

mod eip7702;
pub use eip7702::SpanBatchEip7702TransactionData;
34 changes: 32 additions & 2 deletions crates/protocol/src/batch/tx_data/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use alloy_primitives::{Address, PrimitiveSignature as Signature, U256};
use alloy_rlp::{Bytes, Decodable, Encodable};

use crate::{
SpanBatchEip1559TransactionData, SpanBatchEip2930TransactionData, SpanBatchError,
SpanBatchLegacyTransactionData, SpanDecodingError,
SpanBatchEip1559TransactionData, SpanBatchEip2930TransactionData,
SpanBatchEip7702TransactionData, SpanBatchError, SpanBatchLegacyTransactionData,
SpanDecodingError,
};

/// The typed transaction data for a transaction within a span batch.
Expand All @@ -18,6 +19,8 @@ pub enum SpanBatchTransactionData {
Eip2930(SpanBatchEip2930TransactionData),
/// EIP-1559 transaction data.
Eip1559(SpanBatchEip1559TransactionData),
/// EIP-7702 transaction data.
Eip7702(SpanBatchEip7702TransactionData),
}

impl Encodable for SpanBatchTransactionData {
Expand All @@ -34,6 +37,10 @@ impl Encodable for SpanBatchTransactionData {
out.put_u8(TxType::Eip1559 as u8);
data.encode(out);
}
Self::Eip7702(data) => {
out.put_u8(TxType::Eip7702 as u8);
data.encode(out);
}
}
}
}
Expand Down Expand Up @@ -81,6 +88,17 @@ impl TryFrom<&TxEnvelope> for SpanBatchTransactionData {
access_list: s.access_list.clone(),
}))
}
TxEnvelope::Eip7702(s) => {
let s = s.tx();
Ok(Self::Eip7702(SpanBatchEip7702TransactionData {
value: s.value,
max_fee_per_gas: U256::from(s.max_fee_per_gas),
max_priority_fee_per_gas: U256::from(s.max_priority_fee_per_gas),
data: Bytes::from(s.input().to_vec()),
access_list: s.access_list.clone(),
authorization_list: s.authorization_list.clone(),
}))
}
_ => Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType)),
}
}
Expand All @@ -93,6 +111,7 @@ impl SpanBatchTransactionData {
Self::Legacy(_) => TxType::Legacy,
Self::Eip2930(_) => TxType::Eip2930,
Self::Eip1559(_) => TxType::Eip1559,
Self::Eip7702(_) => TxType::Eip7702,
}
}

Expand All @@ -109,6 +128,9 @@ impl SpanBatchTransactionData {
TxType::Eip1559 => {
Ok(Self::Eip1559(SpanBatchEip1559TransactionData::decode(&mut &b[1..])?))
}
TxType::Eip7702 => {
Ok(Self::Eip7702(SpanBatchEip7702TransactionData::decode(&mut &b[1..])?))
}
_ => Err(alloy_rlp::Error::Custom("Invalid transaction type")),
}
}
Expand Down Expand Up @@ -138,6 +160,14 @@ impl SpanBatchTransactionData {
Self::Eip1559(data) => {
TxEnvelope::Eip1559(data.to_signed_tx(nonce, gas, to, chain_id, signature)?)
}
Self::Eip7702(data) => {
let Some(addr) = to else {
return Err(SpanBatchError::Decoding(
SpanDecodingError::InvalidTransactionData,
));
};
TxEnvelope::Eip7702(data.to_signed_tx(nonce, gas, addr, chain_id, signature)?)
}
})
}
}
7 changes: 4 additions & 3 deletions crates/protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ pub use batch::{
Batch, BatchDecodingError, BatchEncodingError, BatchReader, BatchTransaction, BatchType,
BatchValidationProvider, BatchValidity, BatchWithInclusionBlock, RawSpanBatch, SingleBatch,
SpanBatch, SpanBatchBits, SpanBatchEip1559TransactionData, SpanBatchEip2930TransactionData,
SpanBatchElement, SpanBatchError, SpanBatchLegacyTransactionData, SpanBatchPayload,
SpanBatchPrefix, SpanBatchTransactionData, SpanBatchTransactions, SpanDecodingError,
MAX_SPAN_BATCH_ELEMENTS, SINGLE_BATCH_TYPE, SPAN_BATCH_TYPE,
SpanBatchEip7702TransactionData, SpanBatchElement, SpanBatchError,
SpanBatchLegacyTransactionData, SpanBatchPayload, SpanBatchPrefix, SpanBatchTransactionData,
SpanBatchTransactions, SpanDecodingError, MAX_SPAN_BATCH_ELEMENTS, SINGLE_BATCH_TYPE,
SPAN_BATCH_TYPE,
};

mod errors;
Expand Down