alloy_zksync/network/unsigned_tx/eip712/
meta.rs

1use alloy::primitives::{Address, Bytes, FixedBytes, U256};
2use alloy::rlp::{Decodable, Encodable, Header};
3use serde::{Deserialize, Serialize, ser::SerializeSeq};
4
5use super::utils::{BytecodeHashError, hash_bytecode};
6
7// Serialize `Bytes` as `Vec<u8>` as they are encoded as hex string for human-friendly serializers
8fn serialize_bytes<S: serde::Serializer>(
9    bytes: &Vec<Bytes>,
10    serializer: S,
11) -> Result<S::Ok, S::Error> {
12    let mut seq = serializer.serialize_seq(Some(bytes.len()))?;
13    for e in bytes {
14        seq.serialize_element(&e.0)?;
15    }
16    seq.end()
17}
18
19// Seralize 'Bytes' to encode them into RLP friendly format.
20fn serialize_bytes_custom<S: serde::Serializer>(
21    bytes: &Bytes,
22    serializer: S,
23) -> Result<S::Ok, S::Error> {
24    serializer.serialize_bytes(&bytes.0)
25}
26
27fn serialize_bytes_opt<S: serde::Serializer>(
28    value: &Option<Bytes>,
29    serializer: S,
30) -> Result<S::Ok, S::Error> {
31    match value {
32        Some(bytes) => serializer.serialize_bytes(&bytes.0),
33        None => serializer.serialize_none(),
34    }
35}
36// TODO: The structure should be correct by construction, e.g. we should not allow
37// creating or deserializing meta that has invalid factory deps.
38// TODO: Serde here is used for `TransactionRequest` needs, this has to be reworked once
39// `TransactionRequest` uses custom `Eip712Meta` structure.
40
41/// Represents the EIP-712 metadata for ZKsync transactions.
42#[derive(Default, Serialize, Deserialize, Clone, PartialEq, Debug, Eq, Hash)]
43#[serde(rename_all = "camelCase")]
44pub struct Eip712Meta {
45    /// Gas per pubdata for the transaction.
46    pub gas_per_pubdata: U256,
47    /// Factory dependencies for the transaction.
48    /// Used during the contract deployment, should contain the bytecode of the contract itself,
49    /// as well as bytecodes for any contracts that can be deployed by the contract (e.g. via
50    /// CREATE).
51    #[serde(default)]
52    #[serde(serialize_with = "serialize_bytes")]
53    pub factory_deps: Vec<Bytes>,
54    /// Custom signature for the transaction.
55    ///
56    /// Should only be set in case of using a custom account implementation.
57    #[serde(serialize_with = "serialize_bytes_opt")]
58    pub custom_signature: Option<Bytes>,
59    /// Paymaster parameters for the transaction.
60    pub paymaster_params: Option<PaymasterParams>,
61}
62
63impl Eip712Meta {
64    /// Computes the hashes of the factory dependencies.
65    ///
66    /// Returns an error if any of the dependencies cannot be hashed.
67    pub fn factory_deps_hashes(&self) -> Result<Vec<FixedBytes<32>>, BytecodeHashError> {
68        let mut hashes = Vec::with_capacity(self.factory_deps.len() * 32);
69        for dep in &self.factory_deps {
70            let hash = hash_bytecode(dep)?;
71            hashes.push(hash.into());
72        }
73        Ok(hashes)
74    }
75}
76
77impl Decodable for Eip712Meta {
78    fn decode(buf: &mut &[u8]) -> alloy::rlp::Result<Self> {
79        fn opt_decode<T: Decodable>(buf: &mut &[u8]) -> alloy::rlp::Result<Option<T>> {
80            Ok(Decodable::decode(buf).ok()) // TODO: better validation of error?
81        }
82
83        let gas_per_pubdata = Decodable::decode(buf)?;
84        let factory_deps = Decodable::decode(buf)?;
85        let custom_signature = opt_decode(buf)?;
86        let paymaster_params = opt_decode(buf)?;
87
88        Ok(Self {
89            gas_per_pubdata,
90            factory_deps,
91            custom_signature,
92            paymaster_params,
93        })
94    }
95}
96
97impl Encodable for Eip712Meta {
98    fn encode(&self, out: &mut dyn alloy::rlp::BufMut) {
99        fn opt_encode<T>(stream: &mut dyn alloy::rlp::BufMut, value: Option<T>)
100        where
101            T: Encodable,
102        {
103            if let Some(v) = value {
104                v.encode(stream);
105            } else {
106                "".encode(stream);
107            }
108        }
109        self.gas_per_pubdata.encode(out);
110        self.factory_deps.encode(out);
111        opt_encode(out, self.custom_signature.clone());
112        opt_encode(out, self.paymaster_params.clone());
113    }
114
115    // TODO: Implement `length` method
116}
117
118/// Represents the paymaster parameters for ZKsync Era transactions.
119#[derive(Default, Serialize, Deserialize, Clone, PartialEq, Debug, Eq, Hash)]
120#[serde(rename_all = "camelCase")]
121pub struct PaymasterParams {
122    /// Address of the paymaster.
123    pub paymaster: Address,
124    /// Paymaster input.
125    // A custom serialization is needed (otherwise RLP treats it as string).
126    #[serde(serialize_with = "serialize_bytes_custom")]
127    pub paymaster_input: Bytes,
128}
129
130impl Decodable for PaymasterParams {
131    fn decode(buf: &mut &[u8]) -> alloy::rlp::Result<Self> {
132        let mut bytes = Header::decode_bytes(buf, true)?;
133        let payload_view = &mut bytes;
134        Ok(Self {
135            paymaster: dbg!(Decodable::decode(payload_view))?,
136            paymaster_input: dbg!(Decodable::decode(payload_view))?,
137        })
138    }
139}
140
141impl Encodable for PaymasterParams {
142    fn encode(&self, out: &mut dyn alloy::rlp::BufMut) {
143        // paymaster params have to be encoded as a list.
144        let h = Header {
145            list: true,
146            payload_length: self.paymaster.length() + self.paymaster_input.length(),
147        };
148        h.encode(out);
149        self.paymaster.encode(out);
150        self.paymaster_input.encode(out);
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use alloy::primitives::address;
158    use serde_json::json;
159
160    #[test]
161    fn test_bytes_get_serialized_into_vec() {
162        let meta = Eip712Meta {
163            gas_per_pubdata: U256::from(4),
164            factory_deps: vec![vec![1, 2].into()],
165            custom_signature: Some(vec![3, 4].into()),
166            paymaster_params: Some(PaymasterParams {
167                paymaster: address!("99E12239CBf8112fBB3f7Fd473d0558031abcbb5"),
168                paymaster_input: vec![5, 6].into(),
169            }),
170        };
171
172        let expected_json = json!({
173            "gasPerPubdata": "0x4",
174            "factoryDeps": [[1,2]],
175            "customSignature": [3, 4],
176            "paymasterParams": {
177                 "paymaster": "0x99e12239cbf8112fbb3f7fd473d0558031abcbb5",
178                 "paymasterInput": [5, 6],
179            }
180        });
181
182        let actual_json = serde_json::to_value(&meta).unwrap();
183        assert_eq!(expected_json, actual_json);
184    }
185}