alloy_zksync/network/unsigned_tx/eip712/
mod.rs

1//! EIP-712 transaction type, specific to the ZKsync network.
2
3use alloy::consensus::{
4    SignableTransaction, Signed, Transaction, Typed2718, transaction::RlpEcdsaEncodableTx,
5};
6use alloy::primitives::Signature;
7use alloy::primitives::{Address, Bytes, ChainId, TxKind, U256, keccak256};
8use alloy::rlp::{BufMut, Decodable, Encodable, Header};
9use alloy::rpc::types::TransactionInput;
10use serde::{Deserialize, Serialize};
11
12use crate::network::tx_type::TxType;
13
14pub use self::meta::{Eip712Meta, PaymasterParams};
15pub use self::utils::{BytecodeHashError, hash_bytecode};
16
17mod meta;
18mod signing;
19mod utils;
20
21/// A ZKsync-native transaction type with additional fields.
22/// Besides additional fields, represents an EIP-1559 transaction.
23///
24/// Note: Unlike EIP-1559, does not have `access_list` field.
25#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
26#[serde(rename_all = "camelCase")]
27#[doc(
28    alias = "Eip712Transaction",
29    alias = "TransactionEip712",
30    alias = "Eip712Tx"
31)]
32pub struct TxEip712 {
33    /// EIP-155: Simple replay attack protection
34    #[serde(with = "alloy::serde::quantity")]
35    pub chain_id: ChainId,
36    /// A scalar value equal to the number of transactions sent by the sender; formally Tn.
37    // TODO: Explain composite nonce?
38    pub nonce: U256,
39    /// A scalar value equal to the maximum
40    /// amount of gas that should be used in executing
41    /// this transaction. This is paid up-front, before any
42    /// computation is done and may not be increased
43    /// later; formally Tg.
44    #[serde(with = "alloy::serde::quantity")]
45    pub gas: u64,
46    /// A scalar value equal to the maximum
47    /// amount of gas that should be used in executing
48    /// this transaction. This is paid up-front, before any
49    /// computation is done and may not be increased
50    /// later; formally Tg.
51    ///
52    /// As ethereum circulation is around 120mil eth as of 2022 that is around
53    /// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
54    /// 340282366920938463463374607431768211455
55    ///
56    /// This is also known as `GasFeeCap`
57    #[serde(with = "alloy::serde::quantity")]
58    pub max_fee_per_gas: u128,
59    /// Max Priority fee that transaction is paying
60    ///
61    /// As ethereum circulation is around 120mil eth as of 2022 that is around
62    /// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
63    /// 340282366920938463463374607431768211455
64    ///
65    /// This is also known as `GasTipCap`
66    #[serde(with = "alloy::serde::quantity")]
67    pub max_priority_fee_per_gas: u128, // TODO: Should be option
68    /// Address of the receiver of the message.
69    /// Unlike with other transactions, this field must be present.
70    /// In case of the contract deployment, the address should be set to the deployer system contract,
71    /// and the payload should contain ABI-encoded salt, contract bytecode hash, and constructor arguments.
72    pub to: Address,
73    /// Address of the sender of the message.
74    pub from: Address,
75    /// A scalar value equal to the number of Wei to
76    /// be transferred to the message call’s recipient or,
77    /// in the case of contract creation, as an endowment
78    /// to the newly created account; formally Tv.
79    pub value: U256,
80    /// Input has two uses depending if transaction is Create or Call (if `to` field is None or
81    /// Some). pub init: An unlimited size byte array specifying the
82    /// EVM-code for the account initialisation procedure CREATE,
83    /// data: An unlimited size byte array specifying the
84    /// input data of the message call, formally Td.
85    pub input: Bytes,
86    /// ZKsync-specific fields.
87    #[serde(flatten)]
88    pub eip712_meta: Option<Eip712Meta>,
89}
90
91impl TxEip712 {
92    /// Returns the effective gas price for the given `base_fee`.
93    pub const fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
94        match base_fee {
95            None => self.max_fee_per_gas,
96            Some(base_fee) => {
97                // if the tip is greater than the max priority fee per gas, set it to the max
98                // priority fee per gas + base fee
99                let tip = self.max_fee_per_gas.saturating_sub(base_fee as u128);
100                if tip > self.max_priority_fee_per_gas {
101                    self.max_priority_fee_per_gas + base_fee as u128
102                } else {
103                    // otherwise return the max fee per gas
104                    self.max_fee_per_gas
105                }
106            }
107        }
108    }
109
110    /// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating
111    /// hash that for eip2718 does not require a rlp header.
112    pub(crate) fn encode_with_signature(
113        &self,
114        signature: &Signature,
115        out: &mut dyn BufMut,
116        // with_header: bool,
117    ) {
118        // if with_header {
119        //     let payload_length = self.payload_length_unoptimized(signature);
120        //     Header {
121        //         list: false,
122        //         payload_length: 1
123        //             + Header {
124        //                 list: true,
125        //                 payload_length,
126        //             }
127        //             .length()
128        //             + payload_length,
129        //     }
130        //     .encode(out);
131        // }
132        out.put_u8(self.tx_type() as u8);
133        self.encode_with_signature_fields(signature, out);
134    }
135
136    /// Decodes the transaction from RLP bytes, including the signature.
137    ///
138    /// This __does not__ expect the bytes to start with a transaction type byte or string
139    /// header.
140    ///
141    /// This __does__ expect the bytes to start with a list header and include a signature.
142    #[doc(hidden)]
143    pub fn decode_signed_fields(buf: &mut &[u8]) -> alloy::rlp::Result<Signed<Self>> {
144        let header = Header::decode(buf)?;
145        if !header.list {
146            return Err(alloy::rlp::Error::UnexpectedString);
147        }
148
149        // record original length so we can check encoding
150        let original_len = buf.len();
151
152        let nonce = Decodable::decode(buf)?;
153        let max_priority_fee_per_gas = Decodable::decode(buf)?;
154        let max_fee_per_gas = Decodable::decode(buf)?;
155        let gas = Decodable::decode(buf)?;
156        let to = Decodable::decode(buf)?;
157        let value = Decodable::decode(buf)?;
158        let input = Decodable::decode(buf)?;
159        let signature = Signature::decode_rlp_vrs(buf, bool::decode)?;
160        let chain_id = Decodable::decode(buf)?;
161        let from = Decodable::decode(buf)?;
162        let eip712_meta = Decodable::decode(buf)?;
163
164        let tx = Self {
165            chain_id,
166            nonce,
167            gas,
168            max_fee_per_gas,
169            max_priority_fee_per_gas,
170            to,
171            from,
172            value,
173            input,
174            eip712_meta: Some(eip712_meta),
175        };
176
177        // Context: `SignableTransaction` is commented out for now.
178        let signed = tx.into_signed(signature);
179
180        if buf.len() + header.payload_length != original_len {
181            return Err(alloy::rlp::Error::ListLengthMismatch {
182                expected: header.payload_length,
183                got: original_len - buf.len(),
184            });
185        }
186
187        Ok(signed)
188    }
189
190    pub(crate) fn fields_len(&self) -> usize {
191        self.nonce.length()
192            + self.max_priority_fee_per_gas.length()
193            + self.max_fee_per_gas.length()
194            + self.gas.length()
195            + self.to.length()
196            + self.value.length()
197            + self.input.length()
198            + self.chain_id.length()
199            + self.from.length()
200            + self
201                .eip712_meta
202                .as_ref()
203                .map(|m| m.length())
204                .unwrap_or_default()
205    }
206
207    /// Encodes the transaction from RLP bytes, including the signature. This __does not__ encode a
208    /// tx type byte or string header.
209    ///
210    /// This __does__ encode a list header and include a signature.
211    pub(crate) fn encode_with_signature_fields(&self, signature: &Signature, out: &mut dyn BufMut) {
212        let payload_length = self.fields_len() + signature.rlp_rs_len() + signature.v().length();
213        let header = Header {
214            list: true,
215            payload_length,
216        };
217        header.encode(out);
218
219        self.nonce.encode(out);
220        self.max_priority_fee_per_gas.encode(out);
221        self.max_fee_per_gas.encode(out);
222        self.gas.encode(out);
223        self.to.encode(out);
224        self.value.encode(out);
225        self.input.0.encode(out);
226        signature.write_rlp_vrs(out, signature.v());
227        self.chain_id.encode(out);
228        self.from.encode(out);
229        if let Some(eip712_meta) = &self.eip712_meta {
230            eip712_meta.encode(out);
231        }
232    }
233
234    /// Gets the length of the encoded header.
235    pub(crate) fn encoded_length(&self, signature: &Signature) -> usize {
236        let payload_length = self.fields_len() + signature.rlp_rs_len() + signature.v().length();
237        alloy::rlp::length_of_length(payload_length) + payload_length
238    }
239
240    /// Get transaction type
241    #[doc(alias = "transaction_type")]
242    pub(crate) const fn tx_type(&self) -> TxType {
243        TxType::Eip712
244    }
245
246    // /// Calculates a heuristic for the in-memory size of the [TxEip712] transaction.
247    // #[inline]
248    // pub fn size(&self) -> usize {
249    //     mem::size_of::<ChainId>() + // chain_id
250    //     mem::size_of::<u64>() + // nonce
251    //     mem::size_of::<u64>() + // gas_limit
252    //     mem::size_of::<u128>() + // max_fee_per_gas
253    //     mem::size_of::<u128>() + // max_priority_fee_per_gas
254    //     self.to.size() + // to
255    //     mem::size_of::<U256>() + // value
256    //     self.input.len() // input
257    // }
258}
259
260impl Typed2718 for TxEip712 {
261    fn ty(&self) -> u8 {
262        self.tx_type() as u8
263    }
264}
265
266impl Transaction for TxEip712 {
267    fn chain_id(&self) -> Option<ChainId> {
268        Some(self.chain_id)
269    }
270
271    fn nonce(&self) -> u64 {
272        // TODO: Better interface for nonce decomposition?
273        (self.nonce % U256::from(u64::MAX)).try_into().unwrap()
274    }
275
276    fn gas_limit(&self) -> u64 {
277        self.gas
278    }
279
280    fn gas_price(&self) -> Option<u128> {
281        None
282    }
283
284    fn to(&self) -> Option<Address> {
285        self.to.into()
286    }
287
288    fn is_create(&self) -> bool {
289        matches!(self.kind(), TxKind::Create)
290    }
291
292    fn value(&self) -> U256 {
293        self.value
294    }
295
296    fn input(&self) -> &Bytes {
297        &self.input
298    }
299
300    fn max_fee_per_gas(&self) -> u128 {
301        self.max_fee_per_gas
302    }
303
304    fn max_priority_fee_per_gas(&self) -> Option<u128> {
305        Some(self.max_priority_fee_per_gas)
306    }
307
308    fn max_fee_per_blob_gas(&self) -> Option<u128> {
309        None
310    }
311
312    fn priority_fee_or_price(&self) -> u128 {
313        todo!()
314    }
315
316    fn access_list(&self) -> Option<&alloy::rpc::types::AccessList> {
317        None
318    }
319
320    fn blob_versioned_hashes(&self) -> Option<&[alloy::primitives::B256]> {
321        None
322    }
323
324    fn authorization_list(&self) -> Option<&[alloy::eips::eip7702::SignedAuthorization]> {
325        None
326    }
327
328    fn kind(&self) -> TxKind {
329        self.to.into()
330    }
331
332    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
333        self.effective_gas_price(base_fee)
334    }
335
336    fn is_dynamic_fee(&self) -> bool {
337        false
338    }
339}
340
341// Context: Encodable/Decodable assume that there is no signature in the transaction
342impl SignableTransaction<Signature> for TxEip712 {
343    fn set_chain_id(&mut self, chain_id: ChainId) {
344        self.chain_id = chain_id;
345    }
346
347    fn encode_for_signing(&self, out: &mut dyn alloy::rlp::BufMut) {
348        // Reimplementation of `SolStruct::eip712_signing_hash`
349        out.put_u8(0x19);
350        out.put_u8(0x01);
351        out.put_slice(self.domain_hash().as_slice());
352        out.put_slice(self.eip712_hash_struct().as_slice());
353    }
354
355    fn payload_len_for_signature(&self) -> usize {
356        // 2 bytes for the header, 32 bytes for the domain hash, 32 bytes for the transaction hash
357        2 + 32 + 32
358    }
359
360    fn into_signed(self, signature: Signature) -> Signed<Self> {
361        let mut buf = [0u8; 64];
362        buf[..32].copy_from_slice(self.signature_hash().as_slice());
363        buf[32..].copy_from_slice(keccak256(signature.as_bytes()).as_slice());
364        let hash = keccak256(buf);
365
366        Signed::new_unchecked(self, signature, hash)
367    }
368}
369
370impl RlpEcdsaEncodableTx for TxEip712 {
371    #[doc = " Calculate the encoded length of the transaction\'s fields, without a RLP"]
372    #[doc = " header."]
373    fn rlp_encoded_fields_length(&self) -> usize {
374        self.rlp_encoded_length()
375    }
376
377    #[doc = " Encodes only the transaction\'s fields into the desired buffer, without"]
378    #[doc = " a RLP header."]
379    fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
380        self.rlp_encode(out);
381    }
382}
383
384// // Context: encoding is implemented for the purpose of EIP-712 signing.
385// impl Encodable for TxEip712 {
386//     fn encode(&self, out: &mut dyn BufMut) {
387//         Header {
388//             list: true,
389//             payload_length: self.fields_len(),
390//         }
391//         .encode(out);
392//         self.encode_fields(out);
393//     }
394
395//     fn length(&self) -> usize {
396//         let payload_length = self.fields_len();
397//         Header {
398//             list: true,
399//             payload_length,
400//         }
401//         .length()
402//             + payload_length
403//     }
404// }
405
406// impl Decodable for TxEip712 {
407//     fn decode(data: &mut &[u8]) -> alloy::rlp::Result<Self> {
408//         let header = Header::decode(data)?;
409//         let remaining_len = data.len();
410
411//         if header.payload_length > remaining_len {
412//             return Err(alloy::rlp::Error::InputTooShort);
413//         }
414
415//         println!("decode fields");
416//         Self::decode_fields(data)
417//     }
418// }
419
420impl From<TxEip712> for alloy::rpc::types::transaction::TransactionRequest {
421    fn from(tx: TxEip712) -> Self {
422        Self {
423            transaction_type: Some(tx.tx_type() as u8),
424            chain_id: Some(tx.chain_id),
425            nonce: Some((tx.nonce % U256::from(u64::MAX)).try_into().unwrap()), // TODO: Is decomposed nonce fine here?
426            gas: Some(tx.gas),
427            max_fee_per_gas: Some(tx.max_fee_per_gas),
428            max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
429            to: Some(tx.to.into()),
430            from: Some(tx.from),
431            value: Some(tx.value),
432            input: TransactionInput::new(tx.input),
433            access_list: None,
434            blob_versioned_hashes: None,
435            max_fee_per_blob_gas: None,
436            gas_price: None,
437            sidecar: None,
438            authorization_list: None,
439        }
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use std::str::FromStr;
446
447    use crate::network::unsigned_tx::eip712::{Eip712Meta, PaymasterParams};
448
449    use super::TxEip712;
450    use alloy::consensus::SignableTransaction;
451    use alloy::hex::FromHex;
452    use alloy::primitives::{Address, B256, Bytes, FixedBytes, Signature, U256, address, hex};
453
454    #[test]
455    fn decode_eip712_tx() {
456        // Does not have type byte.
457        let encoded = hex::decode("f8b701800b0c940754b07d1ea3071c3ec9bd86b2aa6f1a59a514980a8301020380a0635f9ee3a1523de15fc8b72a0eea12f5247c6b6e2369ed158274587af6496599a030f7c66d1ed24fca92527e6974b85b07ec30fdd5c2d41eae46966224add965f982010e9409a6aa96b9a17d7f7ba3e3b19811c082aba9f1e304e1a0020202020202020202020202020202020202020202020202020202020202020283010203d694000000000000000000000000000000000000000080").unwrap();
458        let signed_tx = TxEip712::decode_signed_fields(&mut &encoded[..]).unwrap();
459        let tx = signed_tx.tx();
460        assert_eq!(tx.chain_id, 270);
461        assert_eq!(tx.nonce, U256::from(1));
462        assert_eq!(tx.gas, 12);
463        assert_eq!(tx.max_fee_per_gas, 11);
464        assert_eq!(tx.max_priority_fee_per_gas, 0);
465        assert_eq!(tx.to, address!("0754b07d1ea3071c3ec9bd86b2aa6f1a59a51498"));
466        assert_eq!(
467            tx.from,
468            address!("09a6aa96b9a17d7f7ba3e3b19811c082aba9f1e3")
469        );
470        assert_eq!(tx.value, U256::from(10));
471        assert_eq!(tx.input, Bytes::from_hex("0x010203").unwrap());
472        assert_eq!(
473            tx.eip712_meta,
474            Some(Eip712Meta {
475                gas_per_pubdata: U256::from(4),
476                factory_deps: vec![
477                    Bytes::from_hex(
478                        "0x0202020202020202020202020202020202020202020202020202020202020202"
479                    )
480                    .unwrap()
481                ],
482                custom_signature: Some(Bytes::from_hex("0x010203").unwrap()),
483                paymaster_params: Some(PaymasterParams {
484                    paymaster: address!("0000000000000000000000000000000000000000"),
485                    paymaster_input: Bytes::from_hex("0x").unwrap()
486                }),
487            })
488        );
489    }
490
491    #[test]
492    fn decode_eip712_tx_with_paymaster() {
493        // This is request to AA account with paymaster set and no signature.
494        // Does not have type byte.
495        let encoded = hex::decode("f9036580843b9aca00843b9aca0083989680949c1a3d7c98dbf89c7f5d167f2219c29c2fe775a780b903045abef77a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000051ef809ffd89cf8056d4c17f0aff1b6f8257eb6000000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000001e10100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000078cad996530109838eb016619f5931a03250489a000000000000000000000000aaf5f437fb0524492886fba64d703df15bf619ae000000000000000000000000000000000000000000000000000000000000010f00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064a41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000568656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080808082010f94b2a6e81272904caf680008078feb36336d9376b482c350c080d89499e12239cbf8112fbb3f7fd473d0558031abcbb5821234").unwrap();
496        let signed_tx = TxEip712::decode_signed_fields(&mut &encoded[..]).unwrap();
497        let tx = signed_tx.tx();
498        assert_eq!(tx.chain_id, 271);
499        assert_eq!(tx.nonce, U256::from(0));
500        assert_eq!(tx.gas, 10000000);
501        assert_eq!(tx.max_fee_per_gas, 1000000000);
502        assert_eq!(tx.max_priority_fee_per_gas, 1000000000);
503        assert_eq!(tx.to, address!("9c1a3d7c98dbf89c7f5d167f2219c29c2fe775a7"));
504        assert_eq!(
505            tx.from,
506            address!("b2a6e81272904caf680008078feb36336d9376b4")
507        );
508        assert_eq!(tx.value, U256::from(0));
509        assert_eq!(tx.input, Bytes::from_hex("5abef77a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000051ef809ffd89cf8056d4c17f0aff1b6f8257eb6000000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000001e10100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000078cad996530109838eb016619f5931a03250489a000000000000000000000000aaf5f437fb0524492886fba64d703df15bf619ae000000000000000000000000000000000000000000000000000000000000010f00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064a41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000568656c6c6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap());
510
511        assert_eq!(
512            tx.eip712_meta,
513            Some(Eip712Meta {
514                gas_per_pubdata: U256::from(50000),
515                factory_deps: vec![],
516                custom_signature: Some(Bytes::from_hex("0x").unwrap()),
517                paymaster_params: Some(PaymasterParams {
518                    paymaster: address!("99E12239CBf8112fBB3f7Fd473d0558031abcbb5"),
519                    paymaster_input: Bytes::from_hex("0x1234").unwrap()
520                }),
521            })
522        );
523    }
524
525    #[test]
526    fn test_eip712_tx() {
527        let eip712_meta = Eip712Meta {
528            gas_per_pubdata: U256::from(4),
529            factory_deps: vec![vec![2; 32].into()],
530            custom_signature: Some(vec![].into()),
531            paymaster_params: None,
532        };
533        let tx = TxEip712 {
534            chain_id: 270,
535            from: Address::from_str("0xe30f4fb40666753a7596d315f2f1f1d140d1508b").unwrap(),
536            to: Address::from_str("0x82112600a140ceaa9d7da373bb65453f7d99af4b").unwrap(),
537            nonce: U256::from(1),
538            value: U256::from(10),
539            gas: 12,
540            max_fee_per_gas: 11,
541            max_priority_fee_per_gas: 0,
542            input: vec![0x01, 0x02, 0x03].into(),
543            eip712_meta: Some(eip712_meta),
544        };
545        let expected_signature_hash = FixedBytes::<32>::from_str(
546            "0xfc76820a67d9b1b351f2ac661e6d2bcca1c67508ae4930e036f540fa135875fe",
547        )
548        .unwrap();
549        assert_eq!(tx.signature_hash(), expected_signature_hash);
550
551        let signature = Signature::from_str("0x3faf83b5451ad3001f96f577b0bb5dfcaa7769ab11908f281dc6b15c45a3986f0325197832aac9a7ab2f5a83873834d457e0d22c1e72377d45364c6968f8ac3b1c").unwrap();
552        let recovered_signer = signature
553            .recover_address_from_prehash(&tx.signature_hash())
554            .unwrap();
555        assert_eq!(recovered_signer, tx.from);
556
557        let mut buf = Vec::new();
558        tx.encode_with_signature_fields(&signature, &mut buf);
559        let decoded = TxEip712::decode_signed_fields(&mut &buf[..]).unwrap();
560        assert_eq!(decoded, tx.into_signed(signature));
561
562        let expected_hash =
563            B256::from_str("0xb85668399db249d62d06bbc59eace82e01364602fb7159e161ca810ff6ddbbf4")
564                .unwrap();
565        assert_eq!(*decoded.hash(), expected_hash);
566    }
567
568    #[test]
569    fn test_eip712_tx_encode_decode_with_paymaster() {
570        let eip712_meta = Eip712Meta {
571            gas_per_pubdata: U256::from(4),
572            factory_deps: vec![vec![2; 32].into()],
573            custom_signature: Some(vec![].into()),
574            paymaster_params: Some(PaymasterParams {
575                paymaster: address!("99E12239CBf8112fBB3f7Fd473d0558031abcbb5"),
576                paymaster_input: Bytes::from_hex("0x112233").unwrap(),
577            }),
578        };
579        let tx = TxEip712 {
580            chain_id: 270,
581            from: Address::from_str("0xe30f4fb40666753a7596d315f2f1f1d140d1508b").unwrap(),
582            to: Address::from_str("0x82112600a140ceaa9d7da373bb65453f7d99af4b").unwrap(),
583            nonce: U256::from(1),
584            value: U256::from(10),
585            gas: 12,
586            max_fee_per_gas: 11,
587            max_priority_fee_per_gas: 0,
588            input: vec![0x01, 0x02, 0x03].into(),
589            eip712_meta: Some(eip712_meta),
590        };
591
592        // This is a random signature, but that's ok.
593        let signature = Signature::from_str("0x3faf83b5451ad3001f96f577b0bb5dfcaa7769ab11908f281dc6b15c45a3986f0325197832aac9a7ab2f5a83873834d457e0d22c1e72377d45364c6968f8ac3b1c").unwrap();
594
595        let mut buf = Vec::new();
596        tx.encode_with_signature_fields(&signature, &mut buf);
597        let decoded = TxEip712::decode_signed_fields(&mut &buf[..]).unwrap();
598        // Make sure that paymaster data was loaded correctly.
599        assert_eq!(decoded, tx.into_signed(signature));
600    }
601
602    // #[test]
603    // fn recover_signer_eip712() {
604    //     let signer: Address = address!("dd6b8b3dc6b7ad97db52f08a275ff4483e024cea");
605    //     let hash: B256 = b256!("0ec0b6a2df4d87424e5f6ad2a654e27aaeb7dac20ae9e8385cc09087ad532ee0");
606
607    //     let tx =  TxEip712 {
608    //         chain_id: 1,
609    //         nonce: 0x42,
610    //         gas_limit: 44386,
611    //         to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into(),
612    //         value: U256::from(0_u64),
613    //         input:  hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(),
614    //         max_fee_per_gas: 0x4a817c800,
615    //         max_priority_fee_per_gas: 0x3b9aca00,
616    //         access_list: AccessList::default(),
617    //         eip712_meta: Eip712Meta::default(),
618    //     };
619
620    //     let sig = Signature::from_scalars_and_parity(
621    //         b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
622    //         b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
623    //         false,
624    //     )
625    //     .unwrap();
626
627    //     assert_eq!(
628    //         tx.signature_hash(),
629    //         hex!("0d5688ac3897124635b6cf1bc0e29d6dfebceebdc10a54d74f2ef8b56535b682")
630    //     );
631
632    //     let signed_tx = tx.into_signed(sig);
633    //     assert_eq!(*signed_tx.hash(), hash, "Expected same hash");
634    //     // assert_eq!(
635    //     //     signed_tx.recover_signer().unwrap(),
636    //     //     signer,
637    //     //     "Recovering signer should pass."
638    //     // );
639    // }
640
641    // #[test]
642    // fn encode_decode_eip712() {
643    //     let hash: B256 = b256!("0ec0b6a2df4d87424e5f6ad2a654e27aaeb7dac20ae9e8385cc09087ad532ee0");
644
645    //     let tx =  TxEip712 {
646    //         chain_id: 1,
647    //         nonce: 0x42,
648    //         gas_limit: 44386,
649    //         to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into(),
650    //         value: U256::from(0_u64),
651    //         input:  hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(),
652    //         max_fee_per_gas: 0x4a817c800,
653    //         max_priority_fee_per_gas: 0x3b9aca00,
654    //         access_list: AccessList::default(),
655    //         eip712_meta: Eip712Meta::default(),
656    //     };
657
658    //     let sig = Signature::from_scalars_and_parity(
659    //         b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
660    //         b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
661    //         false,
662    //     )
663    //     .unwrap();
664
665    //     let mut buf = vec![];
666    //     tx.encode_with_signature_fields(&sig, &mut buf);
667    //     let decoded = TxEip712::decode_signed_fields(&mut &buf[..]).unwrap();
668    //     assert_eq!(decoded, tx.into_signed(sig));
669    //     assert_eq!(*decoded.hash(), hash);
670    // }
671}