anvil_core/eth/transaction/
optimism.rs

1use alloy_primitives::{Address, Bytes, TxKind, B256, U256};
2use alloy_rlp::{Decodable, Encodable, Error as DecodeError, Header as RlpHeader};
3use op_alloy_consensus::TxDeposit;
4use serde::{Deserialize, Serialize};
5
6pub const DEPOSIT_TX_TYPE_ID: u8 = 0x7E;
7
8impl From<DepositTransaction> for TxDeposit {
9    fn from(tx: DepositTransaction) -> Self {
10        Self {
11            from: tx.from,
12            source_hash: tx.source_hash,
13            to: tx.kind,
14            mint: Some(tx.mint.to::<u128>()),
15            value: tx.value,
16            gas_limit: tx.gas_limit,
17            is_system_transaction: tx.is_system_tx,
18            input: tx.input,
19        }
20    }
21}
22
23/// An op-stack deposit transaction.
24/// See <https://github.com/ethereum-optimism/optimistic-specs/blob/main/specs/deposits.md#the-deposited-transaction-type>
25#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub struct DepositTransaction {
27    pub nonce: u64,
28    pub source_hash: B256,
29    pub from: Address,
30    pub kind: TxKind,
31    pub mint: U256,
32    pub value: U256,
33    pub gas_limit: u64,
34    pub is_system_tx: bool,
35    pub input: Bytes,
36}
37
38impl DepositTransaction {
39    pub fn nonce(&self) -> &u64 {
40        &self.nonce
41    }
42
43    pub fn hash(&self) -> B256 {
44        let mut encoded = Vec::new();
45        self.encode_2718(&mut encoded);
46        B256::from_slice(alloy_primitives::keccak256(encoded).as_slice())
47    }
48
49    // /// Recovers the Ethereum address which was used to sign the transaction.
50    pub fn recover(&self) -> Result<Address, alloy_primitives::SignatureError> {
51        Ok(self.from)
52    }
53
54    pub fn chain_id(&self) -> Option<u64> {
55        None
56    }
57
58    pub fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
59        out.put_u8(DEPOSIT_TX_TYPE_ID);
60        self.encode(out);
61    }
62
63    /// Encodes only the transaction's fields into the desired buffer, without a RLP header.
64    pub(crate) fn encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
65        self.source_hash.encode(out);
66        self.from.encode(out);
67        self.kind.encode(out);
68        self.mint.encode(out);
69        self.value.encode(out);
70        self.gas_limit.encode(out);
71        self.is_system_tx.encode(out);
72        self.input.encode(out);
73    }
74
75    /// Calculates the length of the RLP-encoded transaction's fields.
76    pub(crate) fn fields_len(&self) -> usize {
77        let mut len = 0;
78        len += self.source_hash.length();
79        len += self.from.length();
80        len += self.kind.length();
81        len += self.mint.length();
82        len += self.value.length();
83        len += self.gas_limit.length();
84        len += self.is_system_tx.length();
85        len += self.input.length();
86        len
87    }
88
89    pub fn decode_2718(buf: &mut &[u8]) -> Result<Self, DecodeError> {
90        use bytes::Buf;
91
92        let tx_type = *buf.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?;
93
94        if tx_type != DEPOSIT_TX_TYPE_ID {
95            return Err(alloy_rlp::Error::Custom("invalid tx type: expected deposit tx type"));
96        }
97
98        // Skip the tx type byte
99        buf.advance(1);
100        Self::decode(buf)
101    }
102
103    /// Decodes the inner fields from RLP bytes
104    ///
105    /// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following
106    /// RLP fields in the following order:
107    ///
108    /// - `source_hash`
109    /// - `from`
110    /// - `kind`
111    /// - `mint`
112    /// - `value`
113    /// - `gas_limit`
114    /// - `is_system_tx`
115    /// - `input`
116    pub fn decode_inner(buf: &mut &[u8]) -> Result<Self, DecodeError> {
117        Ok(Self {
118            nonce: 0,
119            source_hash: Decodable::decode(buf)?,
120            from: Decodable::decode(buf)?,
121            kind: Decodable::decode(buf)?,
122            mint: Decodable::decode(buf)?,
123            value: Decodable::decode(buf)?,
124            gas_limit: Decodable::decode(buf)?,
125            is_system_tx: Decodable::decode(buf)?,
126            input: Decodable::decode(buf)?,
127        })
128    }
129}
130
131impl Encodable for DepositTransaction {
132    fn encode(&self, out: &mut dyn bytes::BufMut) {
133        RlpHeader { list: true, payload_length: self.fields_len() }.encode(out);
134        self.encode_fields(out);
135    }
136}
137
138impl Decodable for DepositTransaction {
139    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
140        let header = RlpHeader::decode(buf)?;
141        let remaining_len = buf.len();
142        if header.payload_length > remaining_len {
143            return Err(alloy_rlp::Error::InputTooShort);
144        }
145
146        Self::decode_inner(buf)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_encode_decode() {
156        let tx = DepositTransaction {
157            nonce: 0,
158            source_hash: B256::default(),
159            from: Address::default(),
160            kind: TxKind::Call(Address::default()),
161            mint: U256::from(100),
162            value: U256::from(100),
163            gas_limit: 50000,
164            is_system_tx: false,
165            input: Bytes::default(),
166        };
167
168        let encoded_tx: Vec<u8> = alloy_rlp::encode(&tx);
169
170        let decoded_tx = DepositTransaction::decode(&mut encoded_tx.as_slice()).unwrap();
171
172        assert_eq!(tx, decoded_tx);
173    }
174    #[test]
175    fn test_encode_decode_2718() {
176        let tx = DepositTransaction {
177            nonce: 0,
178            source_hash: B256::default(),
179            from: Address::default(),
180            kind: TxKind::Call(Address::default()),
181            mint: U256::from(100),
182            value: U256::from(100),
183            gas_limit: 50000,
184            is_system_tx: false,
185            input: Bytes::default(),
186        };
187
188        let mut encoded_tx: Vec<u8> = Vec::new();
189        tx.encode_2718(&mut encoded_tx);
190
191        let decoded_tx = DepositTransaction::decode_2718(&mut encoded_tx.as_slice()).unwrap();
192
193        assert_eq!(tx, decoded_tx);
194    }
195}