Skip to main content

ethrex_l2_sdk/
sdk.rs

1use bytes::Bytes;
2use calldata::encode_calldata;
3use ethereum_types::{H160, H256, U256};
4use ethrex_common::types::EIP7702Transaction;
5use ethrex_common::types::FeeTokenTransaction;
6use ethrex_common::types::Fork;
7use ethrex_common::utils::keccak;
8use ethrex_common::{
9    Address,
10    types::{
11        AccessListEntry, BlobsBundle, EIP1559Transaction, GenericTransaction, TxKind, TxType,
12        WrappedEIP4844Transaction,
13    },
14};
15use ethrex_l2_common::{calldata::Value, messages::L1MessageProof};
16use ethrex_l2_rpc::{
17    clients::get_l1_message_proof,
18    signer::{LocalSigner, Signable, Signer},
19};
20use ethrex_rlp::encode::RLPEncode;
21use ethrex_rpc::clients::eth::{EthClient, Overrides, errors::EthClientError};
22use ethrex_rpc::types::block_identifier::{BlockIdentifier, BlockTag};
23use ethrex_rpc::types::receipt::RpcReceipt;
24use secp256k1::SecretKey;
25use serde::{Deserialize, Deserializer, Serialize, Serializer};
26use std::ops::{Add, Div};
27use std::str::FromStr;
28use std::{fs::read_to_string, path::Path};
29use tracing::{error, warn};
30
31pub mod calldata;
32pub mod l1_to_l2_tx_data;
33pub mod privileged_data;
34
35pub use l1_to_l2_tx_data::{L1ToL2TransactionData, send_l1_to_l2_tx};
36
37// Reexport the contracts module
38#[doc(inline)]
39pub use ethrex_sdk_contract_utils::*;
40
41use calldata::from_hex_string_to_h256_array;
42
43use crate::privileged_data::PrivilegedTransactionData;
44
45// 0x797b347d2209f7dcb8f9bb68fe4296a05d5a2c2b
46pub const DEFAULT_BRIDGE_ADDRESS: Address = H160([
47    0x79, 0x7b, 0x34, 0x7d, 0x22, 0x09, 0xf7, 0xdc, 0xb8, 0xf9, 0xbb, 0x68, 0xfe, 0x42, 0x96, 0xa0,
48    0x5d, 0x5a, 0x2c, 0x2b,
49]);
50
51// 0x000000000000000000000000000000000000ffff
52pub const COMMON_BRIDGE_L2_ADDRESS: Address = H160([
53    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
54    0x00, 0x00, 0xff, 0xff,
55]);
56
57// 0x000000000000000000000000000000000000fffe
58pub const L2_TO_L1_MESSENGER_ADDRESS: Address = H160([
59    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
60    0x00, 0x00, 0xff, 0xfe,
61]);
62
63// 0x000000000000000000000000000000000000fffc
64pub const FEE_TOKEN_REGISTRY_ADDRESS: Address = H160([
65    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
66    0x00, 0x00, 0xff, 0xfc,
67]);
68
69// 0x000000000000000000000000000000000000fffb
70pub const FEE_TOKEN_PRICER_ADDRESS: Address = H160([
71    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
72    0x00, 0x00, 0xff, 0xfb,
73]);
74
75// 0xee110000000000000000000000000000000011ff
76pub const ADDRESS_ALIASING: Address = H160([
77    0xee, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
78    0x00, 0x00, 0x11, 0xff,
79]);
80
81pub const L2_WITHDRAW_SIGNATURE: &str = "withdraw(address)";
82
83/// Verifier ID for SP1 proofs in the OnChainProposer verificationKeys mapping.
84pub const SP1_VERIFIER_ID: u8 = 1;
85/// Verifier ID for RISC0 proofs in the OnChainProposer verificationKeys mapping.
86pub const RISC0_VERIFIER_ID: u8 = 2;
87
88pub const REGISTER_FEE_TOKEN_SIGNATURE: &str = "registerNewFeeToken(address)";
89pub const SET_FEE_TOKEN_RATIO_SIGNATURE: &str = "setFeeTokenRatio(address,uint256)";
90
91/// Bytecode of the OpenZeppelin's ERC1967Proxy contract.
92/// This is generated by the [build script](../build.rs) only if the `COMPILE_CONTRACTS` env var is enabled.
93const ERC1967_PROXY_BYTECODE: &[u8] = include_bytes!(concat!(
94    env!("OUT_DIR"),
95    "/contracts/solc_out/ERC1967Proxy.bytecode"
96));
97
98#[derive(Debug, thiserror::Error)]
99pub enum SdkError {
100    #[error("Failed to parse address from hex")]
101    FailedToParseAddressFromHex,
102}
103
104/// BRIDGE_ADDRESS or 0x797b347d2209f7dcb8f9bb68fe4296a05d5a2c2b
105pub fn bridge_address() -> Result<Address, SdkError> {
106    std::env::var("ETHREX_WATCHER_BRIDGE_ADDRESS")
107        .unwrap_or(format!("{DEFAULT_BRIDGE_ADDRESS:#x}"))
108        .parse()
109        .map_err(|_| SdkError::FailedToParseAddressFromHex)
110}
111
112pub async fn wait_for_transaction_receipt(
113    tx_hash: H256,
114    client: &EthClient,
115    max_retries: u64,
116) -> Result<RpcReceipt, EthClientError> {
117    let mut r#try = 1;
118    loop {
119        match client.get_transaction_receipt(tx_hash).await {
120            Ok(Some(receipt)) => return Ok(receipt),
121            Ok(None) => {
122                // Receipt not yet available, retry
123            }
124            Err(e) => {
125                // Geth 1.14+ returns "transaction indexing is in progress" error instead of null
126                // when the transaction indexer hasn't caught up yet. We should retry in this case.
127                // See: https://github.com/ethereum/go-ethereum/issues/29956
128                let error_msg = e.to_string();
129                if !error_msg.contains("transaction indexing is in progress") {
130                    return Err(e);
131                }
132                // Indexing in progress, retry
133            }
134        }
135
136        println!("[{try}/{max_retries}] Retrying to get transaction receipt for {tx_hash:#x}");
137
138        if max_retries == r#try {
139            return Err(EthClientError::Custom(format!(
140                "Transaction receipt for {tx_hash:#x} not found after {max_retries} retries"
141            )));
142        }
143        r#try += 1;
144
145        tokio::time::sleep(std::time::Duration::from_secs(2)).await;
146    }
147}
148
149pub async fn transfer(
150    amount: U256,
151    from: Address,
152    to: Address,
153    private_key: &SecretKey,
154    client: &EthClient,
155) -> Result<H256, EthClientError> {
156    println!("Transferring {amount} from {from:#x} to {to:#x}");
157    let gas_price = client
158        .get_gas_price_with_extra(20)
159        .await?
160        .try_into()
161        .map_err(|_| {
162            EthClientError::InternalError("Failed to convert gas_price to a u64".to_owned())
163        })?;
164
165    let tx = build_generic_tx(
166        client,
167        TxType::EIP1559,
168        to,
169        from,
170        Default::default(),
171        Overrides {
172            value: Some(amount),
173            max_fee_per_gas: Some(gas_price),
174            max_priority_fee_per_gas: Some(gas_price),
175            ..Default::default()
176        },
177    )
178    .await?;
179
180    let signer = LocalSigner::new(*private_key).into();
181    send_generic_transaction(client, tx, &signer).await
182}
183
184pub async fn deposit_through_transfer(
185    amount: U256,
186    from: Address,
187    from_pk: &SecretKey,
188    eth_client: &EthClient,
189) -> Result<H256, EthClientError> {
190    println!("Depositing {amount} from {from:#x} to bridge");
191    transfer(
192        amount,
193        from,
194        bridge_address().map_err(|err| EthClientError::Custom(err.to_string()))?,
195        from_pk,
196        eth_client,
197    )
198    .await
199}
200
201pub async fn withdraw(
202    amount: U256,
203    from: Address,
204    from_pk: SecretKey,
205    proposer_client: &EthClient,
206    nonce: Option<u64>,
207    gas_limit: Option<u64>,
208) -> Result<H256, EthClientError> {
209    let withdraw_transaction = build_generic_tx(
210        proposer_client,
211        TxType::EIP1559,
212        COMMON_BRIDGE_L2_ADDRESS,
213        from,
214        Bytes::from(encode_calldata(
215            L2_WITHDRAW_SIGNATURE,
216            &[Value::Address(from)],
217        )?),
218        Overrides {
219            value: Some(amount),
220            nonce,
221            gas_limit,
222            ..Default::default()
223        },
224    )
225    .await?;
226
227    let signer = LocalSigner::new(from_pk).into();
228
229    send_generic_transaction(proposer_client, withdraw_transaction, &signer).await
230}
231
232pub async fn claim_withdraw(
233    amount: U256,
234    from: Address,
235    from_pk: SecretKey,
236    eth_client: &EthClient,
237    message_proof: &L1MessageProof,
238) -> Result<H256, EthClientError> {
239    println!("Claiming {amount} from bridge to {from:#x}");
240
241    const CLAIM_WITHDRAWAL_SIGNATURE: &str = "claimWithdrawal(uint256,uint256,uint256,bytes32[])";
242
243    let calldata_values = vec![
244        Value::Uint(amount),
245        Value::Uint(message_proof.batch_number.into()),
246        Value::Uint(message_proof.message_id),
247        Value::Array(
248            message_proof
249                .merkle_proof
250                .iter()
251                .map(|hash| Value::FixedBytes(hash.as_fixed_bytes().to_vec().into()))
252                .collect(),
253        ),
254    ];
255
256    let claim_withdrawal_data = encode_calldata(CLAIM_WITHDRAWAL_SIGNATURE, &calldata_values)?;
257
258    println!(
259        "Claiming withdrawal with calldata: {}",
260        hex::encode(&claim_withdrawal_data)
261    );
262
263    let claim_tx = build_generic_tx(
264        eth_client,
265        TxType::EIP1559,
266        bridge_address().map_err(|err| EthClientError::Custom(err.to_string()))?,
267        from,
268        claim_withdrawal_data.into(),
269        Overrides {
270            from: Some(from),
271            ..Default::default()
272        },
273    )
274    .await?;
275
276    let signer = LocalSigner::new(from_pk).into();
277
278    send_generic_transaction(eth_client, claim_tx, &signer).await
279}
280
281pub async fn claim_erc20withdraw(
282    token_l1: Address,
283    token_l2: Address,
284    amount: U256,
285    from_signer: &Signer,
286    eth_client: &EthClient,
287    message_proof: &L1MessageProof,
288) -> Result<H256, EthClientError> {
289    let from = from_signer.address();
290    const CLAIM_WITHDRAWAL_ERC20_SIGNATURE: &str =
291        "claimWithdrawalERC20(address,address,uint256,uint256,uint256,bytes32[])";
292
293    let calldata_values = vec![
294        Value::Address(token_l1),
295        Value::Address(token_l2),
296        Value::Uint(amount),
297        Value::Uint(U256::from(message_proof.batch_number)),
298        Value::Uint(message_proof.message_id),
299        Value::Array(
300            message_proof
301                .merkle_proof
302                .iter()
303                .map(|v| Value::Uint(U256::from_big_endian(v.as_bytes())))
304                .collect(),
305        ),
306    ];
307
308    let claim_withdrawal_data =
309        encode_calldata(CLAIM_WITHDRAWAL_ERC20_SIGNATURE, &calldata_values)?;
310
311    println!(
312        "Claiming withdrawal with calldata: {}",
313        hex::encode(&claim_withdrawal_data)
314    );
315
316    let claim_tx = build_generic_tx(
317        eth_client,
318        TxType::EIP1559,
319        bridge_address().map_err(|err| EthClientError::Custom(err.to_string()))?,
320        from,
321        claim_withdrawal_data.into(),
322        Overrides {
323            from: Some(from),
324            ..Default::default()
325        },
326    )
327    .await?;
328
329    send_generic_transaction(eth_client, claim_tx, from_signer).await
330}
331
332pub async fn deposit_erc20(
333    token_l1: Address,
334    token_l2: Address,
335    amount: U256,
336    from: Address,
337    from_signer: &Signer,
338    eth_client: &EthClient,
339) -> Result<H256, EthClientError> {
340    println!("Claiming {amount} from bridge to {from:#x}");
341
342    const DEPOSIT_ERC20_SIGNATURE: &str = "depositERC20(address,address,address,uint256)";
343
344    let calldata_values = vec![
345        Value::Address(token_l1),
346        Value::Address(token_l2),
347        Value::Address(from),
348        Value::Uint(amount),
349    ];
350
351    let deposit_data = encode_calldata(DEPOSIT_ERC20_SIGNATURE, &calldata_values)?;
352
353    let mut deposit_tx = build_generic_tx(
354        eth_client,
355        TxType::EIP1559,
356        bridge_address().map_err(|err| EthClientError::Custom(err.to_string()))?,
357        from,
358        deposit_data.into(),
359        Overrides {
360            from: Some(from),
361            ..Default::default()
362        },
363    )
364    .await?;
365
366    deposit_tx.gas = deposit_tx.gas.map(|gas| gas * 2); // tx reverts in some cases otherwise
367
368    send_generic_transaction(eth_client, deposit_tx, from_signer).await
369}
370
371pub fn secret_key_deserializer<'de, D>(deserializer: D) -> Result<SecretKey, D::Error>
372where
373    D: Deserializer<'de>,
374{
375    let hex = H256::deserialize(deserializer)?;
376    SecretKey::from_slice(hex.as_bytes()).map_err(serde::de::Error::custom)
377}
378
379pub fn secret_key_serializer<S>(secret_key: &SecretKey, serializer: S) -> Result<S::Ok, S::Error>
380where
381    S: Serializer,
382{
383    let hex = H256::from_slice(&secret_key.secret_bytes());
384    hex.serialize(serializer)
385}
386// https://github.com/Arachnid/deterministic-deployment-proxy
387// 0x4e59b44847b379578588920cA78FbF26c0B4956C
388pub const DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS: Address = H160([
389    0x4e, 0x59, 0xb4, 0x48, 0x47, 0xb3, 0x79, 0x57, 0x85, 0x88, 0x92, 0x0c, 0xa7, 0x8f, 0xbf, 0x26,
390    0xc0, 0xb4, 0x95, 0x6c,
391]);
392
393// https://github.com/safe-global/safe-singleton-factory
394// 0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7
395pub const SAFE_SINGLETON_FACTORY_ADDRESS: Address = H160([
396    0x91, 0x4d, 0x7F, 0xec, 0x6a, 0xac, 0x8c, 0xd5, 0x42, 0xe7, 0x2b, 0xca, 0x78, 0xb3, 0x06, 0x50,
397    0xd4, 0x56, 0x43, 0xd7,
398]);
399
400// https://github.com/pcaversaccio/create2deployer
401// 0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2
402pub const CREATE2DEPLOYER_ADDRESS: Address = H160([
403    0x13, 0xb0, 0xd8, 0x5c, 0xcb, 0x8b, 0xf8, 0x60, 0xb6, 0xb7, 0x9a, 0xf3, 0x02, 0x9f, 0xca, 0x08,
404    0x1a, 0xe9, 0xbe, 0xf2,
405]);
406
407#[derive(Default, Clone)]
408pub struct ProxyDeployment {
409    pub proxy_address: Address,
410    pub proxy_tx_hash: H256,
411    pub implementation_address: Address,
412    pub implementation_tx_hash: H256,
413}
414
415#[derive(Debug, thiserror::Error)]
416pub enum DeployError {
417    #[error("Failed to decode init code: {0}")]
418    FailedToReadInitCode(#[from] std::io::Error),
419    #[error("Failed to decode init code: {0}")]
420    FailedToDecodeBytecode(#[from] hex::FromHexError),
421    #[error("Failed to deploy contract: {0}")]
422    FailedToDeploy(#[from] EthClientError),
423    #[error("Proxy bytecode not found. Make sure to compile the sdk with `COMPILE_CONTRACTS` set.")]
424    ProxyBytecodeNotFound,
425}
426
427pub async fn create_deploy(
428    client: &EthClient,
429    deployer: &Signer,
430    init_code: Bytes,
431    overrides: Overrides,
432) -> Result<(H256, Address), EthClientError> {
433    let mut deploy_overrides = overrides;
434    deploy_overrides.to = Some(TxKind::Create);
435
436    let deploy_tx = build_generic_tx(
437        client,
438        TxType::EIP1559,
439        Address::zero(),
440        deployer.address(),
441        init_code,
442        deploy_overrides,
443    )
444    .await?;
445    let deploy_tx_hash = send_generic_transaction(client, deploy_tx, deployer).await?;
446
447    let receipt = wait_for_transaction_receipt(deploy_tx_hash, client, 1000).await?;
448
449    let deployed_address = receipt.tx_info.contract_address.ok_or_else(|| {
450        EthClientError::Custom("Deploy transaction did not create a contract".to_owned())
451    })?;
452
453    Ok((deploy_tx_hash, deployed_address))
454}
455
456pub async fn create2_deploy_from_path(
457    constructor_args: &[u8],
458    contract_path: &Path,
459    deployer: &Signer,
460    salt: &[u8],
461    eth_client: &EthClient,
462) -> Result<(H256, Address), DeployError> {
463    let bytecode_hex = read_to_string(contract_path)?;
464    let bytecode = hex::decode(bytecode_hex.trim_start_matches("0x").trim())?;
465    create2_deploy_from_bytecode(constructor_args, &bytecode, deployer, salt, eth_client).await
466}
467
468pub async fn create2_deploy_from_bytecode(
469    constructor_args: &[u8],
470    bytecode: &[u8],
471    deployer: &Signer,
472    salt: &[u8],
473    eth_client: &EthClient,
474) -> Result<(H256, Address), DeployError> {
475    let (deploy_tx_hash, contract_address) = create2_deploy_from_bytecode_no_wait(
476        constructor_args,
477        bytecode,
478        deployer,
479        salt,
480        eth_client,
481        Overrides::default(),
482    )
483    .await?;
484    wait_for_transaction_receipt(deploy_tx_hash, eth_client, 10).await?;
485    Ok((deploy_tx_hash, contract_address))
486}
487
488pub async fn create2_deploy_from_bytecode_no_wait(
489    constructor_args: &[u8],
490    bytecode: &[u8],
491    deployer: &Signer,
492    salt: &[u8],
493    eth_client: &EthClient,
494    overrides: Overrides,
495) -> Result<(H256, Address), DeployError> {
496    let init_code = [bytecode, constructor_args].concat();
497
498    let (deploy_tx_hash, contract_address) =
499        create2_deploy_no_wait(salt, &init_code, deployer, eth_client, overrides).await?;
500
501    Ok((deploy_tx_hash, contract_address))
502}
503
504/// Builds the init code for deploying a contract behind an OpenZeppelin `ERC1967Proxy`.
505fn build_proxy_init_code(implementation_address: Address) -> Result<Bytes, DeployError> {
506    #[allow(clippy::const_is_empty)]
507    if ERC1967_PROXY_BYTECODE.is_empty() {
508        return Err(DeployError::ProxyBytecodeNotFound);
509    }
510
511    let mut init_code = ERC1967_PROXY_BYTECODE.to_vec();
512
513    init_code.extend(H256::from(implementation_address).0);
514    init_code.extend(H256::from_low_u64_be(0x40).0);
515    init_code.extend(H256::zero().0);
516
517    Ok(Bytes::from(init_code))
518}
519
520/// Deploys a contract behind an OpenZeppelin's `ERC1967Proxy`.
521/// The embedded `ERC1967_PROXY_BYTECODE` may be empty if the crate was compiled
522/// without the `COMPILE_CONTRACTS` env var set. In that case, this function
523/// will return `DeployError::ProxyBytecodeNotFound`.
524async fn deploy_proxy(
525    deployer: &Signer,
526    eth_client: &EthClient,
527    implementation_address: Address,
528    salt: &[u8],
529) -> Result<(H256, Address), DeployError> {
530    let (tx_hash, address) = deploy_proxy_no_wait(
531        deployer,
532        eth_client,
533        implementation_address,
534        salt,
535        Overrides::default(),
536    )
537    .await?;
538    wait_for_transaction_receipt(tx_hash, eth_client, 10).await?;
539    Ok((tx_hash, address))
540}
541
542/// Same as `deploy_proxy`, but does not wait for the transaction receipt.
543async fn deploy_proxy_no_wait(
544    deployer: &Signer,
545    eth_client: &EthClient,
546    implementation_address: Address,
547    salt: &[u8],
548    overrides: Overrides,
549) -> Result<(H256, Address), DeployError> {
550    let init_code = build_proxy_init_code(implementation_address)?;
551
552    create2_deploy_no_wait(salt, &init_code, deployer, eth_client, overrides)
553        .await
554        .map_err(DeployError::from)
555}
556
557/// Deploys a contract behind an OpenZeppelin's `ERC1967Proxy`.
558pub async fn deploy_with_proxy(
559    deployer: &Signer,
560    eth_client: &EthClient,
561    contract_path: &Path,
562    salt: &[u8],
563) -> Result<ProxyDeployment, DeployError> {
564    let (implementation_tx_hash, implementation_address) =
565        create2_deploy_from_path(&[], contract_path, deployer, salt, eth_client).await?;
566
567    let (proxy_tx_hash, proxy_address) =
568        deploy_proxy(deployer, eth_client, implementation_address, salt).await?;
569
570    Ok(ProxyDeployment {
571        proxy_address,
572        proxy_tx_hash,
573        implementation_address,
574        implementation_tx_hash,
575    })
576}
577
578/// Same as `deploy_with_proxy`, but does not wait for the transaction receipts.
579pub async fn deploy_with_proxy_no_wait(
580    deployer: &Signer,
581    eth_client: &EthClient,
582    contract_path: &Path,
583    salt: &[u8],
584    overrides: Overrides,
585) -> Result<ProxyDeployment, DeployError> {
586    let bytecode_hex = read_to_string(contract_path)?;
587    let bytecode = hex::decode(bytecode_hex.trim_start_matches("0x").trim())?;
588    let (implementation_tx_hash, implementation_address) = create2_deploy_from_bytecode_no_wait(
589        &[],
590        &bytecode,
591        deployer,
592        salt,
593        eth_client,
594        overrides.clone(),
595    )
596    .await?;
597
598    let (proxy_tx_hash, proxy_address) = deploy_proxy_no_wait(
599        deployer,
600        eth_client,
601        implementation_address,
602        salt,
603        Overrides {
604            nonce: overrides.nonce.map(|nonce| nonce + 1),
605            ..overrides
606        },
607    )
608    .await?;
609
610    Ok(ProxyDeployment {
611        proxy_address,
612        proxy_tx_hash,
613        implementation_address,
614        implementation_tx_hash,
615    })
616}
617
618/// Same as `deploy_with_proxy`, but takes the contract bytecode directly instead of a path.
619pub async fn deploy_with_proxy_from_bytecode(
620    deployer: &Signer,
621    eth_client: &EthClient,
622    bytecode: &[u8],
623    salt: &[u8],
624) -> Result<ProxyDeployment, DeployError> {
625    let (implementation_tx_hash, implementation_address) =
626        create2_deploy_from_bytecode(&[], bytecode, deployer, salt, eth_client).await?;
627
628    let (proxy_tx_hash, proxy_address) =
629        deploy_proxy(deployer, eth_client, implementation_address, salt).await?;
630
631    Ok(ProxyDeployment {
632        proxy_address,
633        proxy_tx_hash,
634        implementation_address,
635        implementation_tx_hash,
636    })
637}
638
639/// Same as `deploy_with_proxy_from_bytecode`, but does not wait for the transaction receipts.
640pub async fn deploy_with_proxy_from_bytecode_no_wait(
641    deployer: &Signer,
642    eth_client: &EthClient,
643    bytecode: &[u8],
644    salt: &[u8],
645    overrides: Overrides,
646) -> Result<ProxyDeployment, DeployError> {
647    let (implementation_tx_hash, implementation_address) = create2_deploy_from_bytecode_no_wait(
648        &[],
649        bytecode,
650        deployer,
651        salt,
652        eth_client,
653        overrides.clone(),
654    )
655    .await?;
656
657    let (proxy_tx_hash, proxy_address) = deploy_proxy_no_wait(
658        deployer,
659        eth_client,
660        implementation_address,
661        salt,
662        Overrides {
663            nonce: overrides.nonce.map(|nonce| nonce + 1),
664            ..overrides
665        },
666    )
667    .await?;
668
669    Ok(ProxyDeployment {
670        proxy_address,
671        proxy_tx_hash,
672        implementation_address,
673        implementation_tx_hash,
674    })
675}
676
677async fn build_create2_deploy_tx(
678    salt: &[u8],
679    init_code: &[u8],
680    deployer: &Signer,
681    eth_client: &EthClient,
682    overrides: Overrides,
683) -> Result<GenericTransaction, EthClientError> {
684    let calldata = [salt, init_code].concat();
685    let gas_price = eth_client
686        .get_gas_price_with_extra(20)
687        .await?
688        .try_into()
689        .map_err(|_| {
690            EthClientError::InternalError("Failed to convert gas_price to a u64".to_owned())
691        })?;
692
693    build_generic_tx(
694        eth_client,
695        TxType::EIP1559,
696        DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS,
697        deployer.address(),
698        calldata.into(),
699        Overrides {
700            max_fee_per_gas: Some(gas_price),
701            max_priority_fee_per_gas: Some(gas_price),
702            ..overrides
703        },
704    )
705    .await
706}
707
708async fn create2_deploy_no_wait(
709    salt: &[u8],
710    init_code: &[u8],
711    deployer: &Signer,
712    eth_client: &EthClient,
713    overrides: Overrides,
714) -> Result<(H256, Address), EthClientError> {
715    let deploy_tx =
716        build_create2_deploy_tx(salt, init_code, deployer, eth_client, overrides).await?;
717
718    let deploy_tx_hash = send_generic_transaction(eth_client, deploy_tx, deployer).await?;
719
720    let deployed_address = create2_address(salt, keccak(init_code));
721    Ok((deploy_tx_hash, deployed_address))
722}
723
724#[allow(clippy::indexing_slicing)]
725fn create2_address(salt: &[u8], init_code_hash: H256) -> Address {
726    Address::from_slice(
727        &keccak(
728            [
729                &[0xff],
730                DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS.as_bytes(),
731                salt,
732                init_code_hash.as_bytes(),
733            ]
734            .concat(),
735        )
736        .as_bytes()[12..],
737    )
738}
739
740pub async fn initialize_contract_no_wait(
741    contract_address: Address,
742    initialize_calldata: Vec<u8>,
743    initializer: &Signer,
744    eth_client: &EthClient,
745    overrides: Overrides,
746) -> Result<H256, EthClientError> {
747    let initialize_tx = build_generic_tx(
748        eth_client,
749        TxType::EIP1559,
750        contract_address,
751        initializer.address(),
752        initialize_calldata.into(),
753        overrides,
754    )
755    .await?;
756
757    let initialize_tx_hash =
758        send_generic_transaction(eth_client, initialize_tx, initializer).await?;
759
760    Ok(initialize_tx_hash)
761}
762
763pub async fn call_contract(
764    client: &EthClient,
765    signer: &Signer,
766    to: Address,
767    signature: &str,
768    parameters: Vec<Value>,
769) -> Result<H256, EthClientError> {
770    let calldata = encode_calldata(signature, &parameters)?.into();
771    let from = signer.address();
772    let tx = build_generic_tx(
773        client,
774        TxType::EIP1559,
775        to,
776        from,
777        calldata,
778        Default::default(),
779    )
780    .await?;
781
782    let tx_hash = send_generic_transaction(client, tx, signer).await?;
783
784    wait_for_transaction_receipt(tx_hash, client, 100).await?;
785    Ok(tx_hash)
786}
787
788pub fn address_to_word(address: Address) -> U256 {
789    let mut word = [0u8; 32];
790    for (word_byte, address_byte) in word.iter_mut().skip(12).zip(address.as_bytes().iter()) {
791        *word_byte = *address_byte;
792    }
793    U256::from_big_endian(&word)
794}
795
796pub fn get_erc1967_slot(name: &str) -> U256 {
797    U256::from_big_endian(&keccak(name).0) - U256::one()
798}
799
800pub fn get_address_alias(address: Address) -> Address {
801    let address = U256::from_big_endian(&address.to_fixed_bytes());
802    let alias = address.add(U256::from_big_endian(&ADDRESS_ALIASING.to_fixed_bytes()));
803    H160::from_slice(&alias.to_big_endian()[12..32])
804}
805
806const WAIT_TIME_FOR_RECEIPT_SECONDS: u64 = 2;
807
808pub async fn send_generic_transaction(
809    client: &EthClient,
810    generic_tx: GenericTransaction,
811    signer: &Signer,
812) -> Result<H256, EthClientError> {
813    let mut encoded_tx = vec![generic_tx.r#type.into()];
814    match generic_tx.r#type {
815        TxType::EIP1559 => {
816            let tx: EIP1559Transaction = generic_tx.try_into()?;
817            let signed_tx = tx
818                .sign(signer)
819                .await
820                .map_err(|err| EthClientError::Custom(err.to_string()))?;
821
822            signed_tx.encode(&mut encoded_tx);
823        }
824        TxType::EIP4844 => {
825            let mut tx: WrappedEIP4844Transaction = generic_tx.try_into()?;
826            tx.tx
827                .sign_inplace(signer)
828                .await
829                .map_err(|err| EthClientError::Custom(err.to_string()))?;
830
831            tx.encode(&mut encoded_tx);
832        }
833        TxType::EIP7702 => {
834            let tx: EIP7702Transaction = generic_tx.try_into()?;
835            let signed_tx = tx
836                .sign(signer)
837                .await
838                .map_err(|err| EthClientError::Custom(err.to_string()))?;
839            signed_tx.encode(&mut encoded_tx);
840        }
841        TxType::FeeToken => {
842            let tx: FeeTokenTransaction = generic_tx.try_into()?;
843            let signed_tx = tx
844                .sign(signer)
845                .await
846                .map_err(|err| EthClientError::Custom(err.to_string()))?;
847
848            signed_tx.encode(&mut encoded_tx);
849        }
850        _ => {
851            return Err(EthClientError::Custom(format!(
852                "Unsupported transaction type: {:?}",
853                generic_tx.r#type
854            )));
855        }
856    };
857
858    client.send_raw_transaction(encoded_tx.as_slice()).await
859}
860
861pub async fn send_tx_bump_gas_exponential_backoff(
862    client: &EthClient,
863    mut tx: GenericTransaction,
864    signer: &Signer,
865) -> Result<H256, EthClientError> {
866    let mut number_of_retries = 0;
867
868    'outer: while number_of_retries < client.max_number_of_retries {
869        if let Some(max_fee_per_gas) = client.maximum_allowed_max_fee_per_gas {
870            let (Some(tx_max_fee), Some(tx_max_priority_fee)) =
871                (&mut tx.max_fee_per_gas, &mut tx.max_priority_fee_per_gas)
872            else {
873                return Err(EthClientError::Custom(
874                    "Invalid transaction: max_fee_per_gas or max_priority_fee_per_gas is missing"
875                        .to_string(),
876                ));
877            };
878
879            if *tx_max_fee > max_fee_per_gas {
880                *tx_max_fee = max_fee_per_gas;
881
882                // Ensure that max_priority_fee_per_gas does not exceed max_fee_per_gas
883                if *tx_max_priority_fee > *tx_max_fee {
884                    *tx_max_priority_fee = *tx_max_fee;
885                }
886
887                warn!(
888                    "max_fee_per_gas exceeds the allowed limit, adjusting it to {max_fee_per_gas}"
889                );
890            }
891        }
892
893        // Check blob gas fees only for EIP4844 transactions
894        if let Some(tx_max_fee_per_blob_gas) = &mut tx.max_fee_per_blob_gas
895            && let Some(max_fee_per_blob_gas) = client.maximum_allowed_max_fee_per_blob_gas
896            && *tx_max_fee_per_blob_gas > U256::from(max_fee_per_blob_gas)
897        {
898            *tx_max_fee_per_blob_gas = U256::from(max_fee_per_blob_gas);
899            warn!(
900                "max_fee_per_blob_gas exceeds the allowed limit, adjusting it to {max_fee_per_blob_gas}"
901            );
902        }
903        let Ok(tx_hash) = send_generic_transaction(client, tx.clone(), signer)
904            .await
905            .inspect_err(|e| {
906                error!(
907                    "Error sending generic transaction {e} attempts [{number_of_retries}/{}]",
908                    client.max_number_of_retries
909                );
910            })
911        else {
912            bump_gas_generic_tx(&mut tx, 30);
913            number_of_retries += 1;
914            continue;
915        };
916
917        if number_of_retries > 0 {
918            warn!(
919                "Resending Transaction after bumping gas, attempts [{number_of_retries}/{}]\nTxHash: {tx_hash:#x}",
920                client.max_number_of_retries
921            );
922        }
923
924        let mut receipt = client.get_transaction_receipt(tx_hash).await?;
925
926        let mut attempt = 1;
927
928        #[allow(clippy::as_conversions)]
929        let attempts_to_wait_in_seconds = client
930            .backoff_factor
931            .pow(number_of_retries as u32)
932            .clamp(client.min_retry_delay, client.max_retry_delay);
933        while receipt.is_none() {
934            if attempt >= (attempts_to_wait_in_seconds / WAIT_TIME_FOR_RECEIPT_SECONDS) {
935                // We waited long enough for the receipt but did not find it, bump gas
936                // and go to the next one.
937                bump_gas_generic_tx(&mut tx, 30);
938
939                number_of_retries += 1;
940                continue 'outer;
941            }
942
943            attempt += 1;
944
945            tokio::time::sleep(std::time::Duration::from_secs(
946                WAIT_TIME_FOR_RECEIPT_SECONDS,
947            ))
948            .await;
949
950            receipt = client.get_transaction_receipt(tx_hash).await?;
951        }
952
953        return Ok(tx_hash);
954    }
955
956    Err(EthClientError::TimeoutError)
957}
958
959fn bump_gas_generic_tx(tx: &mut GenericTransaction, bump_percentage: u64) {
960    if let (Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) =
961        (&mut tx.max_fee_per_gas, &mut tx.max_priority_fee_per_gas)
962    {
963        *max_fee_per_gas = (*max_fee_per_gas * (100 + bump_percentage)) / 100;
964        *max_priority_fee_per_gas = (*max_priority_fee_per_gas * (100 + bump_percentage)) / 100;
965    }
966    if let Some(max_fee_per_blob_gas) = &mut tx.max_fee_per_blob_gas {
967        let factor = 1 + (bump_percentage / 100) * 10;
968        *max_fee_per_blob_gas = max_fee_per_blob_gas
969            .saturating_mul(U256::from(factor))
970            .div(10);
971    }
972}
973
974/// Build a GenericTransaction with the given parameters.
975/// Either `overrides.nonce` or `overrides.from` must be provided.
976/// If `overrides.gas_price`, `overrides.chain_id` or `overrides.gas_price`
977/// are not provided, the client will fetch them from the network.
978/// If `overrides.gas_limit` is not provided, the client will estimate the tx cost.
979pub async fn build_generic_tx(
980    client: &EthClient,
981    r#type: TxType,
982    to: Address,
983    from: Address,
984    calldata: Bytes,
985    overrides: Overrides,
986) -> Result<GenericTransaction, EthClientError> {
987    match r#type {
988        TxType::EIP1559
989        | TxType::EIP4844
990        | TxType::EIP7702
991        | TxType::Privileged
992        | TxType::FeeToken => {}
993        TxType::EIP2930 | TxType::Legacy => {
994            return Err(EthClientError::Custom(
995                "Unsupported tx type in build_generic_tx".to_owned(),
996            ));
997        }
998    }
999    if overrides.fee_token.is_none() && r#type == TxType::FeeToken {
1000        return Err(EthClientError::Custom(
1001            "fee_token must be set for FeeToken tx type".to_owned(),
1002        ));
1003    };
1004    let mut tx = GenericTransaction {
1005        r#type,
1006        to: overrides.to.clone().unwrap_or(TxKind::Call(to)),
1007        chain_id: Some(if let Some(chain_id) = overrides.chain_id {
1008            chain_id
1009        } else {
1010            client.get_chain_id().await?.try_into().map_err(|_| {
1011                EthClientError::Custom("Failed at get_chain_id().try_into()".to_owned())
1012            })?
1013        }),
1014        nonce: Some(get_nonce_from_overrides_or_rpc(client, &overrides, from).await?),
1015        max_fee_per_gas: Some(
1016            get_fee_from_override_or_get_gas_price(client, overrides.max_fee_per_gas).await?,
1017        ),
1018        max_priority_fee_per_gas: Some(
1019            priority_fee_from_override_or_rpc(client, overrides.max_priority_fee_per_gas).await?,
1020        ),
1021        max_fee_per_blob_gas: overrides.gas_price_per_blob,
1022        value: overrides.value.unwrap_or_default(),
1023        input: calldata,
1024        access_list: overrides
1025            .access_list
1026            .iter()
1027            .map(AccessListEntry::from)
1028            .collect(),
1029        fee_token: overrides.fee_token,
1030        from,
1031        wrapper_version: overrides.wrapper_version,
1032        authorization_list: overrides.authorization_list,
1033        ..Default::default()
1034    };
1035    tx.gas_price = U256::from(tx.max_fee_per_gas.unwrap_or_default());
1036    if let Some(blobs_bundle) = &overrides.blobs_bundle {
1037        tx.blob_versioned_hashes = blobs_bundle.generate_versioned_hashes();
1038        add_blobs_to_generic_tx(&mut tx, blobs_bundle);
1039    }
1040    tx.gas = Some(match overrides.gas_limit {
1041        Some(gas) => gas,
1042        None => client.estimate_gas(tx.clone()).await?,
1043    });
1044
1045    Ok(tx)
1046}
1047
1048pub fn add_blobs_to_generic_tx(tx: &mut GenericTransaction, bundle: &BlobsBundle) {
1049    tx.blobs = bundle
1050        .blobs
1051        .iter()
1052        .map(|blob| Bytes::copy_from_slice(blob))
1053        .collect()
1054}
1055
1056async fn get_nonce_from_overrides_or_rpc(
1057    client: &EthClient,
1058    overrides: &Overrides,
1059    address: Address,
1060) -> Result<u64, EthClientError> {
1061    if let Some(nonce) = overrides.nonce {
1062        return Ok(nonce);
1063    }
1064    client
1065        .get_nonce(address, BlockIdentifier::Tag(BlockTag::Latest))
1066        .await
1067}
1068
1069async fn get_fee_from_override_or_get_gas_price(
1070    client: &EthClient,
1071    maybe_gas_fee: Option<u64>,
1072) -> Result<u64, EthClientError> {
1073    if let Some(gas_fee) = maybe_gas_fee {
1074        return Ok(gas_fee);
1075    }
1076    client
1077        .get_gas_price()
1078        .await?
1079        .try_into()
1080        .map_err(|_| EthClientError::Custom("Failed to get gas for fee".to_owned()))
1081}
1082
1083async fn priority_fee_from_override_or_rpc(
1084    client: &EthClient,
1085    maybe_priority_fee: Option<u64>,
1086) -> Result<u64, EthClientError> {
1087    if let Some(priority_fee) = maybe_priority_fee {
1088        return Ok(priority_fee);
1089    }
1090
1091    if let Ok(priority_fee) = client.get_max_priority_fee().await
1092        && let Ok(priority_fee_u64) = priority_fee.try_into()
1093    {
1094        return Ok(priority_fee_u64);
1095    }
1096
1097    get_fee_from_override_or_get_gas_price(client, None).await
1098}
1099
1100pub async fn wait_for_l1_message_proof(
1101    client: &EthClient,
1102    transaction_hash: H256,
1103    max_retries: u64,
1104) -> Result<Vec<L1MessageProof>, EthClientError> {
1105    let mut message_proof = get_l1_message_proof(client, transaction_hash).await?;
1106    let mut r#try = 1;
1107    while message_proof.is_none() {
1108        println!(
1109            "[{try}/{max_retries}] Retrying to get message proof for tx {transaction_hash:#x}"
1110        );
1111
1112        if max_retries == r#try {
1113            return Err(EthClientError::Custom(format!(
1114                "L1Message proof for tx {transaction_hash:#x} not found after {max_retries} retries"
1115            )));
1116        }
1117        r#try += 1;
1118
1119        tokio::time::sleep(std::time::Duration::from_secs(2)).await;
1120
1121        message_proof = get_l1_message_proof(client, transaction_hash).await?;
1122    }
1123    message_proof.ok_or(EthClientError::Custom("L1Message proof is None".to_owned()))
1124}
1125
1126pub async fn get_last_committed_batch(
1127    client: &EthClient,
1128    on_chain_proposer_address: Address,
1129) -> Result<u64, EthClientError> {
1130    _call_u64_variable(client, b"lastCommittedBatch()", on_chain_proposer_address).await
1131}
1132
1133pub async fn get_last_verified_batch(
1134    client: &EthClient,
1135    on_chain_proposer_address: Address,
1136) -> Result<u64, EthClientError> {
1137    _call_u64_variable(client, b"lastVerifiedBatch()", on_chain_proposer_address).await
1138}
1139
1140/// Gets the SP1 verification key for a specific batch from the verificationKeys mapping.
1141/// This fetches the commit hash from batchCommitments and then gets the VK.
1142pub async fn get_sp1_vk_for_batch(
1143    client: &EthClient,
1144    on_chain_proposer_address: Address,
1145    batch_number: u64,
1146) -> Result<[u8; 32], EthClientError> {
1147    get_verification_key_for_batch(
1148        client,
1149        on_chain_proposer_address,
1150        batch_number,
1151        SP1_VERIFIER_ID,
1152    )
1153    .await
1154}
1155
1156/// Gets the RISC0 verification key for a specific batch from the verificationKeys mapping.
1157/// This fetches the commit hash from batchCommitments and then gets the VK.
1158pub async fn get_risc0_vk_for_batch(
1159    client: &EthClient,
1160    on_chain_proposer_address: Address,
1161    batch_number: u64,
1162) -> Result<[u8; 32], EthClientError> {
1163    get_verification_key_for_batch(
1164        client,
1165        on_chain_proposer_address,
1166        batch_number,
1167        RISC0_VERIFIER_ID,
1168    )
1169    .await
1170}
1171
1172/// Gets the verification key from the verificationKeys mapping for a given batch.
1173/// First fetches the commit hash from batchCommitments, then uses it to get the VK.
1174async fn get_verification_key_for_batch(
1175    client: &EthClient,
1176    on_chain_proposer_address: Address,
1177    batch_number: u64,
1178    verifier_id: u8,
1179) -> Result<[u8; 32], EthClientError> {
1180    let commit_hash =
1181        get_batch_commit_hash(client, on_chain_proposer_address, batch_number).await?;
1182    get_verification_key(client, on_chain_proposer_address, commit_hash, verifier_id).await
1183}
1184
1185/// Gets the commit hash for a batch from the batchCommitments mapping.
1186pub async fn get_batch_commit_hash(
1187    client: &EthClient,
1188    on_chain_proposer_address: Address,
1189    batch_number: u64,
1190) -> Result<[u8; 32], EthClientError> {
1191    // batchCommitments(uint256) returns the BatchCommitmentInfo struct
1192    // The auto-generated getter returns: (newStateRoot, blobKZGVersionedHash,
1193    // processedPrivilegedTransactionsRollingHash, withdrawalsLogsMerkleRoot, lastBlockHash,
1194    // nonPrivilegedTransactions, commitHash) - arrays are not included
1195    let values = vec![Value::Uint(U256::from(batch_number))];
1196    let calldata = encode_calldata("batchCommitments(uint256)", &values)?;
1197
1198    let response = client
1199        .call(
1200            on_chain_proposer_address,
1201            calldata.into(),
1202            Default::default(),
1203        )
1204        .await?;
1205
1206    // The response contains multiple 32-byte values. commitHash is at index 6 (7th value).
1207    // Each value is 32 bytes (64 hex chars), so commitHash starts at offset 6*64 = 384
1208    let hex = response.strip_prefix("0x").ok_or(EthClientError::Custom(
1209        "Couldn't strip '0x' prefix from response".to_owned(),
1210    ))?;
1211
1212    // commitHash is the 7th field (index 6), at offset 6*64 = 384, length 64
1213    let commit_hash_hex = hex.get(384..448).ok_or(EthClientError::Custom(
1214        "Response too short to contain commitHash".to_owned(),
1215    ))?;
1216
1217    let bytes = hex::decode(commit_hash_hex)
1218        .map_err(|e| EthClientError::Custom(format!("Failed to decode commit hash hex: {e}")))?;
1219
1220    let arr: [u8; 32] = bytes.try_into().map_err(|_| {
1221        EthClientError::Custom("Failed to convert commit hash bytes to [u8; 32]".to_owned())
1222    })?;
1223
1224    Ok(arr)
1225}
1226
1227/// Gets the verification key from the verificationKeys(commitHash, verifierId) mapping.
1228pub async fn get_verification_key(
1229    client: &EthClient,
1230    on_chain_proposer_address: Address,
1231    commit_hash: [u8; 32],
1232    verifier_id: u8,
1233) -> Result<[u8; 32], EthClientError> {
1234    let values = vec![
1235        Value::FixedBytes(commit_hash.to_vec().into()),
1236        Value::Uint(U256::from(verifier_id)),
1237    ];
1238    let calldata = encode_calldata("verificationKeys(bytes32,uint8)", &values)?;
1239
1240    let response = client
1241        .call(
1242            on_chain_proposer_address,
1243            calldata.into(),
1244            Default::default(),
1245        )
1246        .await?;
1247
1248    let hex = response.strip_prefix("0x").ok_or(EthClientError::Custom(
1249        "Couldn't strip '0x' prefix from response".to_owned(),
1250    ))?;
1251
1252    let bytes = hex::decode(hex)
1253        .map_err(|e| EthClientError::Custom(format!("Failed to decode VK hex: {e}")))?;
1254
1255    let arr: [u8; 32] = bytes
1256        .try_into()
1257        .map_err(|_| EthClientError::Custom("Failed to convert VK bytes to [u8; 32]".to_owned()))?;
1258
1259    Ok(arr)
1260}
1261
1262pub async fn get_last_fetched_l1_block(
1263    client: &EthClient,
1264    common_bridge_address: Address,
1265) -> Result<u64, EthClientError> {
1266    _call_u64_variable(client, b"lastFetchedL1Block()", common_bridge_address).await
1267}
1268
1269pub async fn get_l2_gas_limit(
1270    client: &EthClient,
1271    common_bridge_address: Address,
1272) -> Result<u64, EthClientError> {
1273    _call_u64_variable(client, b"l2GasLimit()", common_bridge_address).await
1274}
1275
1276pub async fn get_pending_l1_messages(
1277    client: &EthClient,
1278    common_bridge_address: Address,
1279) -> Result<Vec<H256>, EthClientError> {
1280    let response = _generic_call(
1281        client,
1282        b"getPendingTransactionHashes()",
1283        common_bridge_address,
1284    )
1285    .await?;
1286    from_hex_string_to_h256_array(&response)
1287}
1288
1289pub async fn get_pending_l2_messages(
1290    client: &EthClient,
1291    common_bridge_address: Address,
1292    chain_id: u64,
1293) -> Result<Vec<H256>, EthClientError> {
1294    let selector = keccak(b"getPendingL2MessagesHashes(uint256)")
1295        .as_bytes()
1296        .get(..4)
1297        .ok_or(EthClientError::Custom("Failed to get selector.".to_owned()))?
1298        .to_vec();
1299
1300    let mut calldata = Vec::new();
1301    calldata.extend_from_slice(&selector);
1302    calldata.extend_from_slice(&U256::from(chain_id).to_big_endian());
1303
1304    let response = client
1305        .call(common_bridge_address, calldata.into(), Overrides::default())
1306        .await?;
1307
1308    from_hex_string_to_h256_array(&response)
1309}
1310
1311// TODO: This is a work around for now, issue: https://github.com/lambdaclass/ethrex/issues/4828
1312pub async fn get_l1_active_fork(
1313    client: &EthClient,
1314    activation_time: Option<u64>,
1315) -> Result<Fork, EthClientError> {
1316    let Some(osaka_activation_time) = activation_time else {
1317        return Ok(Fork::Osaka);
1318    };
1319    let current_timestamp = client
1320        .get_block_by_number(BlockIdentifier::Tag(BlockTag::Latest), false)
1321        .await?
1322        .header
1323        .timestamp;
1324    if current_timestamp < osaka_activation_time {
1325        Ok(Fork::Prague)
1326    } else {
1327        Ok(Fork::Osaka)
1328    }
1329}
1330
1331async fn _generic_call(
1332    client: &EthClient,
1333    selector: &[u8],
1334    contract_address: Address,
1335) -> Result<String, EthClientError> {
1336    let selector = keccak(selector)
1337        .as_bytes()
1338        .get(..4)
1339        .ok_or(EthClientError::Custom("Failed to get selector.".to_owned()))?
1340        .to_vec();
1341    let hex_string = client
1342        .call(contract_address, selector.into(), Overrides::default())
1343        .await?;
1344
1345    Ok(hex_string)
1346}
1347
1348async fn _call_u64_variable(
1349    client: &EthClient,
1350    selector: &[u8],
1351    contract_address: Address,
1352) -> Result<u64, EthClientError> {
1353    let hex_string = _generic_call(client, selector, contract_address).await?;
1354
1355    let value = U256::from_str_radix(hex_string.trim_start_matches("0x"), 16)?
1356        .try_into()
1357        .map_err(|_| {
1358            EthClientError::Custom("Failed to convert from_hex_string_to_u256()".to_owned())
1359        })?;
1360
1361    Ok(value)
1362}
1363
1364async fn _call_address_variable(
1365    eth_client: &EthClient,
1366    selector: &[u8],
1367    on_chain_proposer_address: Address,
1368) -> Result<Address, EthClientError> {
1369    let hex_string = _generic_call(eth_client, selector, on_chain_proposer_address).await?;
1370
1371    let hex_str = &hex_string.strip_prefix("0x").ok_or(EthClientError::Custom(
1372        "Couldn't strip prefix from request.".to_owned(),
1373    ))?[24..]; // Get the needed bytes
1374
1375    let value = Address::from_str(hex_str)
1376        .map_err(|_| EthClientError::Custom("Failed to convert from_str()".to_owned()))?;
1377    Ok(value)
1378}
1379
1380async fn _call_bytes32_variable(
1381    client: &EthClient,
1382    selector: &[u8],
1383    contract_address: Address,
1384) -> Result<[u8; 32], EthClientError> {
1385    let hex_string = _generic_call(client, selector, contract_address).await?;
1386
1387    let hex = hex_string.strip_prefix("0x").ok_or(EthClientError::Custom(
1388        "Couldn't strip '0x' prefix from hex string".to_owned(),
1389    ))?;
1390
1391    let bytes = hex::decode(hex)
1392        .map_err(|e| EthClientError::Custom(format!("Failed to decode hex string: {e}")))?;
1393
1394    let arr: [u8; 32] = bytes
1395        .try_into()
1396        .map_err(|_| EthClientError::Custom("Failed to convert bytes to [u8; 32]".to_owned()))?;
1397
1398    Ok(arr)
1399}
1400
1401pub async fn get_fee_token_ratio(
1402    fee_token: &Address,
1403    client: &EthClient,
1404) -> Result<u64, EthClientError> {
1405    let values = vec![Value::Address(*fee_token)];
1406    let calldata = encode_calldata("getFeeTokenRatio(address)", &values)?;
1407
1408    let ratio = client
1409        .call(
1410            FEE_TOKEN_PRICER_ADDRESS,
1411            calldata.into(),
1412            Default::default(),
1413        )
1414        .await?;
1415
1416    let ratio: u64 = ratio
1417        .trim_start_matches("0x")
1418        .parse::<u64>()
1419        .map_err(|e| EthClientError::Custom(format!("Failed to parse ratio to u64: {}", e)))?;
1420
1421    Ok(ratio)
1422}
1423pub async fn register_fee_token_no_wait(
1424    client: &EthClient,
1425    bridge_address: Address,
1426    fee_token: Address,
1427    signer: &Signer,
1428    overrides: Overrides,
1429) -> Result<H256, EthClientError> {
1430    let calldata = encode_calldata(REGISTER_FEE_TOKEN_SIGNATURE, &[Value::Address(fee_token)])?;
1431
1432    let tx_register = build_generic_tx(
1433        client,
1434        TxType::EIP1559,
1435        bridge_address,
1436        signer.address(),
1437        calldata.into(),
1438        overrides,
1439    )
1440    .await?;
1441
1442    send_generic_transaction(client, tx_register, signer).await
1443}
1444
1445pub async fn wait_for_l2_deposit_receipt(
1446    l1_rpc_receipt: &RpcReceipt,
1447    l1_client: &EthClient,
1448    l2_client: &EthClient,
1449) -> Result<RpcReceipt, EthClientError> {
1450    let data = l1_rpc_receipt
1451        .logs
1452        .iter()
1453        .find_map(|log| PrivilegedTransactionData::from_log(log.log.clone()).ok())
1454        .ok_or_else(|| {
1455            EthClientError::Custom(format!(
1456                "RpcReceipt for transaction {:?} contains no valid logs",
1457                l1_rpc_receipt.tx_info.transaction_hash
1458            ))
1459        })?;
1460
1461    let l2_deposit_tx_hash = data
1462        .into_tx(
1463            l1_client,
1464            l2_client
1465                .get_chain_id()
1466                .await?
1467                .try_into()
1468                .map_err(|e| EthClientError::Custom(format!("Invalid chain id: {e}")))?,
1469            0,
1470        )
1471        .await?
1472        .get_privileged_hash()
1473        .ok_or_else(|| EthClientError::Custom("Empty transaction hash".to_owned()))?;
1474
1475    wait_for_transaction_receipt(l2_deposit_tx_hash, l2_client, 10000).await
1476}