1use crate::{
4 contracts::{
5 common::erc20::{ERC20, encode_token_data_for_bridge},
6 l1::{
7 bridge_hub::{
8 Bridgehub::{self},
9 L2TransactionRequestDirect, L2TransactionRequestTwoBridges,
10 },
11 l1_bridge::{L1Bridge, encode_deposit_token_calldata},
12 },
13 l2::l2_bridge::encode_finalize_deposit_calldata,
14 },
15 network::{Zksync, transaction_request::TransactionRequest},
16 provider::{
17 L1CommunicationError, ZksyncProvider, l1_transaction_receipt::L1TransactionReceipt,
18 },
19 utils::{ETHER_L1_ADDRESS, apply_l1_to_l2_alias},
20};
21use alloy::{
22 eips::eip1559::Eip1559Estimation,
23 network::{Ethereum, NetworkWallet, TransactionBuilder},
24 primitives::{Address, Bytes, U256},
25 providers::{WalletProvider, utils::Eip1559Estimator},
26 rpc::types::eth::TransactionRequest as L1TransactionRequest,
27};
28use std::str::FromStr;
29
30pub const REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT: u64 = 800;
31
32#[derive(Clone, Debug)]
36pub struct DepositRequest {
37 pub amount: U256,
39 pub receiver: Option<Address>,
41 pub token: Address,
43 pub bridge_address: Option<Address>,
45 pub gas_per_pubdata_limit: U256,
48 pub auto_approval: bool,
51}
52
53impl DepositRequest {
54 pub fn new(amount: U256) -> Self {
56 Self {
57 amount,
58 receiver: None,
59 token: ETHER_L1_ADDRESS,
60 bridge_address: None,
61 gas_per_pubdata_limit: U256::from(REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT),
62 auto_approval: true,
63 }
64 }
65
66 pub fn amount(&self) -> &U256 {
68 &self.amount
69 }
70
71 pub fn with_receiver(mut self, address: Address) -> Self {
73 self.receiver = Some(address);
74 self
75 }
76
77 pub fn with_token(mut self, token: Address) -> Self {
79 self.token = token;
80 self
81 }
82
83 pub fn with_gas_per_pubdata_limit(mut self, value: U256) -> Self {
85 self.gas_per_pubdata_limit = value;
86 self
87 }
88
89 pub fn with_bridge_address(mut self, bridge_address: Address) -> Self {
91 self.bridge_address = Some(bridge_address);
92 self
93 }
94
95 pub fn with_auto_approval(mut self, auto_approval: bool) -> Self {
97 self.auto_approval = auto_approval;
98 self
99 }
100}
101
102#[derive(Clone, Debug, Copy)]
103struct FeeParams {
104 max_fee_per_gas: u128,
105 max_priority_fee_per_gas: u128,
106}
107
108#[derive(Clone, Debug, Copy)]
109struct BridgeL2TxFeeParams {
110 pub gas_limit: U256,
111 pub tx_base_cost: U256,
112}
113
114#[derive(Clone, Debug, Copy)]
115struct BridgeAddresses {
116 pub l1_bridge_address: Address,
117 pub l2_bridge_address: Address,
118}
119
120pub fn scale_l1_gas_limit(l1_gas_limit: u64) -> u64 {
124 const L1_FEE_ESTIMATION_COEF_NUMERATOR: u64 = 12;
128
129 const L1_FEE_ESTIMATION_COEF_DENOMINATOR: u64 = 10;
133 l1_gas_limit * L1_FEE_ESTIMATION_COEF_NUMERATOR / L1_FEE_ESTIMATION_COEF_DENOMINATOR
134}
135
136pub struct DepositExecutor<'a, P1, P2>
138where
139 P1: alloy::providers::Provider<Ethereum>,
140 P2: ZksyncProvider + WalletProvider<Zksync> + ?Sized,
141{
142 l1_provider: &'a P1,
143 l2_provider: &'a P2,
144 request: &'a DepositRequest,
145}
146
147impl<'a, P1, P2> DepositExecutor<'a, P1, P2>
148where
149 P1: alloy::providers::Provider<Ethereum>,
150 P2: ZksyncProvider + WalletProvider<Zksync> + ?Sized,
151{
152 pub fn new(l1_provider: &'a P1, l2_provider: &'a P2, request: &'a DepositRequest) -> Self {
154 DepositExecutor {
155 l1_provider,
156 l2_provider,
157 request,
158 }
159 }
160
161 async fn get_bridge_addresses_for_deposit(
162 &self,
163 l2_chain_id: U256,
164 ) -> Result<(Address, Address), L1CommunicationError> {
165 let (l1_bridge_address, l2_bridge_address) = match self.request.bridge_address {
166 Some(l1_bridge_address) => {
167 let l1_bridge = L1Bridge::new(l1_bridge_address, self.l1_provider);
168 let l2_bridge_address = l1_bridge
169 .l2BridgeAddress(l2_chain_id)
170 .call()
171 .await
172 .map_err(|_| {
173 L1CommunicationError::Custom("Error while getting L2 bridge address.")
174 })?;
175 (l1_bridge_address, l2_bridge_address)
176 }
177 None => {
178 let bridge_addresses =
179 self.l2_provider.get_bridge_contracts().await.map_err(|_| {
180 L1CommunicationError::Custom(
181 "Error occurred while fetching bridge contracts.",
182 )
183 })?;
184 (
185 bridge_addresses.l1_shared_default_bridge.ok_or(
186 L1CommunicationError::Custom(
187 "L1 shared default bridge is not defined for the chain and bridge address is not specified in the deposit request.",
188 ),
189 )?,
190 bridge_addresses.l2_shared_default_bridge.ok_or(
191 L1CommunicationError::Custom(
192 "L2 shared default bridge is not defined for the chain.",
193 ),
194 )?,
195 )
196 }
197 };
198 Ok((l1_bridge_address, l2_bridge_address))
199 }
200
201 async fn get_l1_fee_params(&self) -> Result<FeeParams, L1CommunicationError> {
202 let max_priority_fee_per_gas = self
203 .l1_provider
204 .get_max_priority_fee_per_gas()
205 .await
206 .map_err(|_| {
207 L1CommunicationError::Custom(
208 "Error occurred while fetching L1 max_priority_fee_per_gas.",
209 )
210 })?;
211 let base_l1_fees_data = self
214 .l1_provider
215 .estimate_eip1559_fees_with(Eip1559Estimator::new(|base_fee_per_gas, _| {
216 Eip1559Estimation {
217 max_fee_per_gas: base_fee_per_gas * 3 / 2,
218 max_priority_fee_per_gas: 0,
219 }
220 }))
221 .await
222 .map_err(|_| {
223 L1CommunicationError::Custom("Error occurred while estimating L1 base fees.")
224 })?;
225 let max_fee_per_gas = base_l1_fees_data.max_fee_per_gas + max_priority_fee_per_gas;
226
227 Ok(FeeParams {
228 max_fee_per_gas,
229 max_priority_fee_per_gas,
230 })
231 }
232
233 async fn get_l1_tx_gas_limit(
234 &self,
235 tx_request: &L1TransactionRequest,
236 ) -> Result<u64, L1CommunicationError> {
237 let l1_tx_gas_estimation = self
238 .l1_provider
239 .estimate_gas(tx_request.clone())
240 .await
241 .map_err(|_| {
242 L1CommunicationError::Custom(
243 "Error occurred while estimating gas limit for the L1 transaction.",
244 )
245 })?;
246 let l1_gas_limit = scale_l1_gas_limit(l1_tx_gas_estimation);
247 Ok(l1_gas_limit)
248 }
249
250 async fn get_bridge_l2_tx_fee_params<P>(
251 &self,
252 bridge_hub_contract: &Bridgehub::BridgehubInstance<&P>,
253 l1_to_l2_tx: TransactionRequest,
254 l2_chain_id: U256,
255 fee_params: &FeeParams,
256 ) -> Result<BridgeL2TxFeeParams, L1CommunicationError>
257 where
258 P: alloy::providers::Provider<Ethereum>,
259 {
260 let gas_limit = self
261 .l2_provider
262 .estimate_gas_l1_to_l2(l1_to_l2_tx)
263 .await
264 .map_err(|_| {
265 L1CommunicationError::Custom(
266 "Error occurred while estimating gas for L1 -> L2 transaction.",
267 )
268 })?;
269
270 let tx_base_cost = bridge_hub_contract
271 .l2TransactionBaseCost(
272 l2_chain_id,
273 U256::from(fee_params.max_fee_per_gas),
274 gas_limit,
275 self.request.gas_per_pubdata_limit,
276 )
277 .call()
278 .await
279 .map_err(|_| {
280 L1CommunicationError::Custom(
281 "Error occurred while estimating L2 transaction base cost.",
282 )
283 })?;
284 Ok(BridgeL2TxFeeParams {
285 gas_limit,
286 tx_base_cost,
287 })
288 }
289
290 async fn get_l1_deposit_tx(
291 &self,
292 sender: Address,
293 receiver: Address,
294 bridge_addresses: Option<BridgeAddresses>,
295 l2_chain_id: U256,
296 fee_params: &FeeParams,
297 ) -> Result<L1TransactionRequest, L1CommunicationError> {
298 let bridge_hub_contract_address = self
299 .l2_provider
300 .get_bridgehub_contract()
301 .await
302 .map_err(|_| {
303 L1CommunicationError::Custom(
304 "Error occurred while fetching the bridge hub contract address.",
305 )
306 })?
307 .unwrap();
308 let bridge_hub_contract = Bridgehub::new(bridge_hub_contract_address, self.l1_provider);
309
310 let l1_tx_request = if self.request.token == ETHER_L1_ADDRESS {
311 let l2_tx_fee = self
312 .get_bridge_l2_tx_fee_params(
313 &bridge_hub_contract,
314 TransactionRequest::default()
315 .with_from(sender)
316 .with_to(receiver)
317 .with_value(self.request.amount)
318 .with_gas_per_pubdata(self.request.gas_per_pubdata_limit)
319 .with_input(Bytes::from("0x")),
320 l2_chain_id,
321 fee_params,
322 )
323 .await?;
324
325 let l1_value = l2_tx_fee.tx_base_cost + self.request.amount;
326 bridge_hub_contract
327 .requestL2TransactionDirect(L2TransactionRequestDirect {
328 chainId: l2_chain_id,
329 mintValue: l1_value,
330 l2Contract: receiver,
331 l2Value: self.request.amount,
332 l2Calldata: Bytes::from_str("0x").unwrap(),
333 l2GasLimit: l2_tx_fee.gas_limit,
334 l2GasPerPubdataByteLimit: self.request.gas_per_pubdata_limit,
335 factoryDeps: vec![],
336 refundRecipient: sender,
337 })
338 .value(l1_value)
339 .into_transaction_request()
340 } else {
341 let bridge_addresses = bridge_addresses.unwrap();
342 let erc20_contract = ERC20::new(self.request.token, self.l1_provider);
343 let token_data = encode_token_data_for_bridge(&erc20_contract)
344 .await
345 .map_err(|_| {
346 L1CommunicationError::Custom("Error while encoding ERC20 token data.")
347 })?;
348 let l2_finalize_deposit_calldata = encode_finalize_deposit_calldata(
349 sender,
350 receiver,
351 self.request.token,
352 self.request.amount,
353 token_data,
354 );
355
356 let l2_tx_fee = self
357 .get_bridge_l2_tx_fee_params(
358 &bridge_hub_contract,
359 TransactionRequest::default()
360 .with_from(apply_l1_to_l2_alias(bridge_addresses.l1_bridge_address))
361 .with_to(bridge_addresses.l2_bridge_address)
362 .with_gas_per_pubdata(self.request.gas_per_pubdata_limit)
363 .with_input(l2_finalize_deposit_calldata),
364 l2_chain_id,
365 fee_params,
366 )
367 .await?;
368
369 let bridge_calldata =
370 encode_deposit_token_calldata(self.request.token, self.request.amount, receiver);
371 bridge_hub_contract
372 .requestL2TransactionTwoBridges(L2TransactionRequestTwoBridges {
373 chainId: l2_chain_id,
374 mintValue: l2_tx_fee.tx_base_cost,
375 l2Value: U256::from(0),
376 l2GasLimit: l2_tx_fee.gas_limit,
377 l2GasPerPubdataByteLimit: self.request.gas_per_pubdata_limit,
378 refundRecipient: sender,
379 secondBridgeAddress: bridge_addresses.l1_bridge_address,
380 secondBridgeValue: U256::from(0),
381 secondBridgeCalldata: bridge_calldata,
382 })
383 .from(sender)
384 .value(l2_tx_fee.tx_base_cost)
385 .into_transaction_request()
386 };
387 Ok(l1_tx_request
388 .max_fee_per_gas(fee_params.max_fee_per_gas)
389 .max_priority_fee_per_gas(fee_params.max_priority_fee_per_gas))
390 }
391
392 async fn approve_tokens(
393 &self,
394 sender: Address,
395 bridge_addresses: Option<BridgeAddresses>,
396 fee_params: &FeeParams,
397 ) -> Result<(), L1CommunicationError> {
398 if self.request.token == ETHER_L1_ADDRESS {
399 return Ok(());
400 }
401 let bridge_addresses = bridge_addresses.unwrap();
402 let erc20_contract = ERC20::new(self.request.token, self.l1_provider);
403 let token_allowance = erc20_contract
404 .allowance(sender, bridge_addresses.l1_bridge_address)
405 .call()
406 .await
407 .map_err(|_| {
408 L1CommunicationError::Custom(
409 "Error occurred while fetching token allowance for the bridge.",
410 )
411 })?;
412
413 let allowance_deficit = self.request.amount - token_allowance;
414 if allowance_deficit > U256::from(0) {
415 if !self.request.auto_approval {
416 return Err(L1CommunicationError::Custom(
417 "Deposit request auto_approval is disabled and the current token allowance won't cover the deposit. Consider enabling deposit request auto_approval or approving tokens manually before the deposit.",
418 ));
419 }
420 let approve_tx_builder = erc20_contract
421 .approve(bridge_addresses.l1_bridge_address, allowance_deficit)
422 .from(sender);
423 let approve_tx = approve_tx_builder.into_transaction_request();
424 let approve_tx_gas_limit = self.get_l1_tx_gas_limit(&approve_tx).await?;
425
426 self
427 .l1_provider
428 .send_transaction( approve_tx
429 .max_fee_per_gas(fee_params.max_fee_per_gas)
430 .max_priority_fee_per_gas(fee_params.max_priority_fee_per_gas)
431 .gas_limit(approve_tx_gas_limit))
432 .await.map_err(|_| {
433 L1CommunicationError::Custom(
434 "Error occurred while approving tokens for the bridge address",
435 )
436 })?
437 .watch()
438 .await
439 .map_err(|_| {
440 L1CommunicationError::Custom(
441 "Error occurred while approving tokens for the bridge address. Approve transaction has failed.",
442 )
443 })?;
444 }
445 Ok(())
446 }
447
448 async fn submit(
449 &self,
450 tx_request: &L1TransactionRequest,
451 ) -> Result<L1TransactionReceipt, L1CommunicationError> {
452 let l1_gas_limit = self.get_l1_tx_gas_limit(tx_request).await?;
453 let l1_tx_request = tx_request.clone().with_gas_limit(l1_gas_limit);
454 let l1_tx_receipt = self
455 .l1_provider
456 .send_transaction(l1_tx_request)
457 .await
458 .map_err(|_| {
459 L1CommunicationError::Custom(
460 "Error occurred while sending the L1 -> L2 deposit transaction.",
461 )
462 })?
463 .get_receipt()
464 .await
465 .map_err(|_| {
466 L1CommunicationError::Custom(
467 "Error occurred while sending the L1 -> L2 deposit transaction receipt.",
468 )
469 })?;
470 Ok(L1TransactionReceipt::new(
471 l1_tx_receipt,
472 self.l2_provider.root().clone(),
473 ))
474 }
475
476 pub async fn execute(&self) -> Result<L1TransactionReceipt, L1CommunicationError> {
489 let l2_chain_id = U256::from(self.l2_provider.get_chain_id().await.map_err(|_| {
490 L1CommunicationError::Custom("Error occurred while fetching L2 chain id.")
491 })?);
492
493 let bridge_addresses = if self.request.token != ETHER_L1_ADDRESS {
494 let (l1_bridge_address, l2_bridge_address) =
495 self.get_bridge_addresses_for_deposit(l2_chain_id).await?;
496
497 Some(BridgeAddresses {
498 l1_bridge_address,
499 l2_bridge_address,
500 })
501 } else {
502 None
503 };
504
505 let sender = self.l2_provider.wallet().default_signer_address();
506 let receiver = self.request.receiver.unwrap_or(sender);
507
508 let l1_fee_params = self.get_l1_fee_params().await?;
509
510 let l1_deposit_tx = self
511 .get_l1_deposit_tx(
512 sender,
513 receiver,
514 bridge_addresses,
515 l2_chain_id,
516 &l1_fee_params,
517 )
518 .await?;
519
520 self.approve_tokens(sender, bridge_addresses, &l1_fee_params)
521 .await?;
522
523 self.submit(&l1_deposit_tx).await
524 }
525}