op_alloy_consensus/transaction/
envelope.rsuse alloy_consensus::{
Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip4844WithSidecar, TxLegacy,
};
use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718};
use alloy_rlp::{Decodable, Encodable, Header};
use derive_more::Display;
use crate::TxDeposit;
pub const DEPOSIT_TX_TYPE_ID: u8 = 126; #[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Display)]
pub enum OpTxType {
#[display("legacy")]
Legacy = 0,
#[display("eip2930")]
Eip2930 = 1,
#[display("eip1559")]
Eip1559 = 2,
#[display("eip4844")]
Eip4844 = 3,
#[display("deposit")]
Deposit = 126,
}
impl OpTxType {
pub const ALL: [Self; 5] =
[Self::Legacy, Self::Eip2930, Self::Eip1559, Self::Eip4844, Self::Deposit];
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for OpTxType {
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let i = u.choose_index(Self::ALL.len())?;
Ok(Self::ALL[i])
}
}
impl From<OpTxType> for u8 {
fn from(v: OpTxType) -> Self {
v as Self
}
}
impl TryFrom<u8> for OpTxType {
type Error = Eip2718Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
0 => Self::Legacy,
1 => Self::Eip2930,
2 => Self::Eip1559,
3 => Self::Eip4844,
126 => Self::Deposit,
_ => return Err(Eip2718Error::UnexpectedType(value)),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
#[non_exhaustive]
pub enum OpTxEnvelope {
#[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))]
Legacy(Signed<TxLegacy>),
#[cfg_attr(feature = "serde", serde(rename = "0x1", alias = "0x01"))]
Eip2930(Signed<TxEip2930>),
#[cfg_attr(feature = "serde", serde(rename = "0x2", alias = "0x02"))]
Eip1559(Signed<TxEip1559>),
#[cfg_attr(feature = "serde", serde(rename = "0x3", alias = "0x03"))]
Eip4844(Signed<TxEip4844Variant>),
#[cfg_attr(feature = "serde", serde(rename = "0x7E", alias = "0x7E"))]
Deposit(TxDeposit),
}
impl From<Signed<TxLegacy>> for OpTxEnvelope {
fn from(v: Signed<TxLegacy>) -> Self {
Self::Legacy(v)
}
}
impl From<Signed<TxEip2930>> for OpTxEnvelope {
fn from(v: Signed<TxEip2930>) -> Self {
Self::Eip2930(v)
}
}
impl From<Signed<TxEip1559>> for OpTxEnvelope {
fn from(v: Signed<TxEip1559>) -> Self {
Self::Eip1559(v)
}
}
impl From<Signed<TxEip4844Variant>> for OpTxEnvelope {
fn from(v: Signed<TxEip4844Variant>) -> Self {
Self::Eip4844(v)
}
}
impl From<Signed<TxEip4844>> for OpTxEnvelope {
fn from(v: Signed<TxEip4844>) -> Self {
let (tx, signature, hash) = v.into_parts();
Self::Eip4844(Signed::new_unchecked(TxEip4844Variant::TxEip4844(tx), signature, hash))
}
}
impl From<Signed<TxEip4844WithSidecar>> for OpTxEnvelope {
fn from(v: Signed<TxEip4844WithSidecar>) -> Self {
let (tx, signature, hash) = v.into_parts();
Self::Eip4844(Signed::new_unchecked(
TxEip4844Variant::TxEip4844WithSidecar(tx),
signature,
hash,
))
}
}
impl From<TxDeposit> for OpTxEnvelope {
fn from(v: TxDeposit) -> Self {
Self::Deposit(v)
}
}
impl OpTxEnvelope {
#[inline]
pub const fn is_legacy(&self) -> bool {
matches!(self, Self::Legacy(_))
}
#[inline]
pub const fn is_eip2930(&self) -> bool {
matches!(self, Self::Eip2930(_))
}
#[inline]
pub const fn is_eip1559(&self) -> bool {
matches!(self, Self::Eip1559(_))
}
#[inline]
pub const fn is_deposit(&self) -> bool {
matches!(self, Self::Deposit(_))
}
pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
match self {
Self::Legacy(tx) => Some(tx),
_ => None,
}
}
pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
match self {
Self::Eip2930(tx) => Some(tx),
_ => None,
}
}
pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
match self {
Self::Eip1559(tx) => Some(tx),
_ => None,
}
}
pub const fn as_deposit(&self) -> Option<&TxDeposit> {
match self {
Self::Deposit(tx) => Some(tx),
_ => None,
}
}
pub const fn tx_type(&self) -> OpTxType {
match self {
Self::Legacy(_) => OpTxType::Legacy,
Self::Eip2930(_) => OpTxType::Eip2930,
Self::Eip1559(_) => OpTxType::Eip1559,
Self::Eip4844(_) => OpTxType::Eip4844,
Self::Deposit(_) => OpTxType::Deposit,
}
}
pub fn inner_length(&self) -> usize {
match self {
Self::Legacy(t) => t.tx().fields_len() + t.signature().rlp_vrs_len(),
Self::Eip2930(t) => {
let payload_length = t.tx().fields_len() + t.signature().rlp_vrs_len();
Header { list: true, payload_length }.length() + payload_length
}
Self::Eip1559(t) => {
let payload_length = t.tx().fields_len() + t.signature().rlp_vrs_len();
Header { list: true, payload_length }.length() + payload_length
}
Self::Eip4844(t) => match t.tx() {
TxEip4844Variant::TxEip4844(tx) => {
let payload_length = tx.fields_len() + t.signature().rlp_vrs_len();
Header { list: true, payload_length }.length() + payload_length
}
TxEip4844Variant::TxEip4844WithSidecar(tx) => {
let inner_payload_length = tx.tx().fields_len() + t.signature().rlp_vrs_len();
let inner_header = Header { list: true, payload_length: inner_payload_length };
let outer_payload_length =
inner_header.length() + inner_payload_length + tx.sidecar.fields_len();
let outer_header = Header { list: true, payload_length: outer_payload_length };
outer_header.length() + outer_payload_length
}
},
Self::Deposit(t) => {
let payload_length = t.fields_len();
Header { list: true, payload_length }.length() + payload_length
}
}
}
fn rlp_payload_length(&self) -> usize {
if let Self::Legacy(t) = self {
let payload_length = t.tx().fields_len() + t.signature().rlp_vrs_len();
return Header { list: true, payload_length }.length() + payload_length;
}
let inner_length = self.inner_length();
inner_length + 1
}
}
impl Encodable for OpTxEnvelope {
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
self.network_encode(out)
}
fn length(&self) -> usize {
let mut payload_length = self.rlp_payload_length();
if !self.is_legacy() {
payload_length += Header { list: false, payload_length }.length();
}
payload_length
}
}
impl Decodable for OpTxEnvelope {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
match Self::network_decode(buf) {
Ok(t) => Ok(t),
Err(Eip2718Error::RlpError(e)) => Err(e),
Err(Eip2718Error::UnexpectedType(_)) => {
Err(alloy_rlp::Error::Custom("unexpected tx type"))
}
_ => Err(alloy_rlp::Error::Custom("unknown error decoding tx envelope")),
}
}
}
impl Decodable2718 for OpTxEnvelope {
fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? {
OpTxType::Eip2930 => Ok(Self::Eip2930(TxEip2930::decode_signed_fields(buf)?)),
OpTxType::Eip1559 => Ok(Self::Eip1559(TxEip1559::decode_signed_fields(buf)?)),
OpTxType::Eip4844 => Ok(Self::Eip4844(TxEip4844Variant::decode_signed_fields(buf)?)),
OpTxType::Deposit => Ok(Self::Deposit(TxDeposit::decode(buf)?)),
OpTxType::Legacy => {
Err(alloy_rlp::Error::Custom("type-0 eip2718 transactions are not supported")
.into())
}
}
}
fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
Ok(Self::Legacy(TxLegacy::decode_signed_fields(buf)?))
}
}
impl Encodable2718 for OpTxEnvelope {
fn type_flag(&self) -> Option<u8> {
match self {
Self::Legacy(_) => None,
Self::Eip2930(_) => Some(OpTxType::Eip2930 as u8),
Self::Eip1559(_) => Some(OpTxType::Eip1559 as u8),
Self::Eip4844(_) => Some(OpTxType::Eip4844 as u8),
Self::Deposit(_) => Some(OpTxType::Deposit as u8),
}
}
fn encode_2718_len(&self) -> usize {
self.inner_length() + !self.is_legacy() as usize
}
fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
match self {
Self::Legacy(tx) => tx.tx().encode_with_signature_fields(tx.signature(), out),
Self::Eip2930(tx) => {
tx.tx().encode_with_signature(tx.signature(), out, false);
}
Self::Eip1559(tx) => {
tx.tx().encode_with_signature(tx.signature(), out, false);
}
Self::Eip4844(tx) => {
tx.tx().encode_with_signature(tx.signature(), out, false);
}
Self::Deposit(tx) => {
tx.encode_inner(out, false);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use alloy_primitives::{hex, Address, Bytes, TxKind, B256, U256};
#[test]
fn test_encode_decode_deposit() {
let tx = TxDeposit {
source_hash: B256::left_padding_from(&[0xde, 0xad]),
from: Address::left_padding_from(&[0xbe, 0xef]),
mint: Some(1),
gas_limit: 2,
to: TxKind::Call(Address::left_padding_from(&[3])),
value: U256::from(4_u64),
input: Bytes::from(vec![5]),
is_system_transaction: false,
};
let tx_envelope = OpTxEnvelope::Deposit(tx);
let encoded = tx_envelope.encoded_2718();
let decoded = OpTxEnvelope::decode_2718(&mut encoded.as_ref()).unwrap();
assert_eq!(encoded.len(), tx_envelope.encode_2718_len());
assert_eq!(decoded, tx_envelope);
}
#[test]
#[cfg(feature = "serde")]
fn test_serde_roundtrip_deposit() {
let tx = TxDeposit {
gas_limit: u64::MAX,
to: TxKind::Call(Address::random()),
value: U256::MAX,
input: Bytes::new(),
source_hash: U256::MAX.into(),
from: Address::random(),
mint: Some(u128::MAX),
is_system_transaction: false,
};
let tx_envelope = OpTxEnvelope::Deposit(tx);
let serialized = serde_json::to_string(&tx_envelope).unwrap();
let deserialized: OpTxEnvelope = serde_json::from_str(&serialized).unwrap();
assert_eq!(tx_envelope, deserialized);
}
#[test]
fn eip2718_deposit_decode() {
let b = hex!("7ef8f8a0417d134467f4737fcdf2475f0ecdd2a0ed6d87ecffc888ba9f60ee7e3b8ac26a94deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e20000008dd00101c1200000000000000040000000066c352bb000000000139c4f500000000000000000000000000000000000000000000000000000000c0cff1460000000000000000000000000000000000000000000000000000000000000001d4c88f4065ac9671e8b1329b90773e89b5ddff9cf8675b2b5e9c1b28320609930000000000000000000000005050f69a9786f081509234f1a7f4684b5e5b76c9");
let tx = OpTxEnvelope::decode_2718(&mut b[..].as_ref()).unwrap();
let deposit = tx.as_deposit().unwrap();
assert!(deposit.mint.is_none());
}
}