zksync_web3_rs/eip712/
transaction_request.rs

1use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr};
2
3use super::{hash_bytecode, rlp_append_option, Eip712Meta};
4use crate::{
5    zks_utils::{
6        self, CONTRACT_DEPLOYER_ADDR, EIP712_TX_TYPE, ERA_CHAIN_ID, MAX_PRIORITY_FEE_PER_GAS,
7    },
8    zks_wallet::{DeployRequest, Overrides, TransferRequest, WithdrawRequest, ZKRequestError},
9};
10use ethers::{
11    abi::{Abi, HumanReadableParser, ParseError},
12    types::{
13        transaction::{eip2930::AccessList, eip712::Eip712Error},
14        Address, Bytes, Signature, U256,
15    },
16    utils::rlp::{Encodable, RlpStream},
17};
18use ethers_contract::encode_function_data;
19use serde::{Deserialize, Serialize};
20
21// TODO: Not all the fields are optional. This was copied from the JS implementation.
22#[derive(Serialize, Deserialize, Clone, Debug)]
23#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
24pub struct Eip712TransactionRequest {
25    /* These need to be filled before estimating the gas */
26    pub to: Address,
27    pub from: Address,
28    pub nonce: U256,
29    pub gas: U256,
30    pub gas_price: U256,
31    pub data: Bytes,
32    pub value: U256,
33    pub chain_id: U256,
34    pub r#type: U256,
35    pub max_priority_fee_per_gas: U256,
36    #[serde(rename = "eip712Meta")]
37    pub custom_data: Eip712Meta,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub access_list: Option<AccessList>,
40
41    /* Filled after estimating the gas */
42    // Unknown until we estimate the gas.
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub gas_limit: Option<U256>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub max_fee_per_gas: Option<U256>, // conflicts with gas_price
47
48    pub ccip_read_enabled: bool,
49}
50
51impl Eip712TransactionRequest {
52    pub fn new() -> Self {
53        Self::default()
54    }
55
56    pub fn from_overrides(overrides: Overrides) -> Self {
57        let mut tx = Self::default();
58        if let Some(value) = overrides.value {
59            tx.value = value;
60        }
61        tx
62    }
63
64    pub fn to<T>(mut self, to: T) -> Self
65    where
66        T: Into<Address>,
67    {
68        self.to = to.into();
69        self
70    }
71
72    pub fn from<T>(mut self, from: T) -> Self
73    where
74        T: Into<Address>,
75    {
76        self.from = from.into();
77        self
78    }
79
80    pub fn nonce<T>(mut self, nonce: T) -> Self
81    where
82        T: Into<U256>,
83    {
84        self.nonce = nonce.into();
85        self
86    }
87
88    pub fn gas_limit<T>(mut self, gas_limit: T) -> Self
89    where
90        T: Into<U256>,
91    {
92        self.gas_limit = Some(gas_limit.into());
93        self
94    }
95
96    pub fn gas_price<T>(mut self, gas_price: T) -> Self
97    where
98        T: Into<U256>,
99    {
100        self.gas_price = gas_price.into();
101        self
102    }
103
104    pub fn data<T>(mut self, data: T) -> Self
105    where
106        T: Into<Bytes>,
107    {
108        self.data = data.into();
109        self
110    }
111
112    pub fn value<T>(mut self, value: T) -> Self
113    where
114        T: Into<U256>,
115    {
116        self.value = value.into();
117        self
118    }
119
120    pub fn chain_id<T>(mut self, chain_id: T) -> Self
121    where
122        T: Into<U256>,
123    {
124        self.chain_id = chain_id.into();
125        self
126    }
127
128    pub fn r#type<T>(mut self, r#type: T) -> Self
129    where
130        T: Into<U256>,
131    {
132        self.r#type = r#type.into();
133        self
134    }
135
136    pub fn access_list<T>(mut self, access_list: AccessList) -> Self {
137        self.access_list = Some(access_list);
138        self
139    }
140
141    pub fn max_priority_fee_per_gas<T>(mut self, max_priority_fee_per_gas: T) -> Self
142    where
143        T: Into<U256>,
144    {
145        self.max_priority_fee_per_gas = max_priority_fee_per_gas.into();
146        self
147    }
148
149    pub fn max_fee_per_gas<T>(mut self, max_fee_per_gas: T) -> Self
150    where
151        T: Into<U256>,
152    {
153        self.max_fee_per_gas = Some(max_fee_per_gas.into());
154        self
155    }
156
157    pub fn custom_data(mut self, custom_data: Eip712Meta) -> Self {
158        self.custom_data = custom_data;
159        self
160    }
161
162    pub fn ccip_read_enabled(mut self, ccip_read_enabled: bool) -> Self {
163        self.ccip_read_enabled = ccip_read_enabled;
164        self
165    }
166
167    pub fn rlp_unsigned(&self) -> Result<Bytes, Eip712Error> {
168        self.rlp(None)
169    }
170
171    pub fn rlp_signed(&self, signature: Signature) -> Result<Bytes, Eip712Error> {
172        self.rlp(Some(signature))
173    }
174
175    pub fn rlp(&self, signature: Option<Signature>) -> Result<Bytes, Eip712Error> {
176        let mut rlp = RlpStream::new();
177        rlp.begin_unbounded_list();
178        rlp.append(&self.nonce);
179        rlp.append(&self.max_priority_fee_per_gas);
180        rlp.append(&self.gas_price);
181        rlp_append_option(&mut rlp, self.gas_limit);
182        rlp.append(&self.to);
183        rlp.append(&self.value);
184        rlp.append(&self.data.0);
185        if let Some(sig) = signature {
186            rlp.append(&sig.v);
187            // Convert to big-endian bytes (32 bytes in total)
188            let mut bytes = [0_u8; 32]; // U256 is 32 bytes
189            sig.r.to_big_endian(&mut bytes);
190            rlp.append(&bytes.as_slice());
191            sig.s.to_big_endian(&mut bytes);
192            rlp.append(&bytes.as_slice());
193        }
194        rlp.append(&self.chain_id);
195        rlp.append(&self.from);
196        self.custom_data.rlp_append(&mut rlp);
197        rlp.finalize_unbounded_list();
198        Ok(rlp.out().freeze().into())
199    }
200}
201
202impl Default for Eip712TransactionRequest {
203    fn default() -> Self {
204        Self {
205            to: Default::default(),
206            from: Default::default(),
207            nonce: Default::default(),
208            gas: Default::default(),
209            gas_limit: Default::default(),
210            gas_price: Default::default(),
211            data: Default::default(),
212            value: Default::default(),
213            chain_id: ERA_CHAIN_ID.into(),
214            r#type: EIP712_TX_TYPE.into(),
215            access_list: Default::default(),
216            max_priority_fee_per_gas: MAX_PRIORITY_FEE_PER_GAS.into(),
217            max_fee_per_gas: Default::default(),
218            custom_data: Default::default(),
219            ccip_read_enabled: Default::default(),
220        }
221    }
222}
223
224impl TryFrom<WithdrawRequest> for Eip712TransactionRequest {
225    type Error = ZKRequestError;
226
227    fn try_from(request: WithdrawRequest) -> Result<Self, Self::Error> {
228        let contract_address =
229            Address::from_str(zks_utils::CONTRACTS_L2_ETH_TOKEN_ADDR).map_err(|e| {
230                ZKRequestError::CustomError(format!("Error getting L2 ETH token address {e:?}"))
231            })?;
232        let function_signature = "function withdraw(address _l1Receiver) external payable override";
233        let function = HumanReadableParser::parse_function(function_signature)
234            .map_err(ParseError::LexerError)?;
235        let function_args = function.decode_input(&zks_utils::encode_args(
236            &function,
237            &[format!("{:?}", request.to)],
238        )?)?;
239        let data: Bytes = function.encode_input(&function_args)?.into();
240
241        Ok(Eip712TransactionRequest::new()
242            .r#type(EIP712_TX_TYPE)
243            .to(contract_address)
244            .value(request.amount)
245            .from(request.from)
246            .data(data))
247    }
248}
249
250impl From<TransferRequest> for Eip712TransactionRequest {
251    fn from(request: TransferRequest) -> Self {
252        Eip712TransactionRequest::new()
253            .r#type(EIP712_TX_TYPE)
254            .to(request.to)
255            .value(request.amount)
256            .from(request.from)
257    }
258}
259
260impl TryFrom<DeployRequest> for Eip712TransactionRequest {
261    type Error = ZKRequestError;
262
263    fn try_from(request: DeployRequest) -> Result<Self, Self::Error> {
264        let mut contract_deployer_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
265        contract_deployer_path.push("src/abi/ContractDeployer.json");
266
267        let custom_data = Eip712Meta::new().factory_deps({
268            let mut factory_deps = Vec::new();
269            if let Some(factory_dependencies) = request.factory_deps {
270                factory_deps.extend(factory_dependencies);
271            }
272            factory_deps.push(request.contract_bytecode.clone());
273            factory_deps
274        });
275
276        let contract_deployer = Abi::load(BufReader::new(
277            File::open(contract_deployer_path).map_err(|e| {
278                ZKRequestError::CustomError(format!(
279                    "Error opening contract deployer abi file {e:?}"
280                ))
281            })?,
282        ))?;
283        let create = contract_deployer.function("create")?;
284
285        // TODO: User could provide this instead of defaulting.
286        let salt = [0_u8; 32];
287        let bytecode_hash = hash_bytecode(&request.contract_bytecode).map_err(|e| {
288            ZKRequestError::CustomError(format!("Error hashing contract bytecode {e:?}"))
289        })?;
290        let call_data: Bytes = match (
291            request.contract_abi.constructor(),
292            request.constructor_parameters.is_empty(),
293        ) {
294            (None, false) => {
295                return Err(ZKRequestError::CustomError(
296                    "Constructor not present".to_owned(),
297                ))
298            }
299            (None, true) | (Some(_), true) => Bytes::default(),
300            (Some(constructor), false) => {
301                zks_utils::encode_constructor_args(constructor, &request.constructor_parameters)?
302                    .into()
303            }
304        };
305
306        let data = encode_function_data(create, (salt, bytecode_hash, call_data))?;
307
308        let contract_deployer_address = Address::from_str(CONTRACT_DEPLOYER_ADDR).map_err(|e| {
309            ZKRequestError::CustomError(format!("Error getting contract deployer address {e:?}"))
310        })?;
311        Ok(Eip712TransactionRequest::new()
312            .r#type(EIP712_TX_TYPE)
313            .to(contract_deployer_address)
314            .custom_data(custom_data)
315            .data(data))
316    }
317}