├── .github └── workflows │ ├── ci.yaml │ └── crates-io.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── clippy.toml ├── envrc.example ├── examples ├── contract-deployment │ ├── Greeter.abi │ ├── Greeter.bin │ ├── Greeter.sol │ └── main.rs ├── paymaster │ └── main.rs └── simple_payment │ └── main.rs ├── resources └── testing │ └── erc20 │ ├── ERC20Token.json │ ├── MyToken.json │ └── abi.json └── src ├── abi ├── ContractDeployer.json ├── IL1Bridge.json ├── IZkSync.json ├── mod.rs └── test_contracts │ ├── basic_combined.json │ ├── counter_combined.json │ ├── greeter_combined.json │ ├── import_combined.json │ └── storage_combined.json ├── contracts ├── l1_bridge_contract.rs ├── main_contract.rs └── mod.rs ├── eip712 ├── meta.rs ├── mod.rs ├── paymaster_params.rs ├── transaction.rs └── transaction_request.rs ├── lib.rs ├── tests ├── mod.rs ├── provider_tests.rs ├── utils.rs └── wallet_tests.rs ├── zks_provider ├── mod.rs └── types.rs ├── zks_utils.rs └── zks_wallet ├── DepositERC20GasLimit.json ├── errors.rs ├── mod.rs ├── requests ├── call_request.rs ├── deploy_request.rs ├── deposit_request.rs ├── mod.rs ├── transfer_request.rs └── withdraw_request.rs └── wallet.rs /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: ["*"] 7 | 8 | concurrency: 9 | group: ${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | build: 14 | name: Build 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout sources 18 | uses: actions/checkout@v3 19 | 20 | - name: Rustup toolchain install 21 | uses: dtolnay/rust-toolchain@stable 22 | with: 23 | toolchain: stable 24 | 25 | - uses: Swatinem/rust-cache@v2 26 | with: 27 | cache-on-failure: true 28 | 29 | - name: Run cargo check 30 | uses: dtolnay/rust-toolchain@stable 31 | with: 32 | command: check 33 | 34 | format: 35 | name: Format 36 | runs-on: ubuntu-latest 37 | steps: 38 | - name: Checkout sources 39 | uses: actions/checkout@v3 40 | 41 | - uses: Swatinem/rust-cache@v2 42 | with: 43 | cache-on-failure: true 44 | 45 | - name: Install stable toolchain 46 | uses: dtolnay/rust-toolchain@stable 47 | with: 48 | toolchain: stable 49 | components: rustfmt 50 | 51 | - name: Run cargo fmt 52 | run: cargo fmt --all -- --check 53 | 54 | lint: 55 | name: Lint 56 | runs-on: ubuntu-latest 57 | steps: 58 | - name: Checkout sources 59 | uses: actions/checkout@v3 60 | 61 | - uses: Swatinem/rust-cache@v2 62 | with: 63 | cache-on-failure: true 64 | 65 | - name: Install stable toolchain 66 | uses: dtolnay/rust-toolchain@stable 67 | with: 68 | toolchain: stable 69 | components: clippy 70 | 71 | - name: Run clippy 72 | run: make clippy 73 | 74 | doctest: 75 | name: Doctest 76 | runs-on: ubuntu-latest 77 | steps: 78 | - name: Checkout sources 79 | uses: actions/checkout@v3 80 | 81 | - uses: Swatinem/rust-cache@v2 82 | with: 83 | cache-on-failure: true 84 | 85 | - name: Install stable toolchain 86 | uses: dtolnay/rust-toolchain@stable 87 | with: 88 | toolchain: stable 89 | components: clippy 90 | 91 | - name: Run doc tests 92 | run: cargo test --doc 93 | 94 | test: 95 | name: Test 96 | runs-on: ubuntu-latest 97 | env: 98 | CARGO_TERM_COLOR: always 99 | ZKSYNC_WEB3_RS_L1_PROVIDER_URL: http://65.21.140.36:8545 100 | ZKSYNC_WEB3_RS_L2_PROVIDER_URL: http://65.21.140.36:3050 101 | steps: 102 | - uses: actions/checkout@v3 103 | 104 | - uses: Swatinem/rust-cache@v2 105 | with: 106 | cache-on-failure: true 107 | 108 | - uses: dtolnay/rust-toolchain@stable 109 | with: 110 | toolchain: stable 111 | components: clippy 112 | 113 | - name: Install cargo-llvm-cov 114 | uses: taiki-e/install-action@cargo-llvm-cov 115 | 116 | - name: Run tests and generate code coverage 117 | run: make coverage 118 | 119 | - name: Upload coverage to Codecov 120 | uses: codecov/codecov-action@v3 121 | with: 122 | token: ${{ secrets.CODECOV_TOKEN }} 123 | files: lcov.info 124 | fail_ci_if_error: true 125 | -------------------------------------------------------------------------------- /.github/workflows/crates-io.yaml: -------------------------------------------------------------------------------- 1 | name: Publish to crates.io 2 | 3 | on: 4 | push: 5 | tags: # Pattern matched against refs/tags 6 | - '*' # Push events to every tag not containing / 7 | 8 | jobs: 9 | 10 | publish: 11 | name: Publish to crates.io 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Checkout sources 16 | uses: actions/checkout@v3 17 | 18 | - name: Rustup toolchain install 19 | uses: dtolnay/rust-toolchain@stable 20 | with: 21 | toolchain: stable 22 | 23 | - run: cargo publish --token ${CRATES_IO_TOKEN} 24 | env: 25 | CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | # Project environment config. See `envrc.example`. 13 | .envrc 14 | 15 | .vscode/settings.json 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zksync-web3-rs" 3 | version = "0.2.1" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Rust SDK for zkSync Era" 7 | repository = "https://github.com/lambdaclass/zksync-web3-rs/" 8 | homepage = "https://github.com/lambdaclass/zksync-web3-rs/" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | ethers = { version = "2.0.4", features = ["rustls"] } 14 | ethers-contract = "2.0.7" 15 | clap = { version = "4.2.7", features = ["derive"] } 16 | lazy_static = "1.4.0" 17 | 18 | # Async 19 | tokio = { version = "1", features = ["macros", "process"] } 20 | async-trait = "0.1.68" 21 | 22 | # Serialization 23 | serde = "1.0.163" 24 | serde_json = { version = "1" } 25 | hex = "0.4" 26 | 27 | # Error handling 28 | thiserror = "1.0.40" # Library error handling 29 | 30 | # Logging 31 | log = "0.4" 32 | env_logger = "0.10" 33 | 34 | # Hash 35 | sha2 = "0.9.5" 36 | anyhow = "1.0.88" 37 | 38 | [features] 39 | ethers-solc = ["ethers/ethers-solc"] 40 | 41 | # Examples 42 | 43 | [[example]] 44 | name = "simple_payment" 45 | path = "examples/simple_payment/main.rs" 46 | 47 | [profile.test] 48 | opt-level = 3 49 | debug-assertions = true 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Lambdaclass 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | format: 2 | cargo fmt --all 3 | 4 | clippy: 5 | cargo clippy --all-targets --all-features -- -D warnings 6 | 7 | coverage: 8 | cargo llvm-cov --lcov --output-path lcov.info -- --test-threads=1 9 | 10 | test: 11 | cargo test -- --test-threads=1 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zksync-web3-rs 2 | 3 | ## Table of Contents 4 | 5 | - [zksync-web3-rs](#zksync-web3-rs) 6 | - [Table of Contents](#table-of-contents) 7 | - [Getting Started with zkSync Web3 SDK](#getting-started-with-zksync-web3-sdk) 8 | - [Prerequisites](#prerequisites) 9 | - [Adding dependencies](#adding-dependencies) 10 | - [First steps](#first-steps) 11 | - [Importing dependencies](#importing-dependencies) 12 | - [Connecting to the zkSync Network](#connecting-to-the-zksync-network) 13 | - [Creating a ZK-Wallet](#creating-a-zk-wallet) 14 | - [Creating a Payment Transaction](#creating-a-payment-transaction) 15 | - [Sending the Transaction](#sending-the-transaction) 16 | - [Checking zkSync account balance](#checking-zksync-account-balance) 17 | - [Simple Transfer Example](#simple-transfer-example) 18 | - [Clone the Repository](#clone-the-repository) 19 | - [Run a zkSync localnet](#run-a-zksync-localnet) 20 | - [Run the Simple Transfer Example](#run-the-simple-transfer-example) 21 | - [Conclusion](#conclusion) 22 | 23 | ## Getting Started with zkSync Web3 SDK 24 | 25 | While most of the existing SDKs should work out of the box, deploying smart contracts or using unique zkSync features, like account abstraction, requires providing additional fields to those that Ethereum transactions have by default. 26 | 27 | To provide easy access to all of the features of zkSync Era, the `zksync-web3-rs` Rust SDK was created, which is made in a way that has an interface very similar to those of [ethers](https://docs.ethers.io/v5/). In fact, ethers is a peer dependency of our library and most of the objects exported by `zksync-web3-rs` (e.g. `Provider` etc.) inherit from the corresponding `ethers` objects and override only the fields that need to be changed. 28 | 29 | The library is made in such a way that after replacing `ethers` with `zksync-web3-rs` most client apps will work out of box. 30 | 31 | ### Prerequisites 32 | 33 | Before you begin, make sure you have the following prerequisites: 34 | 35 | - Rust: Ensure that Rust is installed on your system. You can install Rust by following the instructions at [https://www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install). 36 | 37 | - Git: Install Git on your system if you haven't already. You can find installation instructions at [https://git-scm.com/book/en/v2/Getting-Started-Installing-Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). 38 | 39 | ### Adding dependencies 40 | 41 | This crate is published on crates.io, so you can simply do: 42 | 43 | ```bash 44 | cargo add zksync-web3-rs 45 | ``` 46 | 47 | Or, if you want to use the latest changes, add this to your `Cargo.toml` file: 48 | 49 | ```bash 50 | zksync-web3-rs = { git = "https://www.github.com/lambdaclass/zksync-web3-rs", branch = "main"} 51 | ``` 52 | 53 | > Maybe consider adding tokio as dependency since we are using a lot of async/await functions. If this example is meant to be done in the main function the #[tokio::main] annotation is needed. 54 | 55 | ### First steps 56 | 57 | In the following steps, we will show you how to create a payment transaction using the `zksync-web3-rs` library. 58 | 59 | #### Importing dependencies 60 | 61 | Import the `zksync-web3-rs` library into your project by adding the following line to the top of your `main.rs` file: 62 | 63 | ```rust 64 | use zksync_web3_rs as zksync; 65 | ``` 66 | 67 | #### Connecting to the zkSync Network 68 | 69 | To connect to the zkSync network, you need to provide the URL of the zkSync node. The localnet runs both an *Ethereum* node (L1) on port `8545` and an *Era* node (L2) on port `3050`. You can connect to the zkSync Era network using the following code: 70 | 71 | ```rust 72 | use zksync_web3_rs as zksync; 73 | let provider = zksync::prelude::Provider::try_from("http://localhost:3050").unwrap(); 74 | ``` 75 | 76 | #### Creating a ZK-Wallet 77 | 78 | 79 | > We set the chain id to 270 because we are using the zkSync Era node. If you want to use the mainnet, you should set the chain id to 9. 80 | > https://era.zksync.io/docs/tools/hardhat/testing.html#connect-wallet-to-local-nodes 81 | 82 | ```rust 83 | use zksync_web3_rs as zksync; 84 | use zksync::signers::{Wallet, Signer}; 85 | use zksync::core::k256::ecdsa::SigningKey; 86 | let provider = zksync_web3_rs::prelude::Provider::try_from("http://localhost:3050").unwrap(); 87 | let private_key: Wallet = "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110".parse().unwrap(); 88 | let zksync_era_chain_id: u64 = 270; 89 | let wallet = Wallet::with_chain_id(private_key, zksync_era_chain_id); 90 | let zk_wallet = zksync::ZKSWallet::new(wallet, None, Some(provider.clone()), None).unwrap(); 91 | ``` 92 | 93 | #### Creating a Payment Transaction 94 | To create a payment transaction, you need to provide the sender's address, the receiver's address, and the amount to transfer. You can create a payment transaction using the following code: 95 | Notice the await, this code should run inside an async function. 96 | ```rust,no_run 97 | use zksync_web3_rs as zksync; 98 | use zksync::types::Address; 99 | // Use actual addresses here 100 | let receiver_address: Address = "..".parse().unwrap(); 101 | let sender_address: Address = "..".parse().unwrap(); 102 | let amount_to_transfer = zksync_web3_rs::types::U256::from(1); 103 | let payment_request = zksync::zks_wallet::TransferRequest::new(amount_to_transfer) 104 | .to(receiver_address) 105 | .from(sender_address); // Use zk_wallet.l2_address() method to send it from the wallet address. 106 | ``` 107 | 108 | #### Sending the Transaction 109 | 110 | To send the payment transaction, you need to use the wallet and the transfer request. You can send the transaction using the following code: 111 | 112 | > In case you are wondering, the transaction is signed in the `send_transaction` method inside the transfer process. 113 | 114 | ```rust,compile_fail 115 | let payment_transaction_id = 116 | zk_wallet.transfer(&payment_request, None).await.unwrap(); 117 | ``` 118 | 119 | This will send the transaction to the node and return its ID (hash). To get more information about the transaction we can ask for the `TransactionReceipt` with the following lines: 120 | 121 | ```rust,compile_fail 122 | use zksync_web3_rs as zksync; 123 | use zksync::prelude::Middleware; 124 | // Using the local node, keep in mind 125 | // this should be in an async block. 126 | let provider = zksync::prelude::Provider::try_from("http://localhost:3050").unwrap(); 127 | let payment_transaction_receipt = provider 128 | .get_transaction_receipt(payment_transaction_id) 129 | .await 130 | .unwrap() 131 | .unwrap(); 132 | ``` 133 | 134 | #### Checking zkSync account balance 135 | ```rust,compile_fail 136 | let sender_balance = provider 137 | .get_balance(sender_address, None) 138 | .await 139 | .unwrap(); 140 | ``` 141 | 142 | ### Simple Transfer Example 143 | 144 | There's an executable example involving the previous steps in the `examples` folder. To run the example, follow the steps below: 145 | 146 | #### Clone the Repository 147 | 148 | To get started, clone the `zksync-web3-rs` repository from GitHub. Open a terminal or command prompt and execute the following commands: 149 | 150 | ```bash 151 | git clone https://github.com/lambdaclass/zksync-web3-rs.git 152 | cd zksync-web3-rs 153 | ``` 154 | 155 | #### Run a zkSync localnet 156 | 157 | To run the zkSync localnet, clone the `local-setup` repository and execute the following command: 158 | 159 | ```bash 160 | git clone https://github.com/lambdaclass/local-setup 161 | cd local-setup 162 | docker-compose up 163 | ``` 164 | 165 | #### Run the Simple Transfer Example 166 | 167 | To run the payment transaction example using EIP1559 transactions on zkSync Era, run the following command: 168 | 169 | ```bash 170 | cargo run --example simple_payment -- --host --port --amount --from --to --private-key 171 | ``` 172 | 173 | - `HOST`: The IP address or hostname of the node. 174 | - `PORT`: The port number of the node. 175 | - `AMOUNT`: The amount to transfer. 176 | - `SENDER_ADDRESS`: The address of the sender's Ethereum account, represented in hexadecimal format with the `0x` prefix. For example, `0x123abc...`. 177 | - `RECEIVER_ADDRESS`: The address of the receiver's Ethereum account, represented in hexadecimal format with the `0x` prefix. For example, `0x456def...`. 178 | - `PRIVATE_KEY`: The private key of an Ethereum account with sufficient funds to perform the transaction, represented in hexadecimal format with the `0x` prefix. 179 | - `NETWORK`: The network you want to connect to. There are two options: `era` which will connect to the L2 node and `eth` which will connect to the L1 node. 180 | 181 | This command executes the `simple_payment` binary using the provided Makefile. 182 | 183 | ### Conclusion 184 | 185 | Congratulations! You have successfully completed the "Getting Started" guide for the `zksync-web3-rs` SDK. You have learned how to send a simple payment transaction example using EIP1559 transactions on zkSync Era. 186 | 187 | By exploring the example code and the repository's documentation, you can further enhance your understanding of interacting with zkSync Era's testnet using the `zksync-web3-rs` SDK. 188 | 189 | Feel free to experiment with different configurations and explore other functionalities provided by the SDK. 190 | 191 | If you have any questions or need further assistance, don't hesitate to reach out to the repository's community or maintainers. 192 | 193 | Happy coding! 194 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | allow-unwrap-in-tests = true 2 | -------------------------------------------------------------------------------- /envrc.example: -------------------------------------------------------------------------------- 1 | # Copy this to `.envrc` to activate. 2 | 3 | # Sets the L1 endpoint to be using during test phase. 4 | export ZKSYNC_WEB3_RS_L1_PROVIDER_URL='http://localhost:8545' 5 | 6 | # Sets the L2 endpoint to be using during test phase. 7 | export ZKSYNC_WEB3_RS_L2_PROVIDER_URL='http://localhost:3050' 8 | -------------------------------------------------------------------------------- /examples/contract-deployment/Greeter.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "string", 6 | "name": "_greeting", 7 | "type": "string" 8 | } 9 | ], 10 | "stateMutability": "nonpayable", 11 | "type": "constructor" 12 | }, 13 | { 14 | "inputs": [], 15 | "name": "greet", 16 | "outputs": [ 17 | { 18 | "internalType": "string", 19 | "name": "", 20 | "type": "string" 21 | } 22 | ], 23 | "stateMutability": "view", 24 | "type": "function" 25 | }, 26 | { 27 | "inputs": [ 28 | { 29 | "internalType": "string", 30 | "name": "_greeting", 31 | "type": "string" 32 | } 33 | ], 34 | "name": "setGreeting", 35 | "outputs": [], 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /examples/contract-deployment/Greeter.bin: -------------------------------------------------------------------------------- 1 | 0002000000000002000500000000000200010000000103550000006001100270000000830010019d0000008001000039000000400010043f0000000101200190000000a80000c13d0000000001000031000000040110008c000001880000413d0000000101000367000000000101043b000000e001100270000000880210009c000001770000613d000000890110009c000001880000c13d0000000001000416000000000110004c000001880000c13d0000000001000031000000040210008a0000008503000041000000200420008c000000000400001900000000040340190000008502200197000000000520004c000000000300a019000000850220009c00000000020400190000000002036019000000000220004c000001880000c13d00000001020003670000000403200370000000000303043b000000840430009c000001880000213d00000023043000390000008505000041000000000614004b0000000006000019000000000605801900000085011001970000008504400197000000000714004b0000000005008019000000000114013f000000850110009c00000000010600190000000001056019000000000110004c000001880000c13d0000000401300039000000000112034f000000000101043b000000840210009c000001dc0000213d000000bf04100039000000200200008a000000000424016f000000840540009c000001dc0000213d000000400040043f000000800010043f000000240430003900000000034100190000000005000031000000000353004b000001880000213d0000001f0310018f00000001044003670000000505100272000000560000613d00000000060000190000000507600210000000000874034f000000000808043b000000a00770003900000000008704350000000106600039000000000756004b0000004e0000413d000000000630004c000000650000613d0000000505500210000000000454034f0000000303300210000000a005500039000000000605043300000000063601cf000000000636022f000000000404043b0000010003300089000000000434022f00000000033401cf000000000363019f0000000000350435000000a0011000390000000000010435000000800100043d000000840310009c000001dc0000213d000000000400041a000000010340019000000001034002700000007f0530018f00000000030560190000001f0530008c00000000050000190000000105002039000000000454013f0000000104400190000001960000c13d000000200430008c000000870000413d0000001f0410003900000005044002700000008a044000410000008a05000041000000200610008c000000000405401900000000000004350000001f0330003900000005033002700000008a03300041000000000534004b000000870000813d000000000004041b0000000104400039000000000534004b000000830000413d0000001f0310008c000001f30000a13d00000000032101700000008a02000041000000a00400003900000000000004350000009b0000613d00000020060000390000008a020000410000000004000019000000000506001900000080065000390000000006060433000000000062041b000000200650003900000001022000390000002004400039000000000734004b000000910000413d000000a004500039000000000313004b000000a50000813d0000000303100210000000f80330018f000000010500008a000000000335022f000000000353013f0000000004040433000000000334016f000000000032041b00000001020000390000000103100210000001fd0000013d0000000001000416000000000110004c000001880000c13d00000000010000310000001f02100039000000200900008a000000000492016f000000400200043d0000000003240019000000000443004b00000000040000190000000104004039000000840530009c000001dc0000213d0000000104400190000001dc0000c13d000000400030043f0000001f0310018f00000001040003670000000505100272000000c60000613d000000000600001900000005076002100000000008720019000000000774034f000000000707043b00000000007804350000000106600039000000000756004b000000be0000413d000000000630004c000000d50000613d0000000505500210000000000454034f00000000055200190000000303300210000000000605043300000000063601cf000000000636022f000000000404043b0000010003300089000000000434022f00000000033401cf000000000363019f00000000003504350000008503000041000000200410008c000000000400001900000000040340190000008505100197000000000650004c000000000300a019000000850550009c000000000304c019000000000330004c000001880000c13d0000000004020433000000840340009c000001880000213d000000000312001900000000012400190000001f021000390000008504000041000000000532004b0000000005000019000000000504801900000085022001970000008506300197000000000762004b0000000004008019000000000262013f000000850220009c00000000020500190000000002046019000000000220004c000001880000c13d0000000002010433000000840420009c000001dc0000213d0000003f04200039000000000494016f000000400700043d0000000004470019000000000574004b00000000050000190000000105004039000000840640009c000001dc0000213d0000000105500190000001dc0000c13d000000400040043f000000000627043600000000042100190000002004400039000000000334004b000001880000213d000000000320004c000001120000613d000000000300001900000000046300190000002003300039000000000513001900000000050504330000000000540435000000000423004b0000010b0000413d000000000126001900000000000104350000000004070433000000840140009c000001dc0000213d000000000100041a000000010210019000000001011002700000007f0310018f000000000301c0190000001f0130008c00000000010000190000000101002039000000010110018f000000000112004b000001960000c13d000000200130008c000001480000413d000100000003001d000300000004001d000000000000043500000083010000410000000002000414000000830320009c0000000001024019000000c00110021000000086011001c70000801002000039000500000009001d000400000007001d000200000006001d020602010000040f0000000206000029000000040700002900000005090000290000000102200190000001880000613d00000003040000290000001f024000390000000502200270000000200340008c0000000002004019000000000301043b00000001010000290000001f01100039000000050110027000000000011300190000000002230019000000000312004b000001480000813d000000000002041b0000000102200039000000000312004b000001440000413d0000001f0140008c000001e20000a13d000300000004001d000000000000043500000083010000410000000002000414000000830320009c0000000001024019000000c00110021000000086011001c70000801002000039000500000009001d000400000007001d020602010000040f000000040600002900000005030000290000000102200190000001880000613d000000030700002900000000033701700000002002000039000000000101043b000001690000613d0000002002000039000000000400001900000000056200190000000005050433000000000051041b000000200220003900000001011000390000002004400039000000000534004b000001610000413d000000000373004b000001740000813d0000000303700210000000f80330018f000000010400008a000000000334022f000000000343013f00000000026200190000000002020433000000000232016f000000000021041b00000001010000390000000102700210000001ec0000013d0000000001000416000000000110004c000001880000c13d000000040100008a00000000011000310000008502000041000000000310004c000000000300001900000000030240190000008501100197000000000410004c000000000200a019000000850110009c00000000010300190000000001026019000000000110004c0000018a0000613d00000000010000190000020800010430000000000400041a000000010540019000000001014002700000007f0210018f000000000301001900000000030260190000001f0130008c00000000010000190000000101002039000000000114013f00000001011001900000019c0000613d0000008b0100004100000000001004350000002201000039000000040010043f0000008c010000410000020800010430000000400200043d0000000001320436000000000550004c000001ae0000613d0000000000000435000000000430004c0000000004000019000001b40000613d0000008a0500004100000000040000190000000006410019000000000705041a000000000076043500000001055000390000002004400039000000000634004b000001a60000413d000001b40000013d000001000500008a000000000454016f0000000000410435000000000330004c000000200400003900000000040060190000003f03400039000000200400008a000000000543016f0000000003250019000000000553004b00000000050000190000000105004039000000840630009c000001dc0000213d0000000105500190000001dc0000c13d000000400030043f00000020050000390000000005530436000000000202043300000000002504350000004005300039000000000620004c000001cf0000613d000000000600001900000000075600190000000008610019000000000808043300000000008704350000002006600039000000000726004b000001c80000413d000000000152001900000000000104350000005f01200039000000000141016f0000008302000041000000830410009c0000000001028019000000830430009c000000000203401900000040022002100000006001100210000000000121019f000002070001042e0000008b0100004100000000001004350000004101000039000000040010043f0000008c010000410000020800010430000000000140004c0000000001000019000001e60000613d00000000010604330000000302400210000000010300008a000000000223022f000000000232013f000000000221016f0000000101400210000000000112019f000000000010041b0000002001000039000001000010044300000120000004430000008701000041000002070001042e000000000210004c0000000002000019000001f70000613d000000a00200043d0000000303100210000000010400008a000000000334022f000000000343013f000000000332016f0000000102100210000000000123019f000000000010041b0000000001000019000002070001042e00000204002104230000000102000039000000000001042d0000000002000019000000000001042d0000020600000432000002070001042e000002080001043000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff000000000000000000000000000000000000000000000000ffffffffffffffff80000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000020000000000000000000000000000000020000000000000000000000000000004000000100000000000000000000000000000000000000000000000000000000000000000000000000cfae321700000000000000000000000000000000000000000000000000000000a4136862290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5634e487b71000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000000000000000000000000000049640b54fe6c32c3d2b931a12d0e92ca2c903f61744f706361c28dd6cf0a568a -------------------------------------------------------------------------------- /examples/contract-deployment/Greeter.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.8; 3 | 4 | contract Greeter { 5 | string private greeting; 6 | 7 | constructor(string memory _greeting) { 8 | greeting = _greeting; 9 | } 10 | 11 | function greet() public view returns (string memory) { 12 | return greeting; 13 | } 14 | 15 | function setGreeting(string memory _greeting) public { 16 | greeting = _greeting; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/contract-deployment/main.rs: -------------------------------------------------------------------------------- 1 | use ethers::abi::Abi; 2 | use std::str::FromStr; 3 | use zksync_web3_rs::providers::{Middleware, Provider}; 4 | use zksync_web3_rs::signers::{LocalWallet, Signer}; 5 | use zksync_web3_rs::zks_provider::ZKSProvider; 6 | use zksync_web3_rs::zks_wallet::{CallRequest, DeployRequest}; 7 | use zksync_web3_rs::ZKSWallet; 8 | 9 | // This is the default url for a local `era-test-node` instance. 10 | static ERA_PROVIDER_URL: &str = "http://127.0.0.1:8011"; 11 | 12 | // This is the private key for one of the rich wallets that come bundled with the era-test-node. 13 | static PRIVATE_KEY: &str = "7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"; 14 | 15 | static CONTRACT_BIN: &str = include_str!("./Greeter.bin"); 16 | static CONTRACT_ABI: &str = include_str!("./Greeter.abi"); 17 | 18 | #[tokio::main(flavor = "current_thread")] 19 | async fn main() { 20 | // Note that for this code example we only need to interface with zkSync Era. We don't care 21 | // about the Ethereum layer-1 network. 22 | let zk_wallet = { 23 | let era_provider = Provider::try_from(ERA_PROVIDER_URL).unwrap(); 24 | 25 | let chain_id = era_provider.get_chainid().await.unwrap(); 26 | let l2_wallet = LocalWallet::from_str(PRIVATE_KEY) 27 | .unwrap() 28 | .with_chain_id(chain_id.as_u64()); 29 | ZKSWallet::new(l2_wallet, None, Some(era_provider.clone()), None).unwrap() 30 | }; 31 | 32 | // Deploy contract: 33 | let contract_address = { 34 | let abi = Abi::load(CONTRACT_ABI.as_bytes()).unwrap(); 35 | let contract_bin = hex::decode(CONTRACT_BIN).unwrap().to_vec(); 36 | let request = DeployRequest::with(abi, contract_bin, vec!["Hey".to_owned()]) 37 | .from(zk_wallet.l2_address()); 38 | let address = zk_wallet.deploy(&request).await.unwrap(); 39 | 40 | println!("Contract address: {:#?}", address); 41 | 42 | address 43 | }; 44 | 45 | // Call the greet view method: 46 | { 47 | let era_provider = zk_wallet.get_era_provider().unwrap(); 48 | let call_request = CallRequest::new(contract_address, "greet()(string)".to_owned()); 49 | 50 | let greet = ZKSProvider::call(era_provider.as_ref(), &call_request) 51 | .await 52 | .unwrap(); 53 | 54 | println!("greet: {}", greet[0]); 55 | } 56 | 57 | // Perform a signed transaction calling the setGreeting method 58 | { 59 | let receipt = zk_wallet 60 | .get_era_provider() 61 | .unwrap() 62 | .clone() 63 | .send_eip712( 64 | &zk_wallet.l2_wallet, 65 | contract_address, 66 | "setGreeting(string)", 67 | Some(["Hello".into()].into()), 68 | None, 69 | ) 70 | .await 71 | .unwrap() 72 | .await 73 | .unwrap() 74 | .unwrap(); 75 | 76 | println!( 77 | "setGreeting transaction hash {:#?}", 78 | receipt.transaction_hash 79 | ); 80 | }; 81 | 82 | // Call the greet view method: 83 | 84 | { 85 | let era_provider = zk_wallet.get_era_provider().unwrap(); 86 | let call_request = CallRequest::new(contract_address, "greet()(string)".to_owned()); 87 | 88 | let greet = ZKSProvider::call(era_provider.as_ref(), &call_request) 89 | .await 90 | .unwrap(); 91 | 92 | println!("greet: {}", greet[0]); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/paymaster/main.rs: -------------------------------------------------------------------------------- 1 | use ethers::abi::Bytes; 2 | use ethers::core::k256::ecdsa::SigningKey; 3 | use ethers::prelude::*; 4 | use ethers::providers::Http; 5 | use ethers::{ 6 | abi::Address, 7 | providers::Provider, 8 | signers::{Signer, Wallet}, 9 | types::U256, 10 | }; 11 | use std::str::FromStr; 12 | use std::sync::Arc; 13 | use zksync_web3_rs::core::abi::{Contract, Token}; 14 | use zksync_web3_rs::eip712::{Eip712Meta, Eip712TransactionRequest, PaymasterParams}; 15 | use zksync_web3_rs::zks_provider::ZKSProvider; 16 | use zksync_web3_rs::ZKSWallet; 17 | 18 | static ERA_PROVIDER_URL: &str = "http://127.0.0.1:3050"; 19 | static PK: &str = "0x27593fea79697e947890ecbecce7901b0008345e5d7259710d0dd5e500d040be"; 20 | static PAYMASTER_ADDRESS: &str = ""; 21 | 22 | const PAYMASTER_ABI: &str = r#" 23 | [ 24 | { 25 | "inputs": [ 26 | { 27 | "internalType": "bytes", 28 | "name": "input", 29 | "type": "bytes" 30 | } 31 | ], 32 | "name": "general", 33 | "outputs": [], 34 | "stateMutability": "nonpayable", 35 | "type": "function" 36 | } 37 | ] 38 | "#; 39 | 40 | pub struct PaymasterFlow { 41 | paymaster: Address, 42 | amount: U256, 43 | paymaster_encoded_input: Bytes, 44 | zk_wallet: ZKSWallet, SigningKey>, 45 | era_provider: Arc, Wallet>>, 46 | } 47 | 48 | impl PaymasterFlow { 49 | pub fn new( 50 | private_key: String, 51 | paymaster_address: H160, 52 | chain_id: u64, 53 | provider: Provider, 54 | ) -> Self { 55 | let paymaster_contract = 56 | Contract::load(PAYMASTER_ABI.as_bytes()).expect("Failed to load the paymaster ABI"); 57 | let paymaster_general_fn = paymaster_contract 58 | .function("general") 59 | .expect("Failed to get the general function"); 60 | let wallet = Wallet::from_str(private_key.as_str()) 61 | .expect("Failed to create wallet from private key"); 62 | let signer = Wallet::with_chain_id(wallet, chain_id); 63 | let zk_wallet = ZKSWallet::new(signer, None, Some(provider.clone()), None).unwrap(); 64 | let era_provider = zk_wallet 65 | .get_era_provider() 66 | .expect("Failed to get era provider from zk wallet"); 67 | let paymaster_encoded_input = paymaster_general_fn 68 | .encode_input(&[Token::Bytes(vec![])]) 69 | .expect("Failed to encode paymaster input"); 70 | 71 | Self { 72 | paymaster: paymaster_address, 73 | amount: U256::from_dec_str("1").expect("Failed to parse amount"), 74 | paymaster_encoded_input, 75 | zk_wallet, 76 | era_provider, 77 | } 78 | } 79 | 80 | fn tx_request(&self) -> Eip712TransactionRequest { 81 | let address = self.zk_wallet.l1_wallet.address(); 82 | Eip712TransactionRequest::new() 83 | .from(address) 84 | .to(address) 85 | .value(self.amount) 86 | .custom_data(Eip712Meta::new().paymaster_params(PaymasterParams { 87 | paymaster: self.paymaster, 88 | paymaster_input: self.paymaster_encoded_input.clone(), 89 | })) 90 | } 91 | 92 | async fn send_transaction(&self) -> anyhow::Result> { 93 | let result = self 94 | .era_provider 95 | .send_transaction_eip712(&self.zk_wallet.l2_wallet, self.tx_request()) 96 | .await?; 97 | Ok(result) 98 | } 99 | } 100 | 101 | #[tokio::main] 102 | async fn main() { 103 | let era_provider = Provider::try_from(ERA_PROVIDER_URL).unwrap(); 104 | let paymaster_address = Address::from_str(PAYMASTER_ADDRESS).unwrap(); 105 | let chain_id = era_provider.get_chainid().await.unwrap(); 106 | let flow = PaymasterFlow::new( 107 | PK.to_string(), 108 | paymaster_address, 109 | chain_id.as_u64(), 110 | era_provider.clone(), 111 | ); 112 | let tx = flow 113 | .send_transaction() 114 | .await 115 | .unwrap() 116 | .await 117 | .unwrap() 118 | .unwrap(); 119 | println!("Transaction sent: {:#?}", tx); 120 | } 121 | -------------------------------------------------------------------------------- /examples/simple_payment/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use ethers::{ 3 | abi::Address, 4 | prelude::k256::ecdsa::SigningKey, 5 | providers::{Middleware, Provider}, 6 | signers::{Signer, Wallet}, 7 | types::U256, 8 | }; 9 | use zksync_web3_rs::{zks_wallet::TransferRequest, ZKSWallet}; 10 | 11 | // It is set so that the transaction is replay-protected (EIP-155) 12 | // https://era.zksync.io/docs/api/hardhat/testing.html#connect-wallet-to-local-nodes 13 | //const L1_CHAIN_ID: u64 = 9; 14 | const L2_CHAIN_ID: u64 = 270; 15 | 16 | #[derive(Parser)] 17 | struct Args { 18 | #[clap(long)] 19 | pub host: String, 20 | #[clap(short, long)] 21 | pub port: u16, 22 | #[clap(long, name = "AMOUNT_TO_TRANSFER")] 23 | pub amount: String, 24 | #[clap(long, name = "SENDER_ADDRESS")] 25 | pub from: Address, 26 | #[clap(long, name = "RECEIVER_ADDRESS")] 27 | pub to: Address, 28 | #[clap(long, name = "SENDER_PRIVATE_KEY")] 29 | pub private_key: Wallet, 30 | } 31 | 32 | #[tokio::main] 33 | async fn main() { 34 | env_logger::builder() 35 | .filter_module("reqwest::connect", log::LevelFilter::Off) 36 | .filter_level(log::LevelFilter::Debug) 37 | .init(); 38 | 39 | let args = Args::parse(); 40 | 41 | /* Connecting to the node */ 42 | let amount = U256::from_dec_str(&args.amount).unwrap(); 43 | log::debug!("Amount to transfer: {:?}", amount); 44 | let signer = Wallet::with_chain_id(args.private_key, L2_CHAIN_ID); 45 | 46 | let provider = Provider::try_from(format!( 47 | "http://{host}:{port}", 48 | host = args.host, 49 | port = args.port 50 | )) 51 | .unwrap() 52 | .interval(std::time::Duration::from_millis(10)); 53 | 54 | let zk_wallet = ZKSWallet::new(signer, None, Some(provider.clone()), None).unwrap(); 55 | 56 | /* Payment transaction building */ 57 | let payment_request = TransferRequest::new(amount).to(args.to).from(args.from); 58 | 59 | log::debug!("{:?}", payment_request); 60 | 61 | /* Sending the payment transaction */ 62 | 63 | log::debug!( 64 | "Sender's balance before paying: {:?}", 65 | provider.clone().get_balance(args.from, None).await.unwrap() 66 | ); 67 | log::debug!( 68 | "Receiver's balance before getting payed: {:?}", 69 | provider.get_balance(args.to, None).await.unwrap() 70 | ); 71 | 72 | let payment_transaction_id = zk_wallet.transfer(&payment_request, None).await.unwrap(); 73 | let payment_transaction_receipt = provider 74 | .get_transaction_receipt(payment_transaction_id) 75 | .await 76 | .unwrap() 77 | .unwrap(); 78 | 79 | log::info!("{:?}", payment_transaction_receipt); 80 | 81 | log::debug!( 82 | "Sender's balance after paying: {:?}", 83 | provider.get_balance(args.from, None).await.unwrap() 84 | ); 85 | log::debug!( 86 | "Receiver's balance after getting payed: {:?}", 87 | provider.get_balance(args.to, None).await.unwrap() 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /resources/testing/erc20/abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "stateMutability": "nonpayable", 5 | "type": "constructor" 6 | }, 7 | { 8 | "anonymous": false, 9 | "inputs": [ 10 | { 11 | "indexed": true, 12 | "internalType": "address", 13 | "name": "owner", 14 | "type": "address" 15 | }, 16 | { 17 | "indexed": true, 18 | "internalType": "address", 19 | "name": "spender", 20 | "type": "address" 21 | }, 22 | { 23 | "indexed": false, 24 | "internalType": "uint256", 25 | "name": "value", 26 | "type": "uint256" 27 | } 28 | ], 29 | "name": "Approval", 30 | "type": "event" 31 | }, 32 | { 33 | "anonymous": false, 34 | "inputs": [ 35 | { 36 | "indexed": true, 37 | "internalType": "address", 38 | "name": "from", 39 | "type": "address" 40 | }, 41 | { 42 | "indexed": true, 43 | "internalType": "address", 44 | "name": "to", 45 | "type": "address" 46 | }, 47 | { 48 | "indexed": false, 49 | "internalType": "uint256", 50 | "name": "value", 51 | "type": "uint256" 52 | } 53 | ], 54 | "name": "Transfer", 55 | "type": "event" 56 | }, 57 | { 58 | "inputs": [ 59 | { 60 | "internalType": "address", 61 | "name": "owner", 62 | "type": "address" 63 | }, 64 | { 65 | "internalType": "address", 66 | "name": "spender", 67 | "type": "address" 68 | } 69 | ], 70 | "name": "allowance", 71 | "outputs": [ 72 | { 73 | "internalType": "uint256", 74 | "name": "", 75 | "type": "uint256" 76 | } 77 | ], 78 | "stateMutability": "view", 79 | "type": "function" 80 | }, 81 | { 82 | "inputs": [ 83 | { 84 | "internalType": "address", 85 | "name": "spender", 86 | "type": "address" 87 | }, 88 | { 89 | "internalType": "uint256", 90 | "name": "amount", 91 | "type": "uint256" 92 | } 93 | ], 94 | "name": "approve", 95 | "outputs": [ 96 | { 97 | "internalType": "bool", 98 | "name": "", 99 | "type": "bool" 100 | } 101 | ], 102 | "stateMutability": "nonpayable", 103 | "type": "function" 104 | }, 105 | { 106 | "inputs": [ 107 | { 108 | "internalType": "address", 109 | "name": "account", 110 | "type": "address" 111 | } 112 | ], 113 | "name": "balanceOf", 114 | "outputs": [ 115 | { 116 | "internalType": "uint256", 117 | "name": "", 118 | "type": "uint256" 119 | } 120 | ], 121 | "stateMutability": "view", 122 | "type": "function" 123 | }, 124 | { 125 | "inputs": [], 126 | "name": "decimals", 127 | "outputs": [ 128 | { 129 | "internalType": "uint8", 130 | "name": "", 131 | "type": "uint8" 132 | } 133 | ], 134 | "stateMutability": "view", 135 | "type": "function" 136 | }, 137 | { 138 | "inputs": [ 139 | { 140 | "internalType": "address", 141 | "name": "spender", 142 | "type": "address" 143 | }, 144 | { 145 | "internalType": "uint256", 146 | "name": "subtractedValue", 147 | "type": "uint256" 148 | } 149 | ], 150 | "name": "decreaseAllowance", 151 | "outputs": [ 152 | { 153 | "internalType": "bool", 154 | "name": "", 155 | "type": "bool" 156 | } 157 | ], 158 | "stateMutability": "nonpayable", 159 | "type": "function" 160 | }, 161 | { 162 | "inputs": [ 163 | { 164 | "internalType": "address", 165 | "name": "spender", 166 | "type": "address" 167 | }, 168 | { 169 | "internalType": "uint256", 170 | "name": "addedValue", 171 | "type": "uint256" 172 | } 173 | ], 174 | "name": "increaseAllowance", 175 | "outputs": [ 176 | { 177 | "internalType": "bool", 178 | "name": "", 179 | "type": "bool" 180 | } 181 | ], 182 | "stateMutability": "nonpayable", 183 | "type": "function" 184 | }, 185 | { 186 | "inputs": [], 187 | "name": "name", 188 | "outputs": [ 189 | { 190 | "internalType": "string", 191 | "name": "", 192 | "type": "string" 193 | } 194 | ], 195 | "stateMutability": "view", 196 | "type": "function" 197 | }, 198 | { 199 | "inputs": [], 200 | "name": "symbol", 201 | "outputs": [ 202 | { 203 | "internalType": "string", 204 | "name": "", 205 | "type": "string" 206 | } 207 | ], 208 | "stateMutability": "view", 209 | "type": "function" 210 | }, 211 | { 212 | "inputs": [], 213 | "name": "totalSupply", 214 | "outputs": [ 215 | { 216 | "internalType": "uint256", 217 | "name": "", 218 | "type": "uint256" 219 | } 220 | ], 221 | "stateMutability": "view", 222 | "type": "function" 223 | }, 224 | { 225 | "inputs": [ 226 | { 227 | "internalType": "address", 228 | "name": "to", 229 | "type": "address" 230 | }, 231 | { 232 | "internalType": "uint256", 233 | "name": "amount", 234 | "type": "uint256" 235 | } 236 | ], 237 | "name": "transfer", 238 | "outputs": [ 239 | { 240 | "internalType": "bool", 241 | "name": "", 242 | "type": "bool" 243 | } 244 | ], 245 | "stateMutability": "nonpayable", 246 | "type": "function" 247 | }, 248 | { 249 | "inputs": [ 250 | { 251 | "internalType": "address", 252 | "name": "from", 253 | "type": "address" 254 | }, 255 | { 256 | "internalType": "address", 257 | "name": "to", 258 | "type": "address" 259 | }, 260 | { 261 | "internalType": "uint256", 262 | "name": "amount", 263 | "type": "uint256" 264 | } 265 | ], 266 | "name": "transferFrom", 267 | "outputs": [ 268 | { 269 | "internalType": "bool", 270 | "name": "", 271 | "type": "bool" 272 | } 273 | ], 274 | "stateMutability": "nonpayable", 275 | "type": "function" 276 | } 277 | ] 278 | -------------------------------------------------------------------------------- /src/abi/ContractDeployer.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "accountAddress", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": false, 13 | "internalType": "enum IContractDeployer.AccountNonceOrdering", 14 | "name": "nonceOrdering", 15 | "type": "uint8" 16 | } 17 | ], 18 | "name": "AccountNonceOrderingUpdated", 19 | "type": "event" 20 | }, 21 | { 22 | "anonymous": false, 23 | "inputs": [ 24 | { 25 | "indexed": true, 26 | "internalType": "address", 27 | "name": "accountAddress", 28 | "type": "address" 29 | }, 30 | { 31 | "indexed": false, 32 | "internalType": "enum IContractDeployer.AccountAbstractionVersion", 33 | "name": "aaVersion", 34 | "type": "uint8" 35 | } 36 | ], 37 | "name": "AccountVersionUpdated", 38 | "type": "event" 39 | }, 40 | { 41 | "anonymous": false, 42 | "inputs": [ 43 | { 44 | "indexed": true, 45 | "internalType": "address", 46 | "name": "deployerAddress", 47 | "type": "address" 48 | }, 49 | { 50 | "indexed": true, 51 | "internalType": "bytes32", 52 | "name": "bytecodeHash", 53 | "type": "bytes32" 54 | }, 55 | { 56 | "indexed": true, 57 | "internalType": "address", 58 | "name": "contractAddress", 59 | "type": "address" 60 | } 61 | ], 62 | "name": "ContractDeployed", 63 | "type": "event" 64 | }, 65 | { 66 | "inputs": [ 67 | { 68 | "internalType": "bytes32", 69 | "name": "_salt", 70 | "type": "bytes32" 71 | }, 72 | { 73 | "internalType": "bytes32", 74 | "name": "_bytecodeHash", 75 | "type": "bytes32" 76 | }, 77 | { 78 | "internalType": "bytes", 79 | "name": "_input", 80 | "type": "bytes" 81 | } 82 | ], 83 | "name": "create", 84 | "outputs": [ 85 | { 86 | "internalType": "address", 87 | "name": "", 88 | "type": "address" 89 | } 90 | ], 91 | "stateMutability": "payable", 92 | "type": "function" 93 | }, 94 | { 95 | "inputs": [ 96 | { 97 | "internalType": "bytes32", 98 | "name": "_salt", 99 | "type": "bytes32" 100 | }, 101 | { 102 | "internalType": "bytes32", 103 | "name": "_bytecodeHash", 104 | "type": "bytes32" 105 | }, 106 | { 107 | "internalType": "bytes", 108 | "name": "_input", 109 | "type": "bytes" 110 | } 111 | ], 112 | "name": "create2", 113 | "outputs": [ 114 | { 115 | "internalType": "address", 116 | "name": "", 117 | "type": "address" 118 | } 119 | ], 120 | "stateMutability": "payable", 121 | "type": "function" 122 | }, 123 | { 124 | "inputs": [ 125 | { 126 | "internalType": "bytes32", 127 | "name": "_salt", 128 | "type": "bytes32" 129 | }, 130 | { 131 | "internalType": "bytes32", 132 | "name": "_bytecodeHash", 133 | "type": "bytes32" 134 | }, 135 | { 136 | "internalType": "bytes", 137 | "name": "_input", 138 | "type": "bytes" 139 | }, 140 | { 141 | "internalType": "enum IContractDeployer.AccountAbstractionVersion", 142 | "name": "_aaVersion", 143 | "type": "uint8" 144 | } 145 | ], 146 | "name": "create2Account", 147 | "outputs": [ 148 | { 149 | "internalType": "address", 150 | "name": "", 151 | "type": "address" 152 | } 153 | ], 154 | "stateMutability": "payable", 155 | "type": "function" 156 | }, 157 | { 158 | "inputs": [ 159 | { 160 | "internalType": "bytes32", 161 | "name": "", 162 | "type": "bytes32" 163 | }, 164 | { 165 | "internalType": "bytes32", 166 | "name": "_bytecodeHash", 167 | "type": "bytes32" 168 | }, 169 | { 170 | "internalType": "bytes", 171 | "name": "_input", 172 | "type": "bytes" 173 | }, 174 | { 175 | "internalType": "enum IContractDeployer.AccountAbstractionVersion", 176 | "name": "_aaVersion", 177 | "type": "uint8" 178 | } 179 | ], 180 | "name": "createAccount", 181 | "outputs": [ 182 | { 183 | "internalType": "address", 184 | "name": "", 185 | "type": "address" 186 | } 187 | ], 188 | "stateMutability": "payable", 189 | "type": "function" 190 | }, 191 | { 192 | "inputs": [ 193 | { 194 | "internalType": "address", 195 | "name": "_address", 196 | "type": "address" 197 | } 198 | ], 199 | "name": "extendedAccountVersion", 200 | "outputs": [ 201 | { 202 | "internalType": "enum IContractDeployer.AccountAbstractionVersion", 203 | "name": "", 204 | "type": "uint8" 205 | } 206 | ], 207 | "stateMutability": "view", 208 | "type": "function" 209 | }, 210 | { 211 | "inputs": [ 212 | { 213 | "components": [ 214 | { 215 | "internalType": "bytes32", 216 | "name": "bytecodeHash", 217 | "type": "bytes32" 218 | }, 219 | { 220 | "internalType": "address", 221 | "name": "newAddress", 222 | "type": "address" 223 | }, 224 | { 225 | "internalType": "bool", 226 | "name": "callConstructor", 227 | "type": "bool" 228 | }, 229 | { 230 | "internalType": "uint256", 231 | "name": "value", 232 | "type": "uint256" 233 | }, 234 | { 235 | "internalType": "bytes", 236 | "name": "input", 237 | "type": "bytes" 238 | } 239 | ], 240 | "internalType": "struct ContractDeployer.ForceDeployment", 241 | "name": "_deployment", 242 | "type": "tuple" 243 | }, 244 | { 245 | "internalType": "address", 246 | "name": "_sender", 247 | "type": "address" 248 | } 249 | ], 250 | "name": "forceDeployOnAddress", 251 | "outputs": [], 252 | "stateMutability": "payable", 253 | "type": "function" 254 | }, 255 | { 256 | "inputs": [ 257 | { 258 | "components": [ 259 | { 260 | "internalType": "bytes32", 261 | "name": "bytecodeHash", 262 | "type": "bytes32" 263 | }, 264 | { 265 | "internalType": "address", 266 | "name": "newAddress", 267 | "type": "address" 268 | }, 269 | { 270 | "internalType": "bool", 271 | "name": "callConstructor", 272 | "type": "bool" 273 | }, 274 | { 275 | "internalType": "uint256", 276 | "name": "value", 277 | "type": "uint256" 278 | }, 279 | { 280 | "internalType": "bytes", 281 | "name": "input", 282 | "type": "bytes" 283 | } 284 | ], 285 | "internalType": "struct ContractDeployer.ForceDeployment[]", 286 | "name": "_deployments", 287 | "type": "tuple[]" 288 | } 289 | ], 290 | "name": "forceDeployOnAddresses", 291 | "outputs": [], 292 | "stateMutability": "payable", 293 | "type": "function" 294 | }, 295 | { 296 | "inputs": [ 297 | { 298 | "internalType": "address", 299 | "name": "_address", 300 | "type": "address" 301 | } 302 | ], 303 | "name": "getAccountInfo", 304 | "outputs": [ 305 | { 306 | "components": [ 307 | { 308 | "internalType": "enum IContractDeployer.AccountAbstractionVersion", 309 | "name": "supportedAAVersion", 310 | "type": "uint8" 311 | }, 312 | { 313 | "internalType": "enum IContractDeployer.AccountNonceOrdering", 314 | "name": "nonceOrdering", 315 | "type": "uint8" 316 | } 317 | ], 318 | "internalType": "struct IContractDeployer.AccountInfo", 319 | "name": "info", 320 | "type": "tuple" 321 | } 322 | ], 323 | "stateMutability": "view", 324 | "type": "function" 325 | }, 326 | { 327 | "inputs": [ 328 | { 329 | "internalType": "address", 330 | "name": "_sender", 331 | "type": "address" 332 | }, 333 | { 334 | "internalType": "uint256", 335 | "name": "_senderNonce", 336 | "type": "uint256" 337 | } 338 | ], 339 | "name": "getNewAddressCreate", 340 | "outputs": [ 341 | { 342 | "internalType": "address", 343 | "name": "newAddress", 344 | "type": "address" 345 | } 346 | ], 347 | "stateMutability": "pure", 348 | "type": "function" 349 | }, 350 | { 351 | "inputs": [ 352 | { 353 | "internalType": "address", 354 | "name": "_sender", 355 | "type": "address" 356 | }, 357 | { 358 | "internalType": "bytes32", 359 | "name": "_bytecodeHash", 360 | "type": "bytes32" 361 | }, 362 | { 363 | "internalType": "bytes32", 364 | "name": "_salt", 365 | "type": "bytes32" 366 | }, 367 | { 368 | "internalType": "bytes", 369 | "name": "_input", 370 | "type": "bytes" 371 | } 372 | ], 373 | "name": "getNewAddressCreate2", 374 | "outputs": [ 375 | { 376 | "internalType": "address", 377 | "name": "newAddress", 378 | "type": "address" 379 | } 380 | ], 381 | "stateMutability": "view", 382 | "type": "function" 383 | }, 384 | { 385 | "inputs": [ 386 | { 387 | "internalType": "enum IContractDeployer.AccountAbstractionVersion", 388 | "name": "_version", 389 | "type": "uint8" 390 | } 391 | ], 392 | "name": "updateAccountVersion", 393 | "outputs": [], 394 | "stateMutability": "nonpayable", 395 | "type": "function" 396 | }, 397 | { 398 | "inputs": [ 399 | { 400 | "internalType": "enum IContractDeployer.AccountNonceOrdering", 401 | "name": "_nonceOrdering", 402 | "type": "uint8" 403 | } 404 | ], 405 | "name": "updateNonceOrdering", 406 | "outputs": [], 407 | "stateMutability": "nonpayable", 408 | "type": "function" 409 | } 410 | ] 411 | -------------------------------------------------------------------------------- /src/abi/IL1Bridge.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "anonymous": false, 5 | "inputs": [ 6 | { 7 | "indexed": true, 8 | "internalType": "address", 9 | "name": "to", 10 | "type": "address" 11 | }, 12 | { 13 | "indexed": true, 14 | "internalType": "address", 15 | "name": "l1Token", 16 | "type": "address" 17 | }, 18 | { 19 | "indexed": false, 20 | "internalType": "uint256", 21 | "name": "amount", 22 | "type": "uint256" 23 | } 24 | ], 25 | "name": "ClaimedFailedDeposit", 26 | "type": "event" 27 | }, 28 | { 29 | "anonymous": false, 30 | "inputs": [ 31 | { 32 | "indexed": true, 33 | "internalType": "address", 34 | "name": "from", 35 | "type": "address" 36 | }, 37 | { 38 | "indexed": true, 39 | "internalType": "address", 40 | "name": "to", 41 | "type": "address" 42 | }, 43 | { 44 | "indexed": true, 45 | "internalType": "address", 46 | "name": "l1Token", 47 | "type": "address" 48 | }, 49 | { 50 | "indexed": false, 51 | "internalType": "uint256", 52 | "name": "amount", 53 | "type": "uint256" 54 | } 55 | ], 56 | "name": "DepositInitiated", 57 | "type": "event" 58 | }, 59 | { 60 | "anonymous": false, 61 | "inputs": [ 62 | { 63 | "indexed": true, 64 | "internalType": "address", 65 | "name": "to", 66 | "type": "address" 67 | }, 68 | { 69 | "indexed": true, 70 | "internalType": "address", 71 | "name": "l1Token", 72 | "type": "address" 73 | }, 74 | { 75 | "indexed": false, 76 | "internalType": "uint256", 77 | "name": "amount", 78 | "type": "uint256" 79 | } 80 | ], 81 | "name": "WithdrawalFinalized", 82 | "type": "event" 83 | }, 84 | { 85 | "inputs": [ 86 | { 87 | "internalType": "address", 88 | "name": "_depositSender", 89 | "type": "address" 90 | }, 91 | { 92 | "internalType": "address", 93 | "name": "_l1Token", 94 | "type": "address" 95 | }, 96 | { 97 | "internalType": "bytes32", 98 | "name": "_l2TxHash", 99 | "type": "bytes32" 100 | }, 101 | { 102 | "internalType": "uint256", 103 | "name": "_l2BlockNumber", 104 | "type": "uint256" 105 | }, 106 | { 107 | "internalType": "uint256", 108 | "name": "_l2MessageIndex", 109 | "type": "uint256" 110 | }, 111 | { 112 | "internalType": "uint16", 113 | "name": "_l2TxNumberInBlock", 114 | "type": "uint16" 115 | }, 116 | { 117 | "internalType": "bytes32[]", 118 | "name": "_merkleProof", 119 | "type": "bytes32[]" 120 | } 121 | ], 122 | "name": "claimFailedDeposit", 123 | "outputs": [], 124 | "stateMutability": "nonpayable", 125 | "type": "function" 126 | }, 127 | { 128 | "inputs": [ 129 | { 130 | "internalType": "address", 131 | "name": "_l2Receiver", 132 | "type": "address" 133 | }, 134 | { 135 | "internalType": "address", 136 | "name": "_l1Token", 137 | "type": "address" 138 | }, 139 | { 140 | "internalType": "uint256", 141 | "name": "_amount", 142 | "type": "uint256" 143 | }, 144 | { 145 | "internalType": "uint256", 146 | "name": "_l2TxGasLimit", 147 | "type": "uint256" 148 | }, 149 | { 150 | "internalType": "uint256", 151 | "name": "_l2TxGasPerPubdataByte", 152 | "type": "uint256" 153 | } 154 | ], 155 | "name": "deposit", 156 | "outputs": [ 157 | { 158 | "internalType": "bytes32", 159 | "name": "txHash", 160 | "type": "bytes32" 161 | } 162 | ], 163 | "stateMutability": "payable", 164 | "type": "function" 165 | }, 166 | { 167 | "inputs": [ 168 | { 169 | "internalType": "uint256", 170 | "name": "_l2BlockNumber", 171 | "type": "uint256" 172 | }, 173 | { 174 | "internalType": "uint256", 175 | "name": "_l2MessageIndex", 176 | "type": "uint256" 177 | }, 178 | { 179 | "internalType": "uint16", 180 | "name": "_l2TxNumberInBlock", 181 | "type": "uint16" 182 | }, 183 | { 184 | "internalType": "bytes", 185 | "name": "_message", 186 | "type": "bytes" 187 | }, 188 | { 189 | "internalType": "bytes32[]", 190 | "name": "_merkleProof", 191 | "type": "bytes32[]" 192 | } 193 | ], 194 | "name": "finalizeWithdrawal", 195 | "outputs": [], 196 | "stateMutability": "nonpayable", 197 | "type": "function" 198 | }, 199 | { 200 | "inputs": [ 201 | { 202 | "internalType": "uint256", 203 | "name": "_l2BlockNumber", 204 | "type": "uint256" 205 | }, 206 | { 207 | "internalType": "uint256", 208 | "name": "_l2MessageIndex", 209 | "type": "uint256" 210 | } 211 | ], 212 | "name": "isWithdrawalFinalized", 213 | "outputs": [ 214 | { 215 | "internalType": "bool", 216 | "name": "", 217 | "type": "bool" 218 | } 219 | ], 220 | "stateMutability": "view", 221 | "type": "function" 222 | }, 223 | { 224 | "inputs": [ 225 | { 226 | "internalType": "address", 227 | "name": "_l1Token", 228 | "type": "address" 229 | } 230 | ], 231 | "name": "l2TokenAddress", 232 | "outputs": [ 233 | { 234 | "internalType": "address", 235 | "name": "", 236 | "type": "address" 237 | } 238 | ], 239 | "stateMutability": "view", 240 | "type": "function" 241 | } 242 | ] 243 | } 244 | -------------------------------------------------------------------------------- /src/abi/mod.rs: -------------------------------------------------------------------------------- 1 | use ethers::abi::Contract; 2 | use std::str::FromStr; 3 | 4 | const L1_DEFAULT_BRIDGE_INTERFACE: &str = include_str!("./IL1Bridge.json"); 5 | 6 | fn load_contract(raw_abi_string: &str) -> Contract { 7 | // Note that using `.expect` here is acceptable because this is a private function and we 8 | // expect the value of `raw_abi_string` to be correct. In the future, we should refactor this 9 | // piece of code to run in compile time. 10 | #![allow(clippy::unwrap_used, clippy::expect_used)] 11 | let abi_string = serde_json::Value::from_str(raw_abi_string) 12 | .expect("Malformed contract abi file") 13 | .get("abi") 14 | .expect("Malformed contract abi file") 15 | .to_string(); 16 | Contract::load(abi_string.as_bytes()).unwrap() 17 | } 18 | 19 | pub fn l1_bridge_contract() -> Contract { 20 | load_contract(L1_DEFAULT_BRIDGE_INTERFACE) 21 | } 22 | -------------------------------------------------------------------------------- /src/abi/test_contracts/basic_combined.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [ 5 | { 6 | "internalType": "uint256", 7 | "name": "value", 8 | "type": "uint256" 9 | } 10 | ], 11 | "name": "plus_one", 12 | "outputs": [ 13 | { 14 | "internalType": "uint256", 15 | "name": "", 16 | "type": "uint256" 17 | } 18 | ], 19 | "stateMutability": "pure", 20 | "type": "function" 21 | }, 22 | { 23 | "inputs": [], 24 | "name": "str_out", 25 | "outputs": [ 26 | { 27 | "internalType": "string", 28 | "name": "", 29 | "type": "string" 30 | } 31 | ], 32 | "stateMutability": "view", 33 | "type": "function" 34 | } 35 | ], 36 | "bin": "0002000000000002000100000000000200010000000103550000006001100270000000360010019d0000008001000039000000400010043f0000000101200190000000300000c13d0000000001000031000000040110008c000000750000413d0000000101000367000000000101043b000000e0011002700000003a0210009c000000640000613d0000003b0110009c000000750000c13d0000000001000416000000000110004c000000750000c13d000000040100008a00000000011000310000003c02000041000000200310008c000000000300001900000000030240190000003c01100197000000000410004c000000000200a0190000003c0110009c00000000010300190000000001026019000000000110004c000000750000c13d00000004010000390000000101100367000000000101043b000000010200008a000000000221004b0000008e0000c13d0000003f0100004100000000001004350000001101000039000000040010043f0000004001000041000000d4000104300000000001000416000000000110004c000000750000c13d000000000100041a000000010210019000000001011002700000007f0310018f000000000301c0190000001f0130008c00000000010000190000000101002039000000010110018f000000000112004b000000440000613d0000003f0100004100000000001004350000002201000039000000040010043f0000004001000041000000d400010430000000200130008c0000005d0000413d000100000003001d000000000000043500000036010000410000000002000414000000360320009c0000000001024019000000c00110021000000037011001c7000080100200003900d200cd0000040f0000000102200190000000750000613d000000000101043b00000001020000290000001f0220003900000005022002700000000002210019000000000321004b0000005d0000813d000000000001041b0000000101100039000000000321004b000000590000413d0000003801000041000000000010041b0000002001000039000001000010044300000120000004430000003901000041000000d30001042e0000000001000416000000000110004c000000750000c13d000000040100008a00000000011000310000003c02000041000000000310004c000000000300001900000000030240190000003c01100197000000000410004c000000000200a0190000003c0110009c00000000010300190000000001026019000000000110004c000000770000613d0000000001000019000000d400010430000000000400041a000000010540019000000001014002700000007f0210018f000000000301001900000000030260190000001f0130008c00000000010000190000000101002039000000000114013f00000001011001900000003e0000c13d000000400200043d0000000001320436000000000550004c000000920000c13d000001000500008a000000000454016f0000000000410435000000000330004c000000200400003900000000040060190000009f0000013d0000000101100039000000800010043f0000004101000041000000d30001042e0000000000000435000000000430004c00000000040000190000009f0000613d0000003d0500004100000000040000190000000006410019000000000705041a000000000076043500000001055000390000002004400039000000000634004b000000980000413d0000003f03400039000000200400008a000000000543016f0000000003250019000000000553004b000000000500001900000001050040390000003e0630009c000000c70000213d0000000105500190000000c70000c13d000000400030043f00000020050000390000000005530436000000000202043300000000002504350000004005300039000000000620004c000000ba0000613d000000000600001900000000075600190000000008610019000000000808043300000000008704350000002006600039000000000726004b000000b30000413d000000000152001900000000000104350000005f01200039000000000141016f0000003602000041000000360410009c0000000001028019000000360430009c000000000203401900000040022002100000006001100210000000000121019f000000d30001042e0000003f0100004100000000001004350000004101000039000000040010043f0000004001000041000000d400010430000000d0002104230000000102000039000000000001042d0000000002000019000000000001042d000000d200000432000000d30001042e000000d40001043000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff020000000000000000000000000000000000002000000000000000000000000048656c6c6f20576f726c642100000000000000000000000000000000000000180000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000009146769000000000000000000000000000000000000000000000000000000000029a49288000000000000000000000000000000000000000000000000000000000000000290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563000000000000000000000000000000000000000000000000ffffffffffffffff4e487b710000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000020000000800000000000000000a1a90c2a4bc7ab6e1206e75baf8e7f6cb8e12d08ae8512d43ff03dc841302652", 37 | "factory-deps": {} 38 | } 39 | -------------------------------------------------------------------------------- /src/abi/test_contracts/counter_combined.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [], 5 | "name": "count", 6 | "outputs": [ 7 | { 8 | "internalType": "uint256", 9 | "name": "", 10 | "type": "uint256" 11 | } 12 | ], 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "inputs": [], 18 | "name": "increment", 19 | "outputs": [], 20 | "stateMutability": "nonpayable", 21 | "type": "function" 22 | }, 23 | { 24 | "inputs": [], 25 | "name": "number", 26 | "outputs": [ 27 | { 28 | "internalType": "uint256", 29 | "name": "", 30 | "type": "uint256" 31 | } 32 | ], 33 | "stateMutability": "view", 34 | "type": "function" 35 | }, 36 | { 37 | "inputs": [ 38 | { 39 | "internalType": "uint256", 40 | "name": "newNumber", 41 | "type": "uint256" 42 | } 43 | ], 44 | "name": "setNumber", 45 | "outputs": [], 46 | "stateMutability": "nonpayable", 47 | "type": "function" 48 | } 49 | ], 50 | "bin": "000200000000000200010000000103550000006001100270000000230010019d0000008001000039000000400010043f00000001012001900000002b0000c13d0000000001000031000000040110008c000000780000413d0000000101000367000000000101043b000000e001100270000000250210009c000000330000213d000000280210009c000000520000613d000000290110009c000000780000c13d0000000001000416000000000110004c000000780000c13d000000040100008a00000000011000310000002a02000041000000200310008c000000000300001900000000030240190000002a01100197000000000410004c000000000200a0190000002a0110009c00000000010300190000000001026019000000000110004c000000780000c13d00000004010000390000000101100367000000000101043b000000000010041b0000000001000019000000880001042e0000000001000416000000000110004c000000780000c13d0000002001000039000001000010044300000120000004430000002401000041000000880001042e000000260210009c000000670000613d000000270110009c000000780000c13d0000000001000416000000000110004c000000780000c13d000000040100008a00000000011000310000002a02000041000000000310004c000000000300001900000000030240190000002a01100197000000000410004c000000000200a0190000002a0110009c00000000010300190000000001026019000000000110004c000000780000c13d000000000100041a000000010200008a000000000221004b000000830000c13d0000002b0100004100000000001004350000001101000039000000040010043f0000002c0100004100000089000104300000000001000416000000000110004c000000780000c13d000000040100008a00000000011000310000002a02000041000000000310004c000000000300001900000000030240190000002a01100197000000000410004c000000000200a0190000002a0110009c00000000010300190000000001026019000000000110004c000000780000c13d000000000100041a000000800010043f0000002e01000041000000880001042e0000000001000416000000000110004c000000780000c13d000000040100008a00000000011000310000002a02000041000000000310004c000000000300001900000000030240190000002a01100197000000000410004c000000000200a0190000002a0110009c00000000010300190000000001026019000000000110004c0000007a0000613d00000000010000190000008900010430000000400100043d000000000200041a00000000002104350000002302000041000000230310009c000000000102801900000040011002100000002d011001c7000000880001042e0000000101100039000000000010041b0000000001000019000000880001042e0000008700000432000000880001042e00000089000104300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000008381f589000000000000000000000000000000000000000000000000000000008381f58a00000000000000000000000000000000000000000000000000000000d09de08a0000000000000000000000000000000000000000000000000000000006661abd000000000000000000000000000000000000000000000000000000003fb5c1cb80000000000000000000000000000000000000000000000000000000000000004e487b71000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000008000000000000000000000000000000000000000000000000000000000000000000000000000000000247ea5dc0a268d59052ca1eea64d94f66fbe61531d9a8d07a8ab1bdbcdd96cf4", 51 | "factory-deps": {} 52 | } 53 | -------------------------------------------------------------------------------- /src/abi/test_contracts/greeter_combined.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [ 5 | { 6 | "internalType": "string", 7 | "name": "_greeting", 8 | "type": "string" 9 | } 10 | ], 11 | "stateMutability": "nonpayable", 12 | "type": "constructor" 13 | }, 14 | { 15 | "inputs": [], 16 | "name": "greet", 17 | "outputs": [ 18 | { 19 | "internalType": "string", 20 | "name": "", 21 | "type": "string" 22 | } 23 | ], 24 | "stateMutability": "view", 25 | "type": "function" 26 | }, 27 | { 28 | "inputs": [ 29 | { 30 | "internalType": "string", 31 | "name": "_greeting", 32 | "type": "string" 33 | } 34 | ], 35 | "name": "setGreeting", 36 | "outputs": [], 37 | "stateMutability": "nonpayable", 38 | "type": "function" 39 | } 40 | ], 41 | "bin": "0002000000000002000500000000000200010000000103550000006001100270000000830010019d0000008001000039000000400010043f0000000101200190000000a80000c13d0000000001000031000000040110008c000001880000413d0000000101000367000000000101043b000000e001100270000000880210009c000001770000613d000000890110009c000001880000c13d0000000001000416000000000110004c000001880000c13d0000000001000031000000040210008a0000008503000041000000200420008c000000000400001900000000040340190000008502200197000000000520004c000000000300a019000000850220009c00000000020400190000000002036019000000000220004c000001880000c13d00000001020003670000000403200370000000000303043b000000840430009c000001880000213d00000023043000390000008505000041000000000614004b0000000006000019000000000605801900000085011001970000008504400197000000000714004b0000000005008019000000000114013f000000850110009c00000000010600190000000001056019000000000110004c000001880000c13d0000000401300039000000000112034f000000000101043b000000840210009c000001dc0000213d000000bf04100039000000200200008a000000000424016f000000840540009c000001dc0000213d000000400040043f000000800010043f000000240430003900000000034100190000000005000031000000000353004b000001880000213d0000001f0310018f00000001044003670000000505100272000000560000613d00000000060000190000000507600210000000000874034f000000000808043b000000a00770003900000000008704350000000106600039000000000756004b0000004e0000413d000000000630004c000000650000613d0000000505500210000000000454034f0000000303300210000000a005500039000000000605043300000000063601cf000000000636022f000000000404043b0000010003300089000000000434022f00000000033401cf000000000363019f0000000000350435000000a0011000390000000000010435000000800100043d000000840310009c000001dc0000213d000000000400041a000000010340019000000001034002700000007f0530018f00000000030560190000001f0530008c00000000050000190000000105002039000000000454013f0000000104400190000001960000c13d000000200430008c000000870000413d0000001f0410003900000005044002700000008a044000410000008a05000041000000200610008c000000000405401900000000000004350000001f0330003900000005033002700000008a03300041000000000534004b000000870000813d000000000004041b0000000104400039000000000534004b000000830000413d0000001f0310008c000001f30000a13d00000000032101700000008a02000041000000a00400003900000000000004350000009b0000613d00000020060000390000008a020000410000000004000019000000000506001900000080065000390000000006060433000000000062041b000000200650003900000001022000390000002004400039000000000734004b000000910000413d000000a004500039000000000313004b000000a50000813d0000000303100210000000f80330018f000000010500008a000000000335022f000000000353013f0000000004040433000000000334016f000000000032041b00000001020000390000000103100210000001fd0000013d0000000001000416000000000110004c000001880000c13d00000000010000310000001f02100039000000200900008a000000000492016f000000400200043d0000000003240019000000000443004b00000000040000190000000104004039000000840530009c000001dc0000213d0000000104400190000001dc0000c13d000000400030043f0000001f0310018f00000001040003670000000505100272000000c60000613d000000000600001900000005076002100000000008720019000000000774034f000000000707043b00000000007804350000000106600039000000000756004b000000be0000413d000000000630004c000000d50000613d0000000505500210000000000454034f00000000055200190000000303300210000000000605043300000000063601cf000000000636022f000000000404043b0000010003300089000000000434022f00000000033401cf000000000363019f00000000003504350000008503000041000000200410008c000000000400001900000000040340190000008505100197000000000650004c000000000300a019000000850550009c000000000304c019000000000330004c000001880000c13d0000000004020433000000840340009c000001880000213d000000000312001900000000012400190000001f021000390000008504000041000000000532004b0000000005000019000000000504801900000085022001970000008506300197000000000762004b0000000004008019000000000262013f000000850220009c00000000020500190000000002046019000000000220004c000001880000c13d0000000002010433000000840420009c000001dc0000213d0000003f04200039000000000494016f000000400700043d0000000004470019000000000574004b00000000050000190000000105004039000000840640009c000001dc0000213d0000000105500190000001dc0000c13d000000400040043f000000000627043600000000042100190000002004400039000000000334004b000001880000213d000000000320004c000001120000613d000000000300001900000000046300190000002003300039000000000513001900000000050504330000000000540435000000000423004b0000010b0000413d000000000126001900000000000104350000000004070433000000840140009c000001dc0000213d000000000100041a000000010210019000000001011002700000007f0310018f000000000301c0190000001f0130008c00000000010000190000000101002039000000010110018f000000000112004b000001960000c13d000000200130008c000001480000413d000100000003001d000300000004001d000000000000043500000083010000410000000002000414000000830320009c0000000001024019000000c00110021000000086011001c70000801002000039000500000009001d000400000007001d000200000006001d020602010000040f0000000206000029000000040700002900000005090000290000000102200190000001880000613d00000003040000290000001f024000390000000502200270000000200340008c0000000002004019000000000301043b00000001010000290000001f01100039000000050110027000000000011300190000000002230019000000000312004b000001480000813d000000000002041b0000000102200039000000000312004b000001440000413d0000001f0140008c000001e20000a13d000300000004001d000000000000043500000083010000410000000002000414000000830320009c0000000001024019000000c00110021000000086011001c70000801002000039000500000009001d000400000007001d020602010000040f000000040600002900000005030000290000000102200190000001880000613d000000030700002900000000033701700000002002000039000000000101043b000001690000613d0000002002000039000000000400001900000000056200190000000005050433000000000051041b000000200220003900000001011000390000002004400039000000000534004b000001610000413d000000000373004b000001740000813d0000000303700210000000f80330018f000000010400008a000000000334022f000000000343013f00000000026200190000000002020433000000000232016f000000000021041b00000001010000390000000102700210000001ec0000013d0000000001000416000000000110004c000001880000c13d000000040100008a00000000011000310000008502000041000000000310004c000000000300001900000000030240190000008501100197000000000410004c000000000200a019000000850110009c00000000010300190000000001026019000000000110004c0000018a0000613d00000000010000190000020800010430000000000400041a000000010540019000000001014002700000007f0210018f000000000301001900000000030260190000001f0130008c00000000010000190000000101002039000000000114013f00000001011001900000019c0000613d0000008b0100004100000000001004350000002201000039000000040010043f0000008c010000410000020800010430000000400200043d0000000001320436000000000550004c000001ae0000613d0000000000000435000000000430004c0000000004000019000001b40000613d0000008a0500004100000000040000190000000006410019000000000705041a000000000076043500000001055000390000002004400039000000000634004b000001a60000413d000001b40000013d000001000500008a000000000454016f0000000000410435000000000330004c000000200400003900000000040060190000003f03400039000000200400008a000000000543016f0000000003250019000000000553004b00000000050000190000000105004039000000840630009c000001dc0000213d0000000105500190000001dc0000c13d000000400030043f00000020050000390000000005530436000000000202043300000000002504350000004005300039000000000620004c000001cf0000613d000000000600001900000000075600190000000008610019000000000808043300000000008704350000002006600039000000000726004b000001c80000413d000000000152001900000000000104350000005f01200039000000000141016f0000008302000041000000830410009c0000000001028019000000830430009c000000000203401900000040022002100000006001100210000000000121019f000002070001042e0000008b0100004100000000001004350000004101000039000000040010043f0000008c010000410000020800010430000000000140004c0000000001000019000001e60000613d00000000010604330000000302400210000000010300008a000000000223022f000000000232013f000000000221016f0000000101400210000000000112019f000000000010041b0000002001000039000001000010044300000120000004430000008701000041000002070001042e000000000210004c0000000002000019000001f70000613d000000a00200043d0000000303100210000000010400008a000000000334022f000000000343013f000000000332016f0000000102100210000000000123019f000000000010041b0000000001000019000002070001042e00000204002104230000000102000039000000000001042d0000000002000019000000000001042d0000020600000432000002070001042e000002080001043000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff000000000000000000000000000000000000000000000000ffffffffffffffff80000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000020000000000000000000000000000000020000000000000000000000000000004000000100000000000000000000000000000000000000000000000000000000000000000000000000cfae321700000000000000000000000000000000000000000000000000000000a4136862290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5634e487b71000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000000000000000000000000000049640b54fe6c32c3d2b931a12d0e92ca2c903f61744f706361c28dd6cf0a568a", 42 | "factory-deps": {} 43 | } 44 | -------------------------------------------------------------------------------- /src/abi/test_contracts/import_combined.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [ 5 | { 6 | "internalType": "address", 7 | "name": "_counterAddress", 8 | "type": "address" 9 | } 10 | ], 11 | "stateMutability": "nonpayable", 12 | "type": "constructor" 13 | }, 14 | { 15 | "inputs": [], 16 | "name": "counter", 17 | "outputs": [ 18 | { 19 | "internalType": "contract Counter", 20 | "name": "", 21 | "type": "address" 22 | } 23 | ], 24 | "stateMutability": "view", 25 | "type": "function" 26 | }, 27 | { 28 | "inputs": [], 29 | "name": "getCounterValue", 30 | "outputs": [ 31 | { 32 | "internalType": "uint256", 33 | "name": "", 34 | "type": "uint256" 35 | } 36 | ], 37 | "stateMutability": "view", 38 | "type": "function" 39 | }, 40 | { 41 | "inputs": [], 42 | "name": "incrementCounter", 43 | "outputs": [], 44 | "stateMutability": "nonpayable", 45 | "type": "function" 46 | } 47 | ], 48 | "bin": "00030000000000020002000000000002000200000001035500000060011002700000005c0010019d000100000000001f0000000101200190000000520000c13d0000008005000039000000400050043f0000000001000031000000040110008c000000c50000413d0000000201000367000000000101043b000000e001100270000000610210009c000000960000613d000000620210009c000000b40000613d000000630110009c000000c50000c13d0000000001000416000000000110004c000000c50000c13d000000040100008a00000000011000310000005d02000041000000000310004c000000000300001900000000030240190000005d01100197000000000410004c000000000200a0190000005d0110009c00000000010300190000000001026019000000000110004c000000c50000c13d000000000100041a0000006a0200004100000000002004390000005e01100197000100000001001d00000004001004430000005c0100004100000000020004140000005c0320009c0000000001024019000000c0011002100000006b011001c70000800202000039000200000005001d016c01670000040f00000002050000290000000102200190000000c50000613d000000000101043b000000000110004c000000c50000613d0000006c01000041000000800010043f00000000010004140000000102000029000000040320008c0000004f0000613d0000005c040000410000005c0310009c0000000001048019000000c0011002100000006d011001c7016c01620000040f0000000205000029000000000301001900000060033002700001005c0030019d0000005c0430019700000001022001900000013c0000613d000000400050043f00000000010000190000016d0001042e0000000001000416000000000110004c000000c50000c13d00000000010000310000009f02100039000000200300008a000000000232016f0000007f0320008c000000610000213d000000680100004100000000001004350000004101000039000000040010043f00000069010000410000016e00010430000000400020043f0000001f0210018f000000020300036700000005041002720000006f0000613d00000000050000190000000506500210000000000763034f000000000707043b000000800660003900000000007604350000000105500039000000000645004b000000670000413d000000000520004c0000007e0000613d0000000504400210000000000343034f00000003022002100000008004400039000000000504043300000000052501cf000000000525022f000000000303043b0000010002200089000000000323022f00000000022301cf000000000252019f00000000002404350000005d02000041000000200310008c000000000300001900000000030240190000005d01100197000000000410004c000000000200a0190000005d0110009c00000000010300190000000001026019000000000110004c000000c50000c13d000000800100043d0000005e0210009c000000c50000213d000000000200041a0000005f02200197000000000112019f000000000010041b00000020010000390000010000100443000001200000044300000060010000410000016d0001042e0000000001000416000000000110004c000000c50000c13d000000040100008a00000000011000310000005d02000041000000000310004c000000000300001900000000030240190000005d01100197000000000410004c000000000200a0190000005d0110009c00000000010300190000000001026019000000000110004c000000c50000c13d000000000200041a000000400a00043d000000640100004100000000001a043500000000010004140000005e02200197000000040320008c000000d10000c13d0000000103000031000000200130008c00000020040000390000000004034019000001010000013d0000000001000416000000000110004c000000c50000c13d000000040100008a00000000011000310000005d02000041000000000310004c000000000300001900000000030240190000005d01100197000000000410004c000000000200a0190000005d0110009c00000000010300190000000001026019000000000110004c000000c70000613d00000000010000190000016e00010430000000000100041a0000005e01100197000000400200043d00000000001204350000005c010000410000005c0320009c0000000001024019000000400110021000000067011001c70000016d0001042e0000005c030000410000005c0410009c00000000010380190000005c04a0009c00000000030a40190000004003300210000000c001100210000000000131019f00000065011001c700020000000a001d016c01670000040f000000020a000029000000000301001900000060033002700000005c03300197000000200430008c000000200400003900000000040340190000001f0540018f0000000506400272000000ef0000613d0000000007000019000000050870021000000000098a0019000000000881034f000000000808043b00000000008904350000000107700039000000000867004b000000e70000413d000000000750004c000000fe0000613d0000000506600210000000000761034f00000000066a00190000000305500210000000000806043300000000085801cf000000000858022f000000000707043b0000010005500089000000000757022f00000000055701cf000000000585019f0000000000560435000100000003001f0000000102200190000001160000613d0000001f01400039000000600210018f0000000001a20019000000000221004b00000000020000190000000102004039000000660410009c0000005b0000213d00000001022001900000005b0000c13d000000400010043f000000200230008c000000c50000413d00000000020a043300000000002104350000005c020000410000005c0310009c0000000001028019000000400110021000000067011001c70000016d0001042e000000400200043d0000001f0430018f0000000503300272000001230000613d000000000500001900000005065002100000000007620019000000000661034f000000000606043b00000000006704350000000105500039000000000635004b0000011b0000413d000000000540004c000001320000613d0000000503300210000000000131034f00000000033200190000000304400210000000000503043300000000054501cf000000000545022f000000000101043b0000010004400089000000000141022f00000000014101cf000000000151019f00000000001304350000005c0100004100000001030000310000005c0430009c00000000030180190000005c0420009c000000000102401900000040011002100000006002300210000000000112019f0000016e00010430000000400200043d0000001f0340018f0000000504400272000001490000613d000000000500001900000005065002100000000007620019000000000661034f000000000606043b00000000006704350000000105500039000000000645004b000001410000413d000000000530004c000001580000613d0000000504400210000000000141034f00000000044200190000000303300210000000000504043300000000053501cf000000000535022f000000000101043b0000010003300089000000000131022f00000000013101cf000000000151019f00000000001404350000005c0100004100000001030000310000005c0430009c00000000030180190000005c0420009c000000000102401900000040011002100000006002300210000000000112019f0000016e0001043000000165002104210000000102000039000000000001042d0000000002000019000000000001042d0000016a002104230000000102000039000000000001042d0000000002000019000000000001042d0000016c000004320000016d0001042e0000016e00010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000002000000000000000000000000000000400000010000000000000000000000000000000000000000000000000000000000000000000000000072142b890000000000000000000000000000000000000000000000000000000061bc221a000000000000000000000000000000000000000000000000000000005b34b96606661abd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff00000000000000000000000000000000000000200000000000000000000000004e487b710000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000001806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000d09de08a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000800000000000000000a073360f752ecb28efc98f457238dff3a59b7386ee9d6f106474e0734c498575", 49 | "factory-deps": {} 50 | } 51 | -------------------------------------------------------------------------------- /src/abi/test_contracts/storage_combined.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [ 5 | { 6 | "internalType": "uint256", 7 | "name": "initialValue", 8 | "type": "uint256" 9 | } 10 | ], 11 | "stateMutability": "nonpayable", 12 | "type": "constructor" 13 | }, 14 | { 15 | "inputs": [], 16 | "name": "getValue", 17 | "outputs": [ 18 | { 19 | "internalType": "uint256", 20 | "name": "", 21 | "type": "uint256" 22 | } 23 | ], 24 | "stateMutability": "view", 25 | "type": "function" 26 | }, 27 | { 28 | "inputs": [], 29 | "name": "incrementValue", 30 | "outputs": [], 31 | "stateMutability": "nonpayable", 32 | "type": "function" 33 | }, 34 | { 35 | "inputs": [ 36 | { 37 | "internalType": "uint256", 38 | "name": "newValue", 39 | "type": "uint256" 40 | } 41 | ], 42 | "name": "setValue", 43 | "outputs": [], 44 | "stateMutability": "nonpayable", 45 | "type": "function" 46 | } 47 | ], 48 | "bin": "000200000000000200010000000103550000006001100270000000290010019d0000000101200190000000290000c13d0000008001000039000000400010043f0000000001000031000000040110008c000000900000413d0000000101000367000000000101043b000000e0011002700000002e0210009c000000680000613d0000002f0210009c0000007f0000613d000000300110009c000000900000c13d0000000001000416000000000110004c000000900000c13d000000040100008a00000000011000310000002a02000041000000000310004c000000000300001900000000030240190000002a01100197000000000410004c000000000200a0190000002a0110009c00000000010300190000000001026019000000000110004c000000900000c13d000000000100041a000000800010043f0000003101000041000000a10001042e0000000001000416000000000110004c000000900000c13d00000000010000310000009f02100039000000200300008a000000000232016f0000007f0320008c000000380000213d0000002c0100004100000000001004350000004101000039000000040010043f0000002d01000041000000a200010430000000400020043f0000001f0210018f00000001030003670000000504100272000000460000613d00000000050000190000000506500210000000000763034f000000000707043b000000800660003900000000007604350000000105500039000000000645004b0000003e0000413d000000000520004c000000550000613d0000000504400210000000000343034f00000003022002100000008004400039000000000504043300000000052501cf000000000525022f000000000303043b0000010002200089000000000323022f00000000022301cf000000000252019f00000000002404350000002a02000041000000200310008c000000000300001900000000030240190000002a01100197000000000410004c000000000200a0190000002a0110009c00000000010300190000000001026019000000000110004c000000900000c13d000000800100043d000000000010041b0000002001000039000001000010044300000120000004430000002b01000041000000a10001042e0000000001000416000000000110004c000000900000c13d000000040100008a00000000011000310000002a02000041000000200310008c000000000300001900000000030240190000002a01100197000000000410004c000000000200a0190000002a0110009c00000000010300190000000001026019000000000110004c000000900000c13d00000004010000390000000101100367000000000101043b000000000010041b0000000001000019000000a10001042e0000000001000416000000000110004c000000900000c13d000000040100008a00000000011000310000002a02000041000000000310004c000000000300001900000000030240190000002a01100197000000000410004c000000000200a0190000002a0110009c00000000010300190000000001026019000000000110004c000000920000613d0000000001000019000000a200010430000000000100041a000000010200008a000000000221004b0000009c0000c13d0000002c0100004100000000001004350000001101000039000000040010043f0000002d01000041000000a2000104300000000101100039000000000010041b0000000001000019000000a10001042e000000a000000432000000a10001042e000000a200010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff800000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000400000010000000000000000004e487b710000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000000000000000000000055241077000000000000000000000000000000000000000000000000000000003bfd7fd300000000000000000000000000000000000000000000000000000000209652550000000000000000000000000000000000000020000000800000000000000000430b7207f834c9f6e08a25fac5ffea13f4d9fe6a31344af4bd833859f9cc157b", 49 | "factory-deps": {} 50 | } 51 | -------------------------------------------------------------------------------- /src/contracts/l1_bridge_contract.rs: -------------------------------------------------------------------------------- 1 | use ethers_contract::abigen; 2 | 3 | abigen!(L1Bridge, "src/abi/IL1Bridge.json"); 4 | -------------------------------------------------------------------------------- /src/contracts/main_contract.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ethers::prelude::k256::ecdsa::{RecoveryId, Signature}; 4 | use ethers::prelude::k256::schnorr::signature::hazmat::PrehashSigner; 5 | use ethers::prelude::signer::SignerMiddlewareError; 6 | use ethers::prelude::ProviderError; 7 | use ethers::prelude::SignerMiddleware; 8 | use ethers::providers::Middleware; 9 | use ethers::signers::Wallet; 10 | use ethers::types::{Address, Bytes, TransactionReceipt, U256}; 11 | use ethers_contract::{abigen, ContractError}; 12 | 13 | abigen!(MainContract, "./src/abi/IZkSync.json"); 14 | 15 | // ╔══════════════════════════════════════════════════════════════════════════════════════════╗ 16 | // ║ Error enum: ║ 17 | // ╚══════════════════════════════════════════════════════════════════════════════════════════╝ 18 | 19 | #[derive(thiserror::Error, Debug)] 20 | pub enum MainContractError 21 | where 22 | M: Middleware, 23 | D: PrehashSigner<(Signature, RecoveryId)> + Sync + Send, 24 | { 25 | #[error("Middleware error: {0}")] 26 | MiddlewareError(#[from] SignerMiddlewareError>), 27 | #[error("Contract error: {0}")] 28 | ContractError(#[from] ContractError>>), 29 | #[error("Provider error: {0}")] 30 | ProviderError(#[from] ProviderError), 31 | #[error("Transaction receipt not found")] 32 | TransactionReceiptNotFound, 33 | } 34 | 35 | // ╔══════════════════════════════════════════════════════════════════════════════════════════╗ 36 | // ║ Decorator: ║ 37 | // ╚══════════════════════════════════════════════════════════════════════════════════════════╝ 38 | type SM = SignerMiddleware>; 39 | 40 | pub struct MainContractInstance 41 | where 42 | M: Middleware, 43 | D: PrehashSigner<(Signature, RecoveryId)> + Sync + Send, 44 | { 45 | provider: Arc>, 46 | contract: MainContract>, 47 | } 48 | 49 | impl MainContractInstance 50 | where 51 | M: Middleware, 52 | D: PrehashSigner<(Signature, RecoveryId)> + Sync + Send, 53 | { 54 | pub fn new(address: Address, provider: Arc>>) -> Self { 55 | let contract = MainContract::new(address, Arc::clone(&provider)); 56 | Self { provider, contract } 57 | } 58 | 59 | pub async fn get_base_cost( 60 | &self, 61 | gas_price: U256, 62 | l2_gas_limit: U256, 63 | l2_gas_per_pubdata_byte_limit: U256, 64 | ) -> Result>> { 65 | self.contract 66 | .l_2_transaction_base_cost(gas_price, l2_gas_limit, l2_gas_per_pubdata_byte_limit) 67 | .call() 68 | .await 69 | } 70 | 71 | async fn nonce(&self) -> Result> { 72 | let signer_address = self.provider.address(); 73 | let nonce = self 74 | .provider 75 | .get_transaction_count(signer_address, None) 76 | .await?; 77 | Ok(nonce) 78 | } 79 | 80 | pub async fn request_l2_transaction( 81 | &self, 82 | contract_l2: Address, 83 | l2_value: U256, 84 | call_data: Bytes, 85 | l2_gas_limit: U256, 86 | l2_gas_per_pubdata_byte_limit: U256, 87 | factory_deps: Vec, 88 | refund_recipient: Address, 89 | gas_price: U256, 90 | gas_limit: U256, 91 | l1_value: U256, 92 | ) -> Result> { 93 | let nonce = self.nonce().await?; 94 | let function_call = self 95 | .contract 96 | .request_l2_transaction( 97 | contract_l2, 98 | l2_value, 99 | call_data, 100 | l2_gas_limit, 101 | l2_gas_per_pubdata_byte_limit, 102 | factory_deps, 103 | refund_recipient, 104 | ) 105 | .nonce(nonce) 106 | .from(self.provider.address()) 107 | .gas_price(gas_price) 108 | .gas(gas_limit) 109 | .value(l1_value); 110 | let receipt = function_call 111 | .send() 112 | .await? 113 | // FIXME: Awaiting on a `PendingTransaction` results in an 114 | // `Option`. Under which circumpstances does it return `None`? 115 | .await? 116 | .ok_or(MainContractError::::TransactionReceiptNotFound)?; 117 | 118 | Ok(receipt) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/contracts/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod l1_bridge_contract; 2 | pub mod main_contract; 3 | -------------------------------------------------------------------------------- /src/eip712/meta.rs: -------------------------------------------------------------------------------- 1 | use super::{rlp_append_option, PaymasterParams}; 2 | use crate::zks_utils::DEFAULT_GAS_PER_PUBDATA_LIMIT; 3 | use ethers::{ 4 | types::{Bytes, U256}, 5 | utils::rlp::Encodable, 6 | }; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Serialize, Deserialize, Clone, Debug)] 10 | #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] 11 | pub struct Eip712Meta { 12 | pub gas_per_pubdata: U256, 13 | pub factory_deps: Vec>, 14 | #[serde(skip_serializing_if = "Option::is_none")] 15 | pub custom_signature: Option, 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | pub paymaster_params: Option, 18 | } 19 | 20 | impl Eip712Meta { 21 | pub fn new() -> Self { 22 | Self::default() 23 | } 24 | 25 | pub fn gas_per_pubdata(mut self, gas_per_pubdata: T) -> Self 26 | where 27 | T: Into, 28 | { 29 | self.gas_per_pubdata = gas_per_pubdata.into(); 30 | self 31 | } 32 | 33 | pub fn factory_deps(mut self, factory_deps: T) -> Self 34 | where 35 | T: Into>>, 36 | { 37 | self.factory_deps = factory_deps.into(); 38 | self 39 | } 40 | 41 | pub fn custom_signature(mut self, custom_signature: T) -> Self 42 | where 43 | T: Into, 44 | { 45 | self.custom_signature = Some(custom_signature.into()); 46 | self 47 | } 48 | 49 | pub fn paymaster_params(mut self, paymaster_params: PaymasterParams) -> Self { 50 | self.paymaster_params = Some(paymaster_params); 51 | self 52 | } 53 | } 54 | 55 | impl Default for Eip712Meta { 56 | fn default() -> Self { 57 | Self { 58 | gas_per_pubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT.into(), 59 | factory_deps: Default::default(), 60 | custom_signature: Default::default(), 61 | paymaster_params: Default::default(), 62 | } 63 | } 64 | } 65 | 66 | impl Encodable for Eip712Meta { 67 | fn rlp_append(&self, stream: &mut ethers::utils::rlp::RlpStream) { 68 | // 12 69 | stream.append(&self.gas_per_pubdata); 70 | // 13 71 | stream.begin_list(self.factory_deps.len()); 72 | for dep in self.factory_deps.iter() { 73 | stream.append(dep); 74 | } 75 | // 14 76 | rlp_append_option(stream, self.custom_signature.clone().map(|v| v.to_vec())); 77 | // 15 78 | rlp_append_option(stream, self.paymaster_params.clone()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/eip712/mod.rs: -------------------------------------------------------------------------------- 1 | use ethers::{ 2 | types::transaction::eip712::Eip712Error, 3 | utils::rlp::{Encodable, RlpStream}, 4 | }; 5 | use sha2::Digest; 6 | use std::num::TryFromIntError; 7 | 8 | mod meta; 9 | pub use meta::Eip712Meta; 10 | 11 | mod transaction_request; 12 | pub use transaction_request::Eip712TransactionRequest; 13 | 14 | mod transaction; 15 | pub use transaction::Eip712Transaction; 16 | 17 | mod paymaster_params; 18 | pub use paymaster_params::PaymasterParams; 19 | 20 | /// The 32-byte hash of the bytecode of a zkSync contract is calculated in the following way: 21 | /// 22 | /// * The first 2 bytes denote the version of bytecode hash format and are currently equal to [1,0]. 23 | /// * The second 2 bytes denote the length of the bytecode in 32-byte words. 24 | /// * The rest of the 28-byte (i.e. 28 low big-endian bytes) are equal to the last 28 bytes of the sha256 hash of the contract's bytecode. 25 | pub fn hash_bytecode(bytecode: &[u8]) -> Result<[u8; 32], Eip712Error> { 26 | let step_1: [u8; 2] = 0x0100_u16.to_be_bytes(); 27 | let bytecode_length: u16 = (bytecode.len() / 32) 28 | .try_into() 29 | .map_err(|e: TryFromIntError| Eip712Error::Message(e.to_string()))?; 30 | let step_2: [u8; 2] = bytecode_length.to_be_bytes(); 31 | let step_3: [u8; 28] = sha2::Sha256::digest(bytecode) 32 | .into_iter() 33 | .skip(4) 34 | .collect::>() 35 | .try_into() 36 | .map_err(|e| { 37 | Eip712Error::Message(format!( 38 | "Failed to digest last 28 bytes of bytecode's sha256 hash: {e:?}" 39 | )) 40 | })?; 41 | 42 | let contract_hash: [u8; 32] = [&step_1, &step_2, &step_3[..]] 43 | .concat() 44 | .try_into() 45 | .map_err(|e| { 46 | Eip712Error::Message(format!("Algorithm's steps concatenation failed: {e:?}")) 47 | })?; 48 | 49 | Ok(contract_hash) 50 | } 51 | 52 | pub(crate) fn rlp_append_option(stream: &mut RlpStream, value: Option) 53 | where 54 | T: Encodable, 55 | { 56 | if let Some(v) = value { 57 | stream.append(&v); 58 | } else { 59 | stream.append(&""); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/eip712/paymaster_params.rs: -------------------------------------------------------------------------------- 1 | use ethers::{types::Address, utils::rlp::Encodable}; 2 | use serde::Serialize; 3 | 4 | #[derive(Serialize, serde::Deserialize, Clone, Debug, Default)] 5 | #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] 6 | pub struct PaymasterParams { 7 | pub paymaster: Address, 8 | pub paymaster_input: Vec, 9 | } 10 | 11 | impl PaymasterParams { 12 | pub fn paymaster(mut self, paymaster: Address) -> Self { 13 | self.paymaster = paymaster; 14 | self 15 | } 16 | 17 | pub fn paymaster_input(mut self, paymaster_input: Vec) -> Self { 18 | self.paymaster_input = paymaster_input; 19 | self 20 | } 21 | } 22 | 23 | impl Encodable for PaymasterParams { 24 | fn rlp_append(&self, stream: &mut ethers::utils::rlp::RlpStream) { 25 | stream.begin_list(2); 26 | stream.append(&self.paymaster); 27 | stream.append(&self.paymaster_input.clone()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/eip712/transaction.rs: -------------------------------------------------------------------------------- 1 | use super::{hash_bytecode, Eip712TransactionRequest}; 2 | use crate::zks_utils::{DEFAULT_GAS_PER_PUBDATA_LIMIT, EIP712_TX_TYPE}; 3 | use ethers::{ 4 | abi::encode, 5 | types::{ 6 | transaction::eip712::{ 7 | encode_data, encode_type, EIP712Domain, Eip712, Eip712DomainType, Eip712Error, Types, 8 | }, 9 | Address, Bytes, U256, 10 | }, 11 | utils::keccak256, 12 | }; 13 | use serde::{Deserialize, Serialize}; 14 | use serde_json::json; 15 | 16 | #[derive(Serialize, Deserialize, Debug, Clone)] 17 | #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] 18 | pub struct Eip712Transaction { 19 | pub tx_type: U256, 20 | pub from: Address, 21 | pub to: Address, 22 | pub gas_limit: U256, 23 | pub gas_per_pubdata_byte_limit: U256, 24 | pub max_fee_per_gas: U256, 25 | pub max_priority_fee_per_gas: U256, 26 | pub paymaster: Address, 27 | pub nonce: U256, 28 | pub value: U256, 29 | pub data: Bytes, 30 | pub factory_deps: Vec, 31 | pub paymaster_input: Bytes, 32 | pub chain_id: U256, 33 | } 34 | 35 | impl Eip712Transaction { 36 | // Check if this is necessary or if we can always use the default. 37 | pub fn new() -> Self { 38 | Self::default() 39 | } 40 | 41 | pub fn tx_type(mut self, tx_type: T) -> Self 42 | where 43 | T: Into, 44 | { 45 | self.tx_type = tx_type.into(); 46 | self 47 | } 48 | 49 | pub fn to(mut self, to: T) -> Self 50 | where 51 | T: Into
, 52 | { 53 | self.to = to.into(); 54 | self 55 | } 56 | 57 | pub fn from(mut self, from: T) -> Self 58 | where 59 | T: Into
, 60 | { 61 | self.from = from.into(); 62 | self 63 | } 64 | 65 | pub fn nonce(mut self, nonce: T) -> Self 66 | where 67 | T: Into, 68 | { 69 | self.nonce = nonce.into(); 70 | self 71 | } 72 | 73 | pub fn gas_limit(mut self, gas_limit: T) -> Self 74 | where 75 | T: Into, 76 | { 77 | self.gas_limit = gas_limit.into(); 78 | self 79 | } 80 | 81 | pub fn gas_per_pubdata_byte_limit(mut self, gas_per_pubdata_byte_limit: T) -> Self 82 | where 83 | T: Into, 84 | { 85 | self.gas_per_pubdata_byte_limit = gas_per_pubdata_byte_limit.into(); 86 | self 87 | } 88 | 89 | pub fn max_fee_per_gas(mut self, max_fee_per_gas: T) -> Self 90 | where 91 | T: Into, 92 | { 93 | self.max_fee_per_gas = max_fee_per_gas.into(); 94 | self 95 | } 96 | 97 | pub fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: T) -> Self 98 | where 99 | T: Into, 100 | { 101 | self.max_priority_fee_per_gas = max_priority_fee_per_gas.into(); 102 | self 103 | } 104 | 105 | pub fn paymaster(mut self, paymaster: T) -> Self 106 | where 107 | T: Into
, 108 | { 109 | self.paymaster = paymaster.into(); 110 | self 111 | } 112 | 113 | pub fn value(mut self, value: T) -> Self 114 | where 115 | T: Into, 116 | { 117 | self.value = value.into(); 118 | self 119 | } 120 | 121 | pub fn data(mut self, data: T) -> Self 122 | where 123 | T: Into, 124 | { 125 | self.data = data.into(); 126 | self 127 | } 128 | 129 | pub fn factory_deps(mut self, factory_deps: T) -> Self 130 | where 131 | T: Into>, 132 | { 133 | self.factory_deps = factory_deps.into(); 134 | self 135 | } 136 | 137 | pub fn paymaster_input(mut self, paymaster_input: T) -> Self 138 | where 139 | T: Into, 140 | { 141 | self.paymaster_input = paymaster_input.into(); 142 | self 143 | } 144 | 145 | pub fn chain_id(mut self, chain_id: T) -> Self 146 | where 147 | T: Into, 148 | { 149 | self.chain_id = chain_id.into(); 150 | self 151 | } 152 | } 153 | 154 | impl Default for Eip712Transaction { 155 | fn default() -> Self { 156 | Self { 157 | tx_type: EIP712_TX_TYPE.into(), 158 | from: Default::default(), 159 | to: Default::default(), 160 | gas_limit: Default::default(), 161 | gas_per_pubdata_byte_limit: DEFAULT_GAS_PER_PUBDATA_LIMIT.into(), 162 | max_fee_per_gas: Default::default(), 163 | max_priority_fee_per_gas: Default::default(), 164 | paymaster: Default::default(), 165 | nonce: Default::default(), 166 | value: Default::default(), 167 | data: Default::default(), 168 | factory_deps: >::default(), 169 | paymaster_input: Default::default(), 170 | chain_id: Default::default(), 171 | } 172 | } 173 | } 174 | 175 | // FIXME: Cleanup this. 176 | pub fn eip712_transaction_types() -> Types { 177 | let mut types = Types::new(); 178 | 179 | types.insert( 180 | "Transaction".to_owned(), 181 | vec![ 182 | Eip712DomainType { 183 | name: "txType".to_owned(), 184 | r#type: "uint256".to_owned(), 185 | }, 186 | Eip712DomainType { 187 | name: "from".to_owned(), 188 | r#type: "uint256".to_owned(), 189 | }, 190 | Eip712DomainType { 191 | name: "to".to_owned(), 192 | r#type: "uint256".to_owned(), 193 | }, 194 | Eip712DomainType { 195 | name: "gasLimit".to_owned(), 196 | r#type: "uint256".to_owned(), 197 | }, 198 | Eip712DomainType { 199 | name: "gasPerPubdataByteLimit".to_owned(), 200 | r#type: "uint256".to_owned(), 201 | }, 202 | Eip712DomainType { 203 | name: "maxFeePerGas".to_owned(), 204 | r#type: "uint256".to_owned(), 205 | }, 206 | Eip712DomainType { 207 | name: "maxPriorityFeePerGas".to_owned(), 208 | r#type: "uint256".to_owned(), 209 | }, 210 | Eip712DomainType { 211 | name: "paymaster".to_owned(), 212 | r#type: "uint256".to_owned(), 213 | }, 214 | Eip712DomainType { 215 | name: "nonce".to_owned(), 216 | r#type: "uint256".to_owned(), 217 | }, 218 | Eip712DomainType { 219 | name: "value".to_owned(), 220 | r#type: "uint256".to_owned(), 221 | }, 222 | Eip712DomainType { 223 | name: "data".to_owned(), 224 | r#type: "bytes".to_owned(), 225 | }, 226 | Eip712DomainType { 227 | name: "factoryDeps".to_owned(), 228 | r#type: "bytes32[]".to_owned(), 229 | }, 230 | Eip712DomainType { 231 | name: "paymasterInput".to_owned(), 232 | r#type: "bytes".to_owned(), 233 | }, 234 | ], 235 | ); 236 | types 237 | } 238 | 239 | impl Eip712 for Eip712Transaction { 240 | type Error = Eip712Error; 241 | 242 | fn domain(&self) -> Result { 243 | Ok(EIP712Domain { 244 | name: Some(String::from("zkSync")), 245 | version: Some(String::from("2")), 246 | chain_id: Some(self.chain_id), 247 | verifying_contract: None, 248 | salt: None, 249 | }) 250 | } 251 | 252 | fn type_hash() -> Result<[u8; 32], Self::Error> { 253 | Ok(keccak256(encode_type( 254 | "Transaction", 255 | &eip712_transaction_types(), 256 | )?)) 257 | } 258 | 259 | fn struct_hash(&self) -> Result<[u8; 32], Self::Error> { 260 | let hash = keccak256(encode(&encode_data( 261 | "Transaction", 262 | &json!(self), 263 | &eip712_transaction_types(), 264 | )?)); 265 | Ok(hash) 266 | } 267 | } 268 | 269 | impl TryFrom for Eip712Transaction { 270 | type Error = Eip712Error; 271 | 272 | fn try_from(tx: Eip712TransactionRequest) -> Result { 273 | let mut eip712_transaction = Eip712Transaction::default() 274 | .tx_type(tx.r#type) 275 | .from(tx.from) 276 | .to(tx.to) 277 | .max_priority_fee_per_gas(tx.max_priority_fee_per_gas) 278 | .nonce(tx.nonce) 279 | .value(tx.value) 280 | .data(tx.data) 281 | .factory_deps( 282 | tx.custom_data 283 | .factory_deps 284 | .into_iter() 285 | .map(|dependency_bytecode| { 286 | hash_bytecode(&Bytes::from(dependency_bytecode)).map(Bytes::from) 287 | }) 288 | .collect::, _>>()?, 289 | ) 290 | .chain_id(tx.chain_id); 291 | 292 | if let Some(paymaster_params) = tx.custom_data.paymaster_params { 293 | eip712_transaction = eip712_transaction 294 | .paymaster(paymaster_params.paymaster) 295 | .paymaster_input(paymaster_params.paymaster_input); 296 | } 297 | 298 | if let Some(gas_limit) = tx.gas_limit { 299 | eip712_transaction = eip712_transaction.gas_limit(gas_limit) 300 | } else { 301 | return Err(Eip712Error::Message("gas_limit is missing".to_owned())); 302 | } 303 | 304 | if let Some(max_fee_per_gas) = tx.max_fee_per_gas { 305 | eip712_transaction = eip712_transaction.max_fee_per_gas(max_fee_per_gas) 306 | } else { 307 | return Err(Eip712Error::Message( 308 | "max_fee_per_gas is missing".to_owned(), 309 | )); 310 | } 311 | 312 | Ok(eip712_transaction) 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/eip712/transaction_request.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr}; 2 | 3 | use super::{hash_bytecode, rlp_append_option, Eip712Meta}; 4 | use 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 | }; 10 | use 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 | }; 18 | use ethers_contract::encode_function_data; 19 | use 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"))] 24 | pub 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, 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, 45 | #[serde(skip_serializing_if = "Option::is_none")] 46 | pub max_fee_per_gas: Option, // conflicts with gas_price 47 | 48 | pub ccip_read_enabled: bool, 49 | } 50 | 51 | impl 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(mut self, to: T) -> Self 65 | where 66 | T: Into
, 67 | { 68 | self.to = to.into(); 69 | self 70 | } 71 | 72 | pub fn from(mut self, from: T) -> Self 73 | where 74 | T: Into
, 75 | { 76 | self.from = from.into(); 77 | self 78 | } 79 | 80 | pub fn nonce(mut self, nonce: T) -> Self 81 | where 82 | T: Into, 83 | { 84 | self.nonce = nonce.into(); 85 | self 86 | } 87 | 88 | pub fn gas_limit(mut self, gas_limit: T) -> Self 89 | where 90 | T: Into, 91 | { 92 | self.gas_limit = Some(gas_limit.into()); 93 | self 94 | } 95 | 96 | pub fn gas_price(mut self, gas_price: T) -> Self 97 | where 98 | T: Into, 99 | { 100 | self.gas_price = gas_price.into(); 101 | self 102 | } 103 | 104 | pub fn data(mut self, data: T) -> Self 105 | where 106 | T: Into, 107 | { 108 | self.data = data.into(); 109 | self 110 | } 111 | 112 | pub fn value(mut self, value: T) -> Self 113 | where 114 | T: Into, 115 | { 116 | self.value = value.into(); 117 | self 118 | } 119 | 120 | pub fn chain_id(mut self, chain_id: T) -> Self 121 | where 122 | T: Into, 123 | { 124 | self.chain_id = chain_id.into(); 125 | self 126 | } 127 | 128 | pub fn r#type(mut self, r#type: T) -> Self 129 | where 130 | T: Into, 131 | { 132 | self.r#type = r#type.into(); 133 | self 134 | } 135 | 136 | pub fn access_list(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(mut self, max_priority_fee_per_gas: T) -> Self 142 | where 143 | T: Into, 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(mut self, max_fee_per_gas: T) -> Self 150 | where 151 | T: Into, 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 { 168 | self.rlp(None) 169 | } 170 | 171 | pub fn rlp_signed(&self, signature: Signature) -> Result { 172 | self.rlp(Some(signature)) 173 | } 174 | 175 | pub fn rlp(&self, signature: Option) -> Result { 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 | 202 | impl 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 | 224 | impl TryFrom for Eip712TransactionRequest { 225 | type Error = ZKRequestError; 226 | 227 | fn try_from(request: WithdrawRequest) -> Result { 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 | 250 | impl From 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 | 260 | impl TryFrom for Eip712TransactionRequest { 261 | type Error = ZKRequestError; 262 | 263 | fn try_from(request: DeployRequest) -> Result { 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 | } 318 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![forbid(unsafe_code)] 3 | #![recursion_limit = "256"] 4 | #![warn( 5 | clippy::as_conversions, 6 | clippy::as_ptr_cast_mut, 7 | clippy::unnecessary_cast, 8 | clippy::clone_on_ref_ptr, 9 | clippy::create_dir, 10 | clippy::dbg_macro, 11 | clippy::decimal_literal_representation, 12 | clippy::default_numeric_fallback, 13 | clippy::deref_by_slicing, 14 | clippy::empty_structs_with_brackets, 15 | clippy::float_cmp_const, 16 | clippy::fn_to_numeric_cast_any, 17 | clippy::indexing_slicing, 18 | clippy::iter_kv_map, 19 | clippy::manual_clamp, 20 | clippy::manual_filter, 21 | clippy::map_err_ignore, 22 | clippy::uninlined_format_args, 23 | clippy::unseparated_literal_suffix, 24 | clippy::unused_format_specs, 25 | clippy::single_char_lifetime_names, 26 | clippy::string_add, 27 | clippy::string_slice, 28 | clippy::string_to_string, 29 | clippy::todo, 30 | clippy::try_err 31 | )] 32 | #![deny(clippy::unwrap_used, clippy::expect_used)] 33 | #![allow( 34 | clippy::module_inception, 35 | clippy::module_name_repetitions, 36 | clippy::let_underscore_must_use, 37 | clippy::type_complexity, 38 | clippy::too_many_arguments, 39 | clippy::indexing_slicing, 40 | clippy::single_char_lifetime_names 41 | )] 42 | 43 | pub mod abi; 44 | 45 | pub use ethers::*; 46 | 47 | pub mod contracts; 48 | pub mod eip712; 49 | pub mod zks_provider; 50 | pub mod zks_utils; 51 | pub mod zks_wallet; 52 | 53 | pub use zks_wallet::{ZKSWallet, ZKSWalletError}; 54 | 55 | // For macro expansions only, not public API. 56 | #[allow(unused_extern_crates)] 57 | extern crate self as zksync_web3_rs; 58 | #[cfg(test)] 59 | mod tests; 60 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod provider_tests; 2 | mod utils; 3 | mod wallet_tests; 4 | -------------------------------------------------------------------------------- /src/tests/utils.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use ethers::{ 4 | abi::Abi, 5 | prelude::{MiddlewareBuilder, SignerMiddleware}, 6 | providers::{Http, Provider}, 7 | signers::{LocalWallet, Signer, Wallet}, 8 | types::Bytes, 9 | }; 10 | use ethers_contract::core::k256::ecdsa::SigningKey; 11 | use serde::Deserialize; 12 | 13 | use crate::zks_utils::ERA_CHAIN_ID; 14 | 15 | #[derive(Deserialize)] 16 | pub(crate) struct CompiledContract { 17 | pub abi: Abi, 18 | pub bin: Bytes, 19 | } 20 | 21 | pub fn eth_provider() -> Provider { 22 | let url: String = env::var("ZKSYNC_WEB3_RS_L1_PROVIDER_URL").unwrap(); 23 | Provider::try_from(url).unwrap() 24 | } 25 | 26 | pub fn era_provider() -> Provider { 27 | let url: String = env::var("ZKSYNC_WEB3_RS_L2_PROVIDER_URL").unwrap(); 28 | Provider::try_from(url).unwrap() 29 | } 30 | 31 | pub fn local_wallet() -> LocalWallet { 32 | "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110" 33 | .parse::() 34 | .unwrap() 35 | .with_chain_id(ERA_CHAIN_ID) 36 | } 37 | 38 | pub fn era_signer() -> SignerMiddleware, Wallet> { 39 | let signer = Wallet::with_chain_id( 40 | "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110" 41 | .parse::>() 42 | .unwrap(), 43 | ERA_CHAIN_ID, 44 | ); 45 | era_provider().with_signer(signer) 46 | } 47 | -------------------------------------------------------------------------------- /src/tests/wallet_tests.rs: -------------------------------------------------------------------------------- 1 | mod zks_signer_tests { 2 | use crate::tests::utils::*; 3 | use crate::zks_provider::ZKSProvider; 4 | use crate::zks_utils::{ERA_CHAIN_ID, ETH_CHAIN_ID}; 5 | use crate::zks_wallet::{ 6 | CallRequest, DeployRequest, DepositRequest, TransferRequest, WithdrawRequest, ZKSWallet, 7 | }; 8 | use ethers::abi::Tokenize; 9 | use ethers::contract::abigen; 10 | use ethers::providers::Middleware; 11 | use ethers::signers::{LocalWallet, Signer}; 12 | use ethers::types::Address; 13 | use ethers::types::U256; 14 | use ethers::utils::parse_units; 15 | use std::fs::File; 16 | use std::path::PathBuf; 17 | use std::str::FromStr; 18 | use std::sync::Arc; 19 | 20 | abigen!( 21 | ERC20Token, 22 | r#"[ 23 | balanceOf(address)(uint256) 24 | ]"# 25 | ); 26 | 27 | #[tokio::test] 28 | async fn test_transfer() { 29 | let sender_private_key = 30 | "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"; 31 | let receiver_address: Address = "0xa61464658AfeAf65CccaaFD3a512b69A83B77618" 32 | .parse() 33 | .unwrap(); 34 | let amount_to_transfer: U256 = 1_i32.into(); 35 | 36 | let era_provider = era_provider(); 37 | let wallet = LocalWallet::from_str(sender_private_key) 38 | .unwrap() 39 | .with_chain_id(ERA_CHAIN_ID); 40 | let zk_wallet = ZKSWallet::new(wallet, None, Some(era_provider.clone()), None).unwrap(); 41 | 42 | let sender_balance_before = era_provider 43 | .get_balance(zk_wallet.l2_address(), None) 44 | .await 45 | .unwrap(); 46 | let receiver_balance_before = era_provider 47 | .get_balance(receiver_address, None) 48 | .await 49 | .unwrap(); 50 | 51 | println!("Sender balance before: {sender_balance_before}"); 52 | println!("Receiver balance before: {receiver_balance_before}"); 53 | 54 | let request = TransferRequest::new(amount_to_transfer) 55 | .to(receiver_address) 56 | .from(zk_wallet.l2_address()); 57 | let tx_hash = zk_wallet.transfer(&request, None).await.unwrap(); 58 | 59 | let receipt = era_provider 60 | .get_transaction_receipt(tx_hash) 61 | .await 62 | .unwrap() 63 | .unwrap(); 64 | 65 | assert_eq!(receipt.from, zk_wallet.l2_address()); 66 | assert_eq!(receipt.to.unwrap(), receiver_address); 67 | 68 | let sender_balance_after = era_provider 69 | .get_balance(zk_wallet.l2_address(), None) 70 | .await 71 | .unwrap(); 72 | let receiver_balance_after = era_provider 73 | .get_balance(receiver_address, None) 74 | .await 75 | .unwrap(); 76 | 77 | println!("Sender balance after: {sender_balance_after}"); 78 | println!("Receiver balance after: {receiver_balance_after}"); 79 | 80 | assert_eq!( 81 | sender_balance_after, 82 | sender_balance_before 83 | - (amount_to_transfer 84 | + receipt.effective_gas_price.unwrap() * receipt.gas_used.unwrap()) 85 | ); 86 | assert_eq!( 87 | receiver_balance_after, 88 | receiver_balance_before + amount_to_transfer 89 | ); 90 | } 91 | 92 | #[tokio::test] 93 | async fn test_deposit() { 94 | let private_key = "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"; 95 | let request = DepositRequest::new(parse_units("0.01", "ether").unwrap().into()); 96 | println!("Amount: {}", request.amount); 97 | 98 | let l1_provider = eth_provider(); 99 | let l2_provider = era_provider(); 100 | let wallet = LocalWallet::from_str(private_key) 101 | .unwrap() 102 | .with_chain_id(ERA_CHAIN_ID); 103 | let zk_wallet = ZKSWallet::new( 104 | wallet, 105 | None, 106 | Some(l2_provider.clone()), 107 | Some(l1_provider.clone()), 108 | ) 109 | .unwrap(); 110 | 111 | let l1_balance_before = zk_wallet.eth_balance().await.unwrap(); 112 | let l2_balance_before = zk_wallet.era_balance().await.unwrap(); 113 | println!("L1 balance before: {l1_balance_before}"); 114 | println!("L2 balance before: {l2_balance_before}"); 115 | 116 | let tx_hash = zk_wallet.deposit(&request).await.unwrap(); 117 | let receipt = l1_provider 118 | .get_transaction_receipt(tx_hash) 119 | .await 120 | .unwrap() 121 | .unwrap(); 122 | assert_eq!(receipt.status.unwrap(), 1_u8.into()); 123 | 124 | let _l2_receipt = l2_provider 125 | .get_transaction_receipt(receipt.transaction_hash) 126 | .await 127 | .unwrap(); 128 | 129 | let l1_balance_after = zk_wallet.eth_balance().await.unwrap(); 130 | let l2_balance_after = zk_wallet.era_balance().await.unwrap(); 131 | println!("L1 balance after: {l1_balance_after}"); 132 | println!("L2 balance after: {l2_balance_after}"); 133 | 134 | assert!( 135 | l1_balance_after <= l1_balance_before - request.amount(), 136 | "Balance on L1 should be decreased" 137 | ); 138 | assert!( 139 | l2_balance_after >= l2_balance_before + request.amount(), 140 | "Balance on L2 should be increased" 141 | ); 142 | } 143 | 144 | #[tokio::test] 145 | async fn test_deposit_to_another_address() { 146 | let private_key = "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"; 147 | let to: Address = "0xa61464658AfeAf65CccaaFD3a512b69A83B77618" 148 | .parse() 149 | .unwrap(); 150 | let amount = parse_units("0.01", "ether").unwrap().into(); 151 | println!("Amount: {amount}"); 152 | 153 | let request = DepositRequest::new(amount).to(to); 154 | 155 | let l1_provider = eth_provider(); 156 | let l2_provider = era_provider(); 157 | let wallet = LocalWallet::from_str(private_key).unwrap(); 158 | let zk_wallet = ZKSWallet::new( 159 | wallet, 160 | None, 161 | Some(l2_provider.clone()), 162 | Some(l1_provider.clone()), 163 | ) 164 | .unwrap(); 165 | 166 | let l1_balance_before = zk_wallet.eth_balance().await.unwrap(); 167 | let l2_balance_before = era_provider().get_balance(to, None).await.unwrap(); 168 | println!("L1 balance before: {l1_balance_before}"); 169 | println!("L2 balance before: {l2_balance_before}"); 170 | 171 | let tx_hash = zk_wallet.deposit(&request).await.unwrap(); 172 | let receipt = l1_provider 173 | .get_transaction_receipt(tx_hash) 174 | .await 175 | .unwrap() 176 | .unwrap(); 177 | assert_eq!(receipt.status.unwrap(), 1_u8.into()); 178 | 179 | let _l2_receipt = l2_provider 180 | .get_transaction_receipt(receipt.transaction_hash) 181 | .await 182 | .unwrap(); 183 | 184 | let l1_balance_after = zk_wallet.eth_balance().await.unwrap(); 185 | let l2_balance_after = era_provider().get_balance(to, None).await.unwrap(); 186 | println!("L1 balance after: {l1_balance_after}"); 187 | println!("L2 balance after: {l2_balance_after}"); 188 | 189 | assert!( 190 | l1_balance_after <= l1_balance_before - request.amount(), 191 | "Balance on L1 should be decreased" 192 | ); 193 | assert!( 194 | l2_balance_after >= l2_balance_before + request.amount(), 195 | "Balance on L2 should be increased" 196 | ); 197 | } 198 | 199 | #[ignore = "FIXME Implement a fixture that deploys an ERC20 token"] 200 | #[tokio::test] 201 | async fn test_deposit_erc20_token() { 202 | let amount: U256 = 1_i32.into(); 203 | let private_key = "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"; 204 | let l1_provider = eth_provider(); 205 | let l2_provider = era_provider(); 206 | let wallet = LocalWallet::from_str(private_key).unwrap(); 207 | let zk_wallet = ZKSWallet::new( 208 | wallet, 209 | None, 210 | Some(l2_provider.clone()), 211 | Some(l1_provider.clone()), 212 | ) 213 | .unwrap(); 214 | 215 | let token_l1_address: Address = "0xc8F8cE6491227a6a2Ab92e67a64011a4Eba1C6CF" 216 | .parse() 217 | .unwrap(); 218 | 219 | let contract_l1 = ERC20Token::new(token_l1_address, Arc::new(l1_provider.clone())); 220 | 221 | let balance_erc20_l1_before: U256 = contract_l1 222 | .balance_of(zk_wallet.l1_address()) 223 | .call() 224 | .await 225 | .unwrap(); 226 | 227 | let request = DepositRequest::new(amount).token(Some(token_l1_address)); 228 | 229 | let l1_tx_hash = zk_wallet.deposit(&request).await.unwrap(); 230 | let l1_receipt = zk_wallet 231 | .get_eth_provider() 232 | .unwrap() 233 | .get_transaction_receipt(l1_tx_hash) 234 | .await 235 | .unwrap() 236 | .unwrap(); 237 | assert_eq!(l1_receipt.status.unwrap(), 1_i32.into()); 238 | 239 | let balance_erc20_l1_after: U256 = contract_l1 240 | .balance_of(zk_wallet.l1_address()) 241 | .call() 242 | .await 243 | .unwrap(); 244 | 245 | assert_eq!(balance_erc20_l1_after, balance_erc20_l1_before - amount); 246 | // FIXME check balance on l2. 247 | } 248 | 249 | #[tokio::test] 250 | async fn test_transfer_eip712() { 251 | let sender_private_key = 252 | "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"; 253 | let receiver_address: Address = "0xa61464658AfeAf65CccaaFD3a512b69A83B77618" 254 | .parse() 255 | .unwrap(); 256 | let amount_to_transfer: U256 = 1_i32.into(); 257 | 258 | let era_provider = era_provider(); 259 | let wallet = LocalWallet::from_str(sender_private_key) 260 | .unwrap() 261 | .with_chain_id(ERA_CHAIN_ID); 262 | let zk_wallet = ZKSWallet::new(wallet, None, Some(era_provider.clone()), None).unwrap(); 263 | 264 | let sender_balance_before = era_provider 265 | .get_balance(zk_wallet.l2_address(), None) 266 | .await 267 | .unwrap(); 268 | let receiver_balance_before = era_provider 269 | .get_balance(receiver_address, None) 270 | .await 271 | .unwrap(); 272 | 273 | println!("Sender balance before: {sender_balance_before}"); 274 | println!("Receiver balance before: {receiver_balance_before}"); 275 | 276 | let transfer_request = TransferRequest::new(amount_to_transfer) 277 | .to(receiver_address) 278 | .from(zk_wallet.l2_address()); 279 | let tx_hash = zk_wallet 280 | .transfer_eip712(&transfer_request, None) 281 | .await 282 | .unwrap(); 283 | 284 | let receipt = era_provider 285 | .get_transaction_receipt(tx_hash) 286 | .await 287 | .unwrap() 288 | .unwrap(); 289 | assert_eq!(receipt.from, zk_wallet.l2_address()); 290 | assert_eq!(receipt.to.unwrap(), receiver_address); 291 | 292 | let sender_balance_after = era_provider 293 | .get_balance(zk_wallet.l2_address(), None) 294 | .await 295 | .unwrap(); 296 | let receiver_balance_after = era_provider 297 | .get_balance(receiver_address, None) 298 | .await 299 | .unwrap(); 300 | 301 | println!("Sender balance after: {sender_balance_after}"); 302 | println!("Receiver balance after: {receiver_balance_after}"); 303 | 304 | assert_eq!( 305 | sender_balance_after, 306 | sender_balance_before 307 | - (amount_to_transfer 308 | + receipt.effective_gas_price.unwrap() * receipt.gas_used.unwrap()) 309 | ); 310 | assert_eq!( 311 | receiver_balance_after, 312 | receiver_balance_before + amount_to_transfer 313 | ); 314 | } 315 | 316 | #[tokio::test] 317 | async fn test_deploy_contract_with_constructor_arg_uint() { 318 | let deployer_private_key = 319 | "7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"; 320 | let era_provider = era_provider(); 321 | let wallet = LocalWallet::from_str(deployer_private_key) 322 | .unwrap() 323 | .with_chain_id(ERA_CHAIN_ID); 324 | let zk_wallet = ZKSWallet::new(wallet, None, Some(era_provider.clone()), None).unwrap(); 325 | 326 | let mut contract_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 327 | contract_path.push("src/abi/test_contracts/storage_combined.json"); 328 | let contract: CompiledContract = 329 | serde_json::from_reader(File::open(contract_path).unwrap()).unwrap(); 330 | 331 | let deploy_request = 332 | DeployRequest::with(contract.abi, contract.bin.to_vec(), vec!["10".to_owned()]) 333 | .from(zk_wallet.l2_address()); 334 | let contract_address = zk_wallet.deploy(&deploy_request).await.unwrap(); 335 | let deploy_result = era_provider.get_code(contract_address, None).await; 336 | 337 | assert!(deploy_result.is_ok()); 338 | } 339 | 340 | #[tokio::test] 341 | async fn test_deploy_contract_with_constructor_arg_string() { 342 | let deployer_private_key = 343 | "7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"; 344 | let era_provider = era_provider(); 345 | let wallet = LocalWallet::from_str(deployer_private_key) 346 | .unwrap() 347 | .with_chain_id(ERA_CHAIN_ID); 348 | let zk_wallet = ZKSWallet::new(wallet, None, Some(era_provider.clone()), None).unwrap(); 349 | 350 | let mut contract_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 351 | contract_path.push("src/abi/test_contracts/greeter_combined.json"); 352 | let contract: CompiledContract = 353 | serde_json::from_reader(File::open(contract_path).unwrap()).unwrap(); 354 | 355 | let deploy_request = 356 | DeployRequest::with(contract.abi, contract.bin.to_vec(), vec!["Hey".to_owned()]) 357 | .from(zk_wallet.l2_address()); 358 | let contract_address = zk_wallet.deploy(&deploy_request).await.unwrap(); 359 | let deploy_result = era_provider.get_code(contract_address, None).await; 360 | 361 | assert!(deploy_result.is_ok()); 362 | } 363 | 364 | #[tokio::test] 365 | async fn test_deploy_contract_with_import() { 366 | let deployer_private_key = 367 | "7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"; 368 | let era_provider = era_provider(); 369 | let wallet = LocalWallet::from_str(deployer_private_key) 370 | .unwrap() 371 | .with_chain_id(ERA_CHAIN_ID); 372 | let zk_wallet = ZKSWallet::new(wallet, None, Some(era_provider.clone()), None).unwrap(); 373 | 374 | // Deploy imported contract first. 375 | let mut contract_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 376 | contract_path.push("src/abi/test_contracts/counter_combined.json"); 377 | let counter_contract: CompiledContract = 378 | serde_json::from_reader(File::open(contract_path).unwrap()).unwrap(); 379 | 380 | let deploy_request = 381 | DeployRequest::with(counter_contract.abi, counter_contract.bin.to_vec(), vec![]) 382 | .from(zk_wallet.l2_address()); 383 | let counter_contract_address = zk_wallet.deploy(&deploy_request).await.unwrap(); 384 | let deploy_result = era_provider.get_code(counter_contract_address, None).await; 385 | 386 | assert!(deploy_result.is_ok()); 387 | 388 | // Deploy another contract that imports the previous one. 389 | let mut contract_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 390 | contract_path.push("src/abi/test_contracts/import_combined.json"); 391 | 392 | let import_contract: CompiledContract = 393 | serde_json::from_reader(File::open(contract_path).unwrap()).unwrap(); 394 | 395 | let deploy_request = DeployRequest::with( 396 | import_contract.abi, 397 | import_contract.bin.to_vec(), 398 | vec![format!("{counter_contract_address:?}")], 399 | ) 400 | .from(zk_wallet.l2_address()); 401 | let import_contract_address = zk_wallet.deploy(&deploy_request).await.unwrap(); 402 | let call_request = CallRequest::new( 403 | import_contract_address, 404 | "getCounterValue()(uint256)".to_owned(), 405 | ); 406 | let value = ZKSProvider::call(&era_provider, &call_request) 407 | .await 408 | .unwrap(); 409 | 410 | assert_eq!(value, U256::from(0_u64).into_tokens()); 411 | } 412 | 413 | #[tokio::test] 414 | async fn test_withdraw_to_same_address() { 415 | let sender_private_key = 416 | "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"; 417 | let wallet = LocalWallet::from_str(sender_private_key) 418 | .unwrap() 419 | .with_chain_id(ERA_CHAIN_ID); 420 | let zk_wallet = 421 | ZKSWallet::new(wallet, None, Some(era_provider()), Some(eth_provider())).unwrap(); 422 | 423 | // See balances before withdraw 424 | let l1_balance_before = zk_wallet.eth_balance().await.unwrap(); 425 | let l2_balance_before = zk_wallet.era_balance().await.unwrap(); 426 | 427 | println!("Balance on L1 before withdrawal: {l1_balance_before}"); 428 | println!("Balance on L2 before withdrawal: {l2_balance_before}"); 429 | 430 | // Withdraw 431 | let amount_to_withdraw: U256 = parse_units(1_u8, "ether").unwrap().into(); 432 | 433 | let withdraw_request = WithdrawRequest::new(amount_to_withdraw).to(zk_wallet.l1_address()); 434 | let tx_hash = zk_wallet.withdraw(&withdraw_request).await.unwrap(); 435 | let tx_receipt = zk_wallet 436 | .get_era_provider() 437 | .unwrap() 438 | .wait_for_finalize(tx_hash, None, None) 439 | .await 440 | .unwrap(); 441 | assert_eq!( 442 | 1, 443 | tx_receipt.status.unwrap().as_u64(), 444 | "Check that transaction in L2 is successful" 445 | ); 446 | 447 | println!("L2 Transaction hash: {:?}", tx_receipt.transaction_hash); 448 | 449 | let l2_balance_after_withdraw = zk_wallet.era_balance().await.unwrap(); 450 | let l1_balance_after_withdraw = zk_wallet.eth_balance().await.unwrap(); 451 | 452 | assert_eq!( 453 | l2_balance_after_withdraw, 454 | l2_balance_before 455 | - (amount_to_withdraw + tx_receipt.effective_gas_price.unwrap() * tx_receipt.gas_used.unwrap()), 456 | "Check that L2 balance inmediately after withdrawal has decreased by the used gas and amount" 457 | ); 458 | 459 | assert_eq!( 460 | l1_balance_before, l1_balance_after_withdraw, 461 | "Check that L1 balance has not changed" 462 | ); 463 | 464 | let tx_finalize_hash = zk_wallet.finalize_withdraw(tx_hash).await.unwrap(); 465 | 466 | let tx_finalize_receipt = zk_wallet 467 | .get_eth_provider() 468 | .unwrap() 469 | .get_transaction_receipt(tx_finalize_hash) 470 | .await 471 | .unwrap() 472 | .unwrap(); 473 | println!( 474 | "L1 Transaction hash: {:?}", 475 | tx_finalize_receipt.transaction_hash 476 | ); 477 | 478 | assert_eq!( 479 | 1, 480 | tx_finalize_receipt.status.unwrap().as_u64(), 481 | "Check that transaction in L1 is successful" 482 | ); 483 | 484 | // See balances after withdraw 485 | let l1_balance_after_finalize = zk_wallet.eth_balance().await.unwrap(); 486 | let l2_balance_after_finalize = zk_wallet.era_balance().await.unwrap(); 487 | 488 | println!("Balance on L1 after finalize withdraw: {l1_balance_after_finalize}"); 489 | println!("Balance on L2 after finalize withdraw: {l2_balance_after_finalize}"); 490 | 491 | assert_eq!( 492 | l2_balance_after_finalize, l2_balance_after_withdraw, 493 | "Check that L2 balance after finalize has decreased by the used gas" 494 | ); 495 | 496 | assert_ne!( 497 | l1_balance_after_finalize, l1_balance_before, 498 | "Check that L1 balance after finalize is not the same" 499 | ); 500 | assert_eq!( 501 | l1_balance_after_finalize, 502 | l1_balance_before 503 | + (amount_to_withdraw 504 | - tx_finalize_receipt.effective_gas_price.unwrap() 505 | * tx_finalize_receipt.gas_used.unwrap()), 506 | "Check that L1 balance after finalize has increased by the amount" 507 | ); 508 | } 509 | 510 | #[tokio::test] 511 | async fn test_withdraw_to_other_address() { 512 | let sender_private_key = 513 | "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"; 514 | let receiver_private_key = 515 | "0xe667e57a9b8aaa6709e51ff7d093f1c5b73b63f9987e4ab4aa9a5c699e024ee8"; 516 | let l2_wallet = LocalWallet::from_str(sender_private_key) 517 | .unwrap() 518 | .with_chain_id(ERA_CHAIN_ID); 519 | 520 | let l1_wallet = LocalWallet::from_str(receiver_private_key) 521 | .unwrap() 522 | .with_chain_id(ETH_CHAIN_ID); 523 | let zk_wallet = ZKSWallet::new( 524 | l2_wallet, 525 | Some(l1_wallet), 526 | Some(era_provider()), 527 | Some(eth_provider()), 528 | ) 529 | .unwrap(); 530 | 531 | // See balances before withdraw 532 | let l1_balance_before = zk_wallet.eth_balance().await.unwrap(); 533 | let l2_balance_before = zk_wallet.era_balance().await.unwrap(); 534 | 535 | println!("Balance on L1 before withdrawal: {l1_balance_before}"); 536 | println!("Balance on L2 before withdrawal: {l2_balance_before}"); 537 | 538 | // Withdraw 539 | let amount_to_withdraw: U256 = parse_units(1_u8, "ether").unwrap().into(); 540 | let withdraw_request = WithdrawRequest::new(amount_to_withdraw).to(zk_wallet.l1_address()); 541 | let tx_receipt = zk_wallet.withdraw(&withdraw_request).await.unwrap(); 542 | let tx_receipt = zk_wallet 543 | .get_era_provider() 544 | .unwrap() 545 | .wait_for_finalize(tx_receipt, None, None) 546 | .await 547 | .unwrap(); 548 | assert_eq!( 549 | 1, 550 | tx_receipt.status.unwrap().as_u64(), 551 | "Check that transaction in L2 is successful" 552 | ); 553 | 554 | println!("L2 Transaction hash: {:?}", tx_receipt.transaction_hash); 555 | 556 | let l2_balance_after_withdraw = zk_wallet.era_balance().await.unwrap(); 557 | let l1_balance_after_withdraw = zk_wallet.eth_balance().await.unwrap(); 558 | 559 | assert_eq!( 560 | l2_balance_after_withdraw, 561 | l2_balance_before 562 | - (amount_to_withdraw + tx_receipt.effective_gas_price.unwrap() * tx_receipt.gas_used.unwrap()), 563 | "Check that L2 balance inmediately after withdrawal has decreased by the used gas and amount" 564 | ); 565 | 566 | assert_eq!( 567 | l1_balance_before, l1_balance_after_withdraw, 568 | "Check that L1 balance has not changed" 569 | ); 570 | 571 | let tx_finalize_hash = zk_wallet 572 | .finalize_withdraw(tx_receipt.transaction_hash) 573 | .await 574 | .unwrap(); 575 | 576 | let tx_finalize_receipt = zk_wallet 577 | .get_eth_provider() 578 | .unwrap() 579 | .get_transaction_receipt(tx_finalize_hash) 580 | .await 581 | .unwrap() 582 | .unwrap(); 583 | println!( 584 | "L1 Transaction hash: {:?}", 585 | tx_finalize_receipt.transaction_hash 586 | ); 587 | 588 | assert_eq!( 589 | 1, 590 | tx_finalize_receipt.status.unwrap().as_u64(), 591 | "Check that transaction in L1 is successful" 592 | ); 593 | 594 | // See balances after withdraw 595 | let l1_balance_after_finalize = zk_wallet.eth_balance().await.unwrap(); 596 | let l2_balance_after_finalize = zk_wallet.era_balance().await.unwrap(); 597 | 598 | println!("Balance on L1 after finalize withdraw: {l1_balance_after_finalize}"); 599 | println!("Balance on L2 after finalize withdraw: {l2_balance_after_finalize}"); 600 | 601 | assert_eq!( 602 | l2_balance_after_finalize, l2_balance_after_withdraw, 603 | "Check that L2 balance after finalize has decreased by the used gas" 604 | ); 605 | 606 | assert_ne!( 607 | l1_balance_after_finalize, l1_balance_before, 608 | "Check that L1 balance after finalize is not the same" 609 | ); 610 | assert_eq!( 611 | l1_balance_after_finalize, 612 | l1_balance_before 613 | + (amount_to_withdraw 614 | - tx_finalize_receipt.effective_gas_price.unwrap() 615 | * tx_finalize_receipt.gas_used.unwrap()), 616 | "Check that L1 balance after finalize has increased by the amount" 617 | ); 618 | } 619 | } 620 | -------------------------------------------------------------------------------- /src/zks_provider/types.rs: -------------------------------------------------------------------------------- 1 | use ethers::types::{Address, Bytes, H256, U256, U64}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::collections::HashMap; 4 | 5 | #[derive(Serialize, Deserialize, Debug, Clone)] 6 | pub struct Fee { 7 | pub gas_limit: U256, 8 | pub gas_per_pubdata_limit: U256, 9 | pub max_fee_per_gas: U256, 10 | pub max_priority_fee_per_gas: U256, 11 | } 12 | 13 | impl Copy for Fee {} 14 | 15 | #[derive(Serialize, Deserialize, Debug, Clone)] 16 | #[serde(rename_all = "camelCase")] 17 | pub struct BlockDetails { 18 | pub base_system_contracts_hashes: BaseSystemContractsHashes, 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub commit_tx_hash: Option, 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | pub committed_at: Option, 23 | #[serde(skip_serializing_if = "Option::is_none")] 24 | pub execute_tx_hash: Option, 25 | #[serde(skip_serializing_if = "Option::is_none")] 26 | pub executed_at: Option, 27 | pub l1_batch_number: u128, 28 | pub l1_gas_price: u128, 29 | pub l1_tx_count: u128, 30 | pub l2_fair_gas_price: u128, 31 | pub l2_tx_count: u128, 32 | pub number: u128, 33 | pub operator_address: Address, 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub prove_tx_hash: Option, 36 | #[serde(skip_serializing_if = "Option::is_none")] 37 | pub proven_at: Option, 38 | pub root_hash: H256, 39 | pub status: String, 40 | pub timestamp: u128, 41 | } 42 | 43 | #[derive(Serialize, Deserialize, Debug, Clone)] 44 | pub struct BaseSystemContractsHashes { 45 | pub bootloader: H256, 46 | pub default_aa: H256, 47 | } 48 | 49 | impl Copy for BaseSystemContractsHashes {} 50 | 51 | #[derive(Serialize, Deserialize, Debug, Clone)] 52 | #[serde(rename_all = "camelCase")] 53 | pub struct BridgeContracts { 54 | pub l1_erc20_default_bridge: Address, 55 | pub l2_erc20_default_bridge: Address, 56 | } 57 | 58 | #[derive(Serialize, Deserialize, Debug, Clone)] 59 | #[serde(rename_all = "camelCase")] 60 | pub struct TokenInfo { 61 | pub decimals: U64, 62 | pub l1_address: Address, 63 | pub l2_address: Address, 64 | pub name: String, 65 | pub symbol: String, 66 | } 67 | 68 | pub type BlockRange = Vec; 69 | 70 | // TODO: Complete struct. 71 | #[derive(Serialize, Deserialize, Debug, Clone)] 72 | #[serde(rename_all = "camelCase")] 73 | pub struct L1BatchDetails { 74 | pub base_system_contracts_hashes: BaseSystemContractsHashes, 75 | pub commit_tx_hash: H256, 76 | pub committed_at: String, 77 | pub execute_tx_hash: H256, 78 | pub executed_at: String, 79 | pub l1_gas_price: u128, 80 | pub l1_tx_count: u128, 81 | pub l2_fair_gas_price: u128, 82 | pub l2_tx_count: u128, 83 | pub number: u128, 84 | pub prove_tx_hash: H256, 85 | pub proven_at: String, 86 | pub root_hash: H256, 87 | pub status: String, 88 | pub timestamp: u128, 89 | } 90 | 91 | #[derive(Serialize, Deserialize, Debug, Clone)] 92 | #[serde(rename_all = "camelCase")] 93 | pub struct Proof { 94 | pub id: u64, 95 | #[serde(rename = "proof")] 96 | pub merkle_proof: Vec, 97 | pub root: Bytes, 98 | } 99 | 100 | // TODO: Complete struct. 101 | #[derive(Serialize, Deserialize, Debug, Clone)] 102 | pub struct Transaction { 103 | pub common_data: CommonData, 104 | pub execute: Execute, 105 | pub received_timestamp_ms: u64, 106 | } 107 | 108 | #[derive(Serialize, Deserialize, Debug, Clone)] 109 | pub struct CommonData { 110 | #[serde(rename = "L1")] 111 | pub l1: L1, 112 | } 113 | 114 | #[derive(Serialize, Deserialize, Debug, Clone)] 115 | #[serde(rename_all = "camelCase")] 116 | pub struct L1 { 117 | pub canonical_tx_hash: H256, 118 | pub deadline_block: u64, 119 | pub eth_block: u64, 120 | pub eth_hash: H256, 121 | pub full_fee: U256, 122 | pub gas_limit: U256, 123 | pub gas_per_pubdata_limit: U256, 124 | pub layer2_tip_fee: U256, 125 | pub max_fee_per_gas: U256, 126 | pub op_processing_type: String, 127 | pub priority_queue_type: String, 128 | pub refund_recipient: Address, 129 | pub sender: Address, 130 | pub serial_id: u64, 131 | pub to_mint: U256, 132 | } 133 | 134 | #[derive(Serialize, Deserialize, Debug, Clone)] 135 | #[serde(rename_all = "camelCase")] 136 | pub struct Execute { 137 | pub calldata: Bytes, 138 | pub contract_address: Address, 139 | pub factory_deps: Vec>, 140 | pub value: U256, 141 | } 142 | 143 | #[derive(Serialize, Deserialize, Debug, Clone)] 144 | #[serde(rename_all = "camelCase")] 145 | pub struct TransactionDetails { 146 | pub eth_commit_tx_hash: H256, 147 | pub eth_execute_tx_hash: H256, 148 | pub eth_prove_tx_hash: H256, 149 | pub fee: U256, 150 | pub initiator_address: Address, 151 | pub is_l1_originated: bool, 152 | pub received_at: String, 153 | pub status: String, 154 | } 155 | 156 | #[derive(Serialize, Deserialize, Debug, Clone)] 157 | #[serde(rename_all = "camelCase")] 158 | pub struct TracerConfig { 159 | #[serde(skip_serializing_if = "Option::is_none")] 160 | pub disable_storage: Option, 161 | #[serde(skip_serializing_if = "Option::is_none")] 162 | pub disable_stack: Option, 163 | #[serde(skip_serializing_if = "Option::is_none")] 164 | pub enable_memory: Option, 165 | #[serde(skip_serializing_if = "Option::is_none")] 166 | pub enable_return_data: Option, 167 | #[serde(skip_serializing_if = "Option::is_none")] 168 | pub tracer: Option, 169 | #[serde(skip_serializing_if = "Option::is_none")] 170 | pub tracer_config: Option>, 171 | } 172 | 173 | // TODO: Check correct types for the ones using serde_json::Value. 174 | #[derive(Serialize, Deserialize, Debug, Clone)] 175 | #[serde(rename_all = "camelCase")] 176 | pub struct DebugTrace { 177 | calls: Vec, 178 | error: Option, 179 | from: Address, 180 | gas: U256, 181 | gas_used: U256, 182 | input: Bytes, 183 | output: Bytes, 184 | revert_reason: Option, 185 | to: Address, 186 | r#type: String, 187 | value: U256, 188 | } 189 | -------------------------------------------------------------------------------- /src/zks_utils.rs: -------------------------------------------------------------------------------- 1 | use ethers::{ 2 | abi::{ 3 | encode, 4 | token::{LenientTokenizer, StrictTokenizer, Tokenizer}, 5 | Constructor, Function, Param, ParamType, Token, 6 | }, 7 | types::{Address, H160, U256}, 8 | }; 9 | use ethers_contract::AbiError; 10 | use std::str::FromStr; 11 | 12 | /* Misc */ 13 | 14 | pub const ETH_CHAIN_ID: u16 = 0x9; 15 | pub const ERA_CHAIN_ID: u16 = 0x10E; 16 | pub const ERA_MAINNET_CHAIN_ID: u16 = 324; 17 | 18 | pub const EIP712_TX_TYPE: u8 = 0x71; 19 | // The large L2 gas per pubdata to sign. This gas is enough to ensure that 20 | // any reasonable limit will be accepted. Note, that the operator is NOT required to 21 | // use the honest value of gas per pubdata and it can use any value up to the one signed by the user. 22 | // In the future releases, we will provide a way to estimate the current gasPerPubdata. 23 | pub const DEFAULT_GAS_PER_PUBDATA_LIMIT: u64 = 50000; 24 | pub const MAX_PRIORITY_FEE_PER_GAS: u64 = 1063439364; 25 | pub const MAX_FEE_PER_GAS: u64 = 1063439378; 26 | pub const DEFAULT_GAS: u64 = 91435; 27 | /// This the number of pubdata such that it should be always possible to publish 28 | /// from a single transaction. Note, that these pubdata bytes include only bytes that are 29 | /// to be published inside the body of transaction (i.e. excluding of factory deps). 30 | pub const GUARANTEED_PUBDATA_PER_L1_BATCH: u64 = 4000; 31 | pub const MAX_L2_TX_GAS_LIMIT: u64 = 80000000; 32 | // The users should always be able to provide `MAX_GAS_PER_PUBDATA_BYTE` gas per pubdata in their 33 | // transactions so that they are able to send at least GUARANTEED_PUBDATA_PER_L1_BATCH bytes per 34 | // transaction. 35 | pub const MAX_GAS_PER_PUBDATA_BYTE: u64 = MAX_L2_TX_GAS_LIMIT / GUARANTEED_PUBDATA_PER_L1_BATCH; 36 | 37 | pub const RECOMMENDED_DEPOSIT_L1_GAS_LIMIT: u64 = 10000000; 38 | pub const RECOMMENDED_DEPOSIT_L2_GAS_LIMIT: u64 = 10000000; 39 | pub const DEPOSIT_GAS_PER_PUBDATA_LIMIT: u64 = 800; 40 | pub const DEFAULT_ERC20_DEPOSIT_GAS_LIMIT: u64 = 300000_u64; 41 | 42 | /* Contracts */ 43 | 44 | pub const CHAIN_STATE_KEEPER_BOOTLOADER_HASH: &str = 45 | "0x0100038581be3d0e201b3cc45d151ef5cc59eb3a0f146ad44f0f72abf00b594c"; 46 | pub const CHAIN_STATE_KEEPER_DEFAULT_AA_HASH: &str = 47 | "0x0100038dc66b69be75ec31653c64cb931678299b9b659472772b2550b703f41c"; 48 | 49 | pub const CONTRACT_DEPLOYER_ADDR: &str = "0x0000000000000000000000000000000000008006"; 50 | pub const CONTRACTS_DIAMOND_INIT_ADDR: &str = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9"; 51 | pub const CONTRACTS_DIAMOND_UPGRADE_INIT_ADDR: &str = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9"; 52 | pub const CONTRACTS_MAILBOX_FACET_ADDR: &str = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9"; 53 | pub const CONTRACTS_DIAMOND_CUT_FACET_ADDR: &str = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9"; 54 | pub const CONTRACTS_EXECUTOR_FACET_ADDR: &str = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9"; 55 | pub const CONTRACTS_GOVERNANCE_FACET_ADDR: &str = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9"; 56 | pub const CONTRACTS_GETTERS_FACET_ADDR: &str = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9"; 57 | pub const CONTRACTS_VERIFIER_ADDR: &str = "0xDAbb67b676F5b01FcC8997Cc8439846D0d8078ca"; 58 | pub const CONTRACTS_DIAMOND_PROXY_ADDR: &str = "0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF"; 59 | pub const CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR: &str = "0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF"; 60 | pub const CONTRACTS_L1_ERC20_BRIDGE_IMPL_ADDR: &str = "0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF"; 61 | pub const CONTRACTS_L2_ERC20_BRIDGE_ADDR: &str = "0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF"; 62 | pub const CONTRACTS_L2_TESTNET_PAYMASTER_ADDR: &str = "0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF"; 63 | pub const CONTRACTS_L1_ALLOW_LIST_ADDR: &str = "0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF"; 64 | pub const CONTRACTS_CREATE2_FACTORY_ADDR: &str = "0xce0042B868300000d44A59004Da54A005ffdcf9f"; 65 | pub const CONTRACTS_VALIDATOR_TIMELOCK_ADDR: &str = "0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF"; 66 | pub const CONTRACTS_L1_WETH_BRIDGE_IMPL_ADDR: &str = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9"; 67 | pub const CONTRACTS_L1_WETH_BRIDGE_PROXY_ADDR: &str = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9"; 68 | pub const CONTRACTS_L1_WETH_TOKEN_ADDR: &str = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9"; 69 | pub const CONTRACTS_L2_ETH_TOKEN_ADDR: &str = "0x000000000000000000000000000000000000800a"; 70 | pub const CONTRACTS_L1_MESSENGER_ADDR: &str = "0x0000000000000000000000000000000000008008"; 71 | 72 | pub const ETHER_L1_ADDRESS: Address = H160([ 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 74 | 0x00, 0x00, 0x00, 0x00, 75 | ]); 76 | 77 | /* Precompiles */ 78 | 79 | pub const ECRECOVER_PRECOMPILE_ADDRESS: Address = H160([ 80 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x01, 82 | ]); 83 | 84 | pub const SHA256_PRECOMPILE_ADDRESS: Address = H160([ 85 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 86 | 0x00, 0x00, 0x00, 0x02, 87 | ]); 88 | 89 | pub const RIPEMD_160_PRECOMPILE_ADDRESS: Address = H160([ 90 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 91 | 0x00, 0x00, 0x00, 0x03, 92 | ]); 93 | 94 | pub const IDENTITY_PRECOMPILE_ADDRESS: Address = H160([ 95 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 96 | 0x00, 0x00, 0x00, 0x04, 97 | ]); 98 | 99 | pub const MODEXP_PRECOMPILE_ADDRESS: Address = H160([ 100 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 101 | 0x00, 0x00, 0x00, 0x05, 102 | ]); 103 | 104 | pub const ECADD_PRECOMPILE_ADDRESS: Address = H160([ 105 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 106 | 0x00, 0x00, 0x00, 0x06, 107 | ]); 108 | 109 | pub const ECMUL_PRECOMPILE_ADDRESS: Address = H160([ 110 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 111 | 0x00, 0x00, 0x00, 0x07, 112 | ]); 113 | 114 | pub const ECPAIRING_PRECOMPILE_ADDRESS: Address = H160([ 115 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 116 | 0x00, 0x00, 0x00, 0x08, 117 | ]); 118 | 119 | pub const BLAKE2F_PRECOMPILE_ADDRESS: Address = H160([ 120 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 121 | 0x00, 0x00, 0x00, 0x09, 122 | ]); 123 | 124 | pub fn is_precompile(address: Address) -> bool { 125 | address == ECRECOVER_PRECOMPILE_ADDRESS 126 | || address == SHA256_PRECOMPILE_ADDRESS 127 | || address == RIPEMD_160_PRECOMPILE_ADDRESS 128 | || address == IDENTITY_PRECOMPILE_ADDRESS 129 | || address == MODEXP_PRECOMPILE_ADDRESS 130 | || address == ECADD_PRECOMPILE_ADDRESS 131 | || address == ECMUL_PRECOMPILE_ADDRESS 132 | || address == ECPAIRING_PRECOMPILE_ADDRESS 133 | || address == BLAKE2F_PRECOMPILE_ADDRESS 134 | } 135 | 136 | /// Given a function and a vector of string arguments, it proceeds to convert the args to ethabi 137 | /// Tokens and then ABI encode them. 138 | /// > This function was taken from foundry. 139 | pub fn encode_args(func: &Function, args: &[impl AsRef]) -> Result, AbiError> { 140 | let params = func 141 | .inputs 142 | .iter() 143 | .zip(args) 144 | .map(|(input, arg)| (&input.kind, arg.as_ref())) 145 | .collect::>(); 146 | let tokens = parse_tokens(params, true)?; 147 | Ok(encode(&tokens)) 148 | } 149 | 150 | /// Given a constructor and a vector of string arguments, it proceeds to convert the args to ethabi 151 | /// Tokens and then ABI encode them. 152 | pub fn encode_constructor_args( 153 | constructor: &Constructor, 154 | args: &[impl AsRef], 155 | ) -> Result, AbiError> { 156 | let params = constructor 157 | .inputs 158 | .iter() 159 | .zip(args) 160 | .map(|(input, arg)| (&input.kind, arg.as_ref())) 161 | .collect::>(); 162 | let tokens = parse_tokens(params, true)?; 163 | Ok(encode(&tokens)) 164 | } 165 | 166 | /// Parses string input as Token against the expected ParamType 167 | /// > This function was taken from foundry. 168 | pub fn parse_tokens<'a, I: IntoIterator>( 169 | params: I, 170 | lenient: bool, 171 | ) -> Result, AbiError> { 172 | let mut tokens = Vec::new(); 173 | 174 | for (param, value) in params.into_iter() { 175 | let mut token = if lenient { 176 | LenientTokenizer::tokenize(param, value) 177 | } else { 178 | StrictTokenizer::tokenize(param, value) 179 | }; 180 | if token.is_err() && value.starts_with("0x") { 181 | match param { 182 | ParamType::FixedBytes(32) => { 183 | if value.len() < 66 { 184 | let padded_value = [value, &"0".repeat(66 - value.len())].concat(); 185 | token = if lenient { 186 | LenientTokenizer::tokenize(param, &padded_value) 187 | } else { 188 | StrictTokenizer::tokenize(param, &padded_value) 189 | }; 190 | } 191 | } 192 | ParamType::Uint(_) => { 193 | // try again if value is hex 194 | if let Ok(value) = U256::from_str(value).map(|v| v.to_string()) { 195 | token = if lenient { 196 | LenientTokenizer::tokenize(param, &value) 197 | } else { 198 | StrictTokenizer::tokenize(param, &value) 199 | }; 200 | } 201 | } 202 | // TODO: Not sure what to do here. Put the no effect in for now, but that is not 203 | // ideal. We could attempt massage for every value type? 204 | _ => {} 205 | } 206 | } 207 | 208 | let token = token.map(sanitize_token)?; 209 | tokens.push(token); 210 | } 211 | Ok(tokens) 212 | } 213 | 214 | /// Cleans up potential shortcomings of the ethabi Tokenizer. 215 | /// 216 | /// For example: parsing a string array with a single empty string: `[""]`, is returned as 217 | /// 218 | /// ```text 219 | /// [ 220 | /// String( 221 | /// "\"\"", 222 | /// ), 223 | /// ], 224 | /// ``` 225 | /// 226 | /// But should just be 227 | /// 228 | /// ```text 229 | /// [ 230 | /// String( 231 | /// "", 232 | /// ), 233 | /// ], 234 | /// ``` 235 | /// 236 | /// This will handle this edge case 237 | /// > This function was taken from foundry. 238 | pub fn sanitize_token(token: Token) -> Token { 239 | match token { 240 | Token::Array(tokens) => { 241 | let mut sanitized = Vec::with_capacity(tokens.len()); 242 | for token in tokens { 243 | let token = match token { 244 | Token::String(val) => { 245 | let val = match val.as_str() { 246 | // this is supposed to be an empty string 247 | "\"\"" | "''" => "".to_owned(), 248 | _ => val, 249 | }; 250 | Token::String(val) 251 | } 252 | _ => sanitize_token(token), 253 | }; 254 | sanitized.push(token) 255 | } 256 | Token::Array(sanitized) 257 | } 258 | _ => token, 259 | } 260 | } 261 | 262 | pub fn ec_add_function() -> Function { 263 | #[allow(deprecated)] 264 | Function { 265 | name: "".to_owned(), 266 | inputs: vec![ 267 | Param { 268 | name: "".to_owned(), 269 | kind: ParamType::Int(256), 270 | internal_type: Some("sint256".to_owned()), 271 | }, 272 | Param { 273 | name: "".to_owned(), 274 | kind: ParamType::Int(256), 275 | internal_type: Some("sint256".to_owned()), 276 | }, 277 | Param { 278 | name: "".to_owned(), 279 | kind: ParamType::Int(256), 280 | internal_type: Some("sint256".to_owned()), 281 | }, 282 | Param { 283 | name: "".to_owned(), 284 | kind: ParamType::Int(256), 285 | internal_type: Some("sint256".to_owned()), 286 | }, 287 | ], 288 | outputs: vec![ 289 | Param { 290 | name: "".to_owned(), 291 | kind: ParamType::Int(256), 292 | internal_type: Some("sint256".to_owned()), 293 | }, 294 | Param { 295 | name: "".to_owned(), 296 | kind: ParamType::Int(256), 297 | internal_type: Some("sint256".to_owned()), 298 | }, 299 | ], 300 | state_mutability: ethers::abi::StateMutability::Payable, 301 | constant: None, 302 | } 303 | } 304 | 305 | pub fn ec_mul_function() -> Function { 306 | #[allow(deprecated)] 307 | Function { 308 | name: "".to_owned(), 309 | inputs: vec![ 310 | Param { 311 | name: "".to_owned(), 312 | kind: ParamType::Int(256), 313 | internal_type: Some("sint256".to_owned()), 314 | }, 315 | Param { 316 | name: "".to_owned(), 317 | kind: ParamType::Int(256), 318 | internal_type: Some("sint256".to_owned()), 319 | }, 320 | Param { 321 | name: "".to_owned(), 322 | kind: ParamType::Uint(256), 323 | internal_type: Some("uint256".to_owned()), 324 | }, 325 | ], 326 | outputs: vec![ 327 | Param { 328 | name: "".to_owned(), 329 | kind: ParamType::Int(256), 330 | internal_type: Some("sint256".to_owned()), 331 | }, 332 | Param { 333 | name: "".to_owned(), 334 | kind: ParamType::Int(256), 335 | internal_type: Some("sint256".to_owned()), 336 | }, 337 | ], 338 | state_mutability: ethers::abi::StateMutability::Payable, 339 | constant: None, 340 | } 341 | } 342 | 343 | pub fn mod_exp_function() -> Function { 344 | #[allow(deprecated)] 345 | Function { 346 | name: "".to_owned(), 347 | inputs: vec![ 348 | Param { 349 | name: "".to_owned(), 350 | kind: ParamType::Int(256), 351 | internal_type: Some("sint256".to_owned()), 352 | }, 353 | Param { 354 | name: "".to_owned(), 355 | kind: ParamType::Int(256), 356 | internal_type: Some("sint256".to_owned()), 357 | }, 358 | Param { 359 | name: "".to_owned(), 360 | kind: ParamType::Int(256), 361 | internal_type: Some("sint256".to_owned()), 362 | }, 363 | Param { 364 | name: "".to_owned(), 365 | kind: ParamType::Bytes, 366 | internal_type: Some("bytes".to_owned()), 367 | }, 368 | Param { 369 | name: "".to_owned(), 370 | kind: ParamType::Bytes, 371 | internal_type: Some("bytes".to_owned()), 372 | }, 373 | Param { 374 | name: "".to_owned(), 375 | kind: ParamType::Bytes, 376 | internal_type: Some("bytes".to_owned()), 377 | }, 378 | ], 379 | outputs: vec![Param { 380 | name: "".to_owned(), 381 | kind: ParamType::Bytes, 382 | internal_type: Some("bytes".to_owned()), 383 | }], 384 | state_mutability: ethers::abi::StateMutability::Payable, 385 | constant: None, 386 | } 387 | } 388 | -------------------------------------------------------------------------------- /src/zks_wallet/DepositERC20GasLimit.json: -------------------------------------------------------------------------------- 1 | { 2 | "0x0000000000095413afc295d19edeb1ad7b71c952": 140000, 3 | "0xeb4c2781e4eba804ce9a9803c67d0893436bb27d": 160000, 4 | "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd": 140000, 5 | "0xb64ef51c888972c908cfacf59b47c1afbc0ab8ac": 140000, 6 | "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": 150000, 7 | "0x9ba00d6856a4edf4665bca2c2309936572473b7e": 270000, 8 | "0x8daebade922df735c38c80c7ebd708af50815faa": 140000, 9 | "0x0d8775f648430679a709e98d2b0cb6250d2887ef": 140000, 10 | "0xdac17f958d2ee523a2206206994597c13d831ec7": 140000, 11 | "0x6de037ef9ad2725eb40118bb1702ebb27e4aeb24": 150000, 12 | "0x056fd409e1d7a124bd7017459dfea2f387b6d5cd": 180000, 13 | "0x0f5d2fb29fb7d3cfee444a200298f468908cc942": 140000, 14 | "0x514910771af9ca656af840dff83e8264ecf986ca": 140000, 15 | "0x1985365e9f78359a9b6ad760e32412f4a445e862": 180000, 16 | "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": 140000, 17 | "0xe41d2489571d322189246dafa5ebde1f4699f498": 140000, 18 | "0x6b175474e89094c44da98b954eedeac495271d0f": 140000, 19 | "0xaaaebe6fe48e54f431b0c390cfaf0b017d09d42d": 150000, 20 | "0x2b591e99afe9f32eaa6214f7b7629768c40eeb39": 140000, 21 | "0x65ece136b89ebaa72a7f7aa815674946e44ca3f9": 140000, 22 | "0x0000000000085d4780b73119b644ae5ecd22b376": 150000, 23 | "0xdb25f211ab05b1c97d595516f45794528a807ad8": 180000, 24 | "0x408e41876cccdc0f92210600ef50372656052a38": 140000, 25 | "0x15a2b3cfafd696e1c783fe99eed168b78a3a371e": 160000, 26 | "0x38e4adb44ef08f22f5b5b76a8f0c2d0dcbe7dca1": 160000, 27 | "0x3108ccfd96816f9e663baa0e8c5951d229e8c6da": 140000, 28 | "0x56d811088235f11c8920698a204a5010a788f4b3": 240000, 29 | "0x57ab1ec28d129707052df4df418d58a2d46d5f51": 220000, 30 | "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2": 140000, 31 | "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": 150000, 32 | "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f": 200000, 33 | "0x744d70fdbe2ba4cf95131626614a1763df805b9e": 230000, 34 | "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": 140000, 35 | "0x4c7065bca76fe44afb0d16c2441b1e6e163354e2": 250000, 36 | "0xdd974d5c2e2928dea5f71b9825b8b646686bd200": 140000, 37 | "0x80fb784b7ed66730e8b1dbd9820afd29931aab03": 140000, 38 | "0xd56dac73a4d6766464b38ec6d91eb45ce7457c44": 140000, 39 | "0x4fabb145d64652a948d72533023f6e7a623c7c53": 150000, 40 | "0x38a2fdc11f526ddd5a607c1f251c065f40fbf2f7": 140000, 41 | "0x7dd9c5cba05e151c895fde1cf355c9a1d5da6429": 140000 42 | } 43 | -------------------------------------------------------------------------------- /src/zks_wallet/errors.rs: -------------------------------------------------------------------------------- 1 | use ethers::{ 2 | abi::{Error, ParseError}, 3 | prelude::{ 4 | k256::{ 5 | ecdsa::{RecoveryId, Signature as RecoverableSignature}, 6 | schnorr::signature::hazmat::PrehashSigner, 7 | }, 8 | signer::SignerMiddlewareError, 9 | AbiError, ContractError, SignerMiddleware, 10 | }, 11 | providers::{Middleware, ProviderError}, 12 | signers::{Wallet, WalletError}, 13 | types::transaction::eip712::Eip712Error, 14 | }; 15 | 16 | use crate::contracts::main_contract::MainContractError; 17 | 18 | #[derive(thiserror::Error, Debug)] 19 | pub enum ZKSWalletError 20 | where 21 | M: Middleware, 22 | D: PrehashSigner<(RecoverableSignature, RecoveryId)> + Sync + Send, 23 | { 24 | #[error("Provider error: {0}")] 25 | ProviderError(#[from] ProviderError), 26 | #[error("Middleware error: {0}")] 27 | MiddlewareError(#[from] SignerMiddlewareError>), 28 | #[error("Wallet error: {0}")] 29 | EthWalletError(#[from] WalletError), 30 | #[error("ABI error: {0}")] 31 | AbiError(#[from] AbiError), 32 | #[error("EIP712 error: {0}")] 33 | Eip712Error(#[from] Eip712Error), 34 | #[error("No L1 Ethereum provider")] 35 | NoL1ProviderError(), 36 | #[error("No L2 Ethereum provider")] 37 | NoL2ProviderError(), 38 | #[error("Contract error: {0}")] 39 | ContractError(#[from] ContractError), 40 | #[error("Contract error: {0}")] 41 | RequestConversionError(#[from] ZKRequestError), 42 | #[error("{0}")] 43 | CustomError(String), 44 | #[error("Main contract error: {0}")] 45 | MainContractError(#[from] MainContractError), 46 | } 47 | 48 | impl From>>> for ZKSWalletError 49 | where 50 | M: Middleware, 51 | D: PrehashSigner<(RecoverableSignature, RecoveryId)> + Sync + Send, 52 | { 53 | fn from(value: ContractError>>) -> Self { 54 | Self::CustomError(format!("{value:?}")) 55 | } 56 | } 57 | 58 | #[derive(thiserror::Error, Debug)] 59 | pub enum ZKRequestError { 60 | #[error("Error parsing function: {0}")] 61 | ParseFunctionError(#[from] ParseError), 62 | #[error("ABI error: {0}")] 63 | AbiError(#[from] AbiError), 64 | #[error("Encoding or decoding error: {0}")] 65 | Error(#[from] Error), 66 | #[error("{0}")] 67 | CustomError(String), 68 | } 69 | -------------------------------------------------------------------------------- /src/zks_wallet/mod.rs: -------------------------------------------------------------------------------- 1 | mod errors; 2 | pub use errors::{ZKRequestError, ZKSWalletError}; 3 | 4 | mod requests; 5 | pub use requests::{ 6 | call_request::CallRequest, deploy_request::DeployRequest, deposit_request::DepositRequest, 7 | transfer_request::TransferRequest, withdraw_request::WithdrawRequest, 8 | }; 9 | 10 | mod wallet; 11 | pub use wallet::ZKSWallet; 12 | 13 | use ethers::types::U256; 14 | pub struct Overrides { 15 | pub value: Option, 16 | } 17 | -------------------------------------------------------------------------------- /src/zks_wallet/requests/call_request.rs: -------------------------------------------------------------------------------- 1 | use ethers::{ 2 | abi::{encode, Function, HumanReadableParser, ParseError}, 3 | types::{Address, Eip1559TransactionRequest}, 4 | }; 5 | use std::fmt::Debug; 6 | 7 | use crate::{ 8 | zks_utils::{self, is_precompile}, 9 | zks_wallet::errors::ZKRequestError, 10 | }; 11 | 12 | #[derive(Clone, Debug)] 13 | pub struct CallRequest { 14 | pub to: Address, 15 | pub function_signature: String, 16 | pub function_parameters: Option>, 17 | } 18 | 19 | impl CallRequest { 20 | pub fn new(to: Address, function_signature: String) -> Self { 21 | Self { 22 | to, 23 | function_signature, 24 | function_parameters: None, 25 | } 26 | } 27 | 28 | pub fn function_parameters(mut self, function_parameters: Vec) -> Self { 29 | self.function_parameters = Some(function_parameters); 30 | self 31 | } 32 | 33 | pub fn to(mut self, to: Address) -> Self { 34 | self.to = to; 35 | self 36 | } 37 | 38 | pub fn function_signature(mut self, function_signature: String) -> Self { 39 | self.function_signature = function_signature; 40 | self 41 | } 42 | 43 | pub fn get_parsed_function(&self) -> Result { 44 | if self.to == zks_utils::ECADD_PRECOMPILE_ADDRESS { 45 | Ok(zks_utils::ec_add_function()) 46 | } else if self.to == zks_utils::ECMUL_PRECOMPILE_ADDRESS { 47 | Ok(zks_utils::ec_mul_function()) 48 | } else if self.to == zks_utils::MODEXP_PRECOMPILE_ADDRESS { 49 | Ok(zks_utils::mod_exp_function()) 50 | } else { 51 | HumanReadableParser::parse_function(&self.function_signature) 52 | .map_err(ParseError::LexerError) 53 | } 54 | } 55 | } 56 | 57 | impl TryFrom for Eip1559TransactionRequest { 58 | type Error = ZKRequestError; 59 | 60 | fn try_from(request: CallRequest) -> Result { 61 | let function = request.get_parsed_function()?; 62 | let function_args = if let Some(function_args) = request.function_parameters { 63 | function.decode_input(&zks_utils::encode_args(&function, &function_args)?)? 64 | } else { 65 | vec![] 66 | }; 67 | 68 | let data = match (!function_args.is_empty(), is_precompile(request.to)) { 69 | // The contract to call is a precompile with arguments. 70 | (true, true) => encode(&function_args), 71 | // The contract to call is a regular contract with arguments. 72 | (true, false) => function.encode_input(&function_args)?, 73 | // The contract to call is a precompile without arguments. 74 | (false, true) => Default::default(), 75 | // The contract to call is a regular contract without arguments. 76 | (false, false) => function.short_signature().into(), 77 | }; 78 | 79 | Ok(Eip1559TransactionRequest::new().to(request.to).data(data)) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/zks_wallet/requests/deploy_request.rs: -------------------------------------------------------------------------------- 1 | use ethers::{abi::Abi, types::Address}; 2 | use std::fmt::Debug; 3 | 4 | #[derive(Clone, Debug)] 5 | pub struct DeployRequest { 6 | pub contract_abi: Abi, 7 | pub contract_bytecode: Vec, 8 | pub constructor_parameters: Vec, 9 | pub from: Address, 10 | pub factory_deps: Option>>, 11 | } 12 | 13 | impl DeployRequest { 14 | pub fn with( 15 | contract_abi: Abi, 16 | contract_bytecode: Vec, 17 | constructor_parameters: Vec, 18 | ) -> Self { 19 | Self { 20 | contract_abi, 21 | contract_bytecode, 22 | constructor_parameters, 23 | from: Default::default(), 24 | factory_deps: None, 25 | } 26 | } 27 | 28 | pub fn from(mut self, from: Address) -> Self { 29 | self.from = from; 30 | self 31 | } 32 | 33 | pub fn factory_deps(mut self, factory_deps: Vec>) -> Self { 34 | self.factory_deps = Some(factory_deps); 35 | self 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/zks_wallet/requests/deposit_request.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{Address, U256}; 2 | 3 | use crate::zks_utils::{ 4 | DEPOSIT_GAS_PER_PUBDATA_LIMIT, ETHER_L1_ADDRESS, RECOMMENDED_DEPOSIT_L1_GAS_LIMIT, 5 | RECOMMENDED_DEPOSIT_L2_GAS_LIMIT, 6 | }; 7 | 8 | fn default_gas_limit() -> U256 { 9 | RECOMMENDED_DEPOSIT_L1_GAS_LIMIT.into() 10 | } 11 | 12 | fn default_l2_gas_limit() -> U256 { 13 | RECOMMENDED_DEPOSIT_L2_GAS_LIMIT.into() 14 | } 15 | 16 | fn default_gas_per_pubdata_byte() -> U256 { 17 | DEPOSIT_GAS_PER_PUBDATA_LIMIT.into() 18 | } 19 | 20 | #[derive(Clone, Debug)] 21 | pub struct DepositRequest { 22 | pub amount: U256, 23 | pub to: Option
, 24 | pub l2_gas_limit: U256, 25 | pub gas_per_pubdata_byte: U256, 26 | pub operator_tip: U256, 27 | pub gas_price: Option, 28 | pub gas_limit: U256, 29 | pub token: Address, 30 | pub bridge_address: Option
, 31 | } 32 | 33 | impl DepositRequest { 34 | pub fn new(amount: U256) -> Self { 35 | Self { 36 | amount, 37 | to: None, 38 | l2_gas_limit: default_l2_gas_limit(), 39 | gas_per_pubdata_byte: default_gas_per_pubdata_byte(), 40 | operator_tip: 0_i32.into(), 41 | gas_price: None, 42 | gas_limit: default_gas_limit(), 43 | token: ETHER_L1_ADDRESS, 44 | bridge_address: None, 45 | } 46 | } 47 | 48 | pub fn amount(&self) -> &U256 { 49 | &self.amount 50 | } 51 | 52 | pub fn to(mut self, address: Address) -> Self { 53 | self.to = Some(address); 54 | self 55 | } 56 | 57 | pub fn l2_gas_limit(mut self, value: Option) -> Self { 58 | self.l2_gas_limit = match value { 59 | Some(l2_gas_limit) => l2_gas_limit, 60 | None => default_l2_gas_limit(), 61 | }; 62 | self 63 | } 64 | 65 | pub fn gas_per_pubdata_byte(mut self, value: Option) -> Self { 66 | self.gas_per_pubdata_byte = match value { 67 | Some(gas_per_pubdata_byte) => gas_per_pubdata_byte, 68 | None => default_gas_per_pubdata_byte(), 69 | }; 70 | self 71 | } 72 | 73 | pub fn operator_tip(mut self, operator_tip: U256) -> Self { 74 | self.operator_tip = operator_tip; 75 | self 76 | } 77 | 78 | pub fn gas_price(mut self, value: Option) -> Self { 79 | self.gas_price = value; 80 | self 81 | } 82 | 83 | pub fn gas_limit(mut self, value: Option) -> Self { 84 | self.gas_limit = match value { 85 | Some(gas_limit) => gas_limit, 86 | _ => default_gas_limit(), 87 | }; 88 | self 89 | } 90 | 91 | pub fn token(mut self, token: Option
) -> Self { 92 | self.token = match token { 93 | Some(address) => address, 94 | _ => ETHER_L1_ADDRESS, 95 | }; 96 | self 97 | } 98 | 99 | pub fn bridge_address(mut self, bridge_address: Option
) -> Self { 100 | self.bridge_address = bridge_address; 101 | self 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/zks_wallet/requests/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod call_request; 2 | pub mod deploy_request; 3 | pub mod deposit_request; 4 | pub mod transfer_request; 5 | pub mod withdraw_request; 6 | -------------------------------------------------------------------------------- /src/zks_wallet/requests/transfer_request.rs: -------------------------------------------------------------------------------- 1 | use ethers::types::{Address, Eip1559TransactionRequest, U256}; 2 | use std::fmt::Debug; 3 | 4 | #[derive(Clone, Debug)] 5 | pub struct TransferRequest { 6 | pub amount: U256, 7 | pub to: Address, 8 | pub from: Address, 9 | } 10 | 11 | impl TransferRequest { 12 | pub fn new(amount: U256) -> Self { 13 | Self { 14 | amount, 15 | to: Default::default(), 16 | from: Default::default(), 17 | } 18 | } 19 | 20 | pub fn from(mut self, from: Address) -> Self { 21 | self.from = from; 22 | self 23 | } 24 | 25 | pub fn to(mut self, to: Address) -> Self { 26 | self.to = to; 27 | self 28 | } 29 | 30 | pub fn amount(mut self, amount: U256) -> Self { 31 | self.amount = amount; 32 | self 33 | } 34 | } 35 | 36 | impl From for Eip1559TransactionRequest { 37 | fn from(request: TransferRequest) -> Eip1559TransactionRequest { 38 | Eip1559TransactionRequest::new() 39 | .to(request.to) 40 | .value(request.amount) 41 | .from(request.from) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/zks_wallet/requests/withdraw_request.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use ethers::types::{Address, U256}; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct WithdrawRequest { 7 | pub amount: U256, 8 | pub to: Address, 9 | pub from: Address, 10 | } 11 | 12 | impl WithdrawRequest { 13 | pub fn new(amount: U256) -> Self { 14 | Self { 15 | amount, 16 | to: Default::default(), 17 | from: Default::default(), 18 | } 19 | } 20 | 21 | pub fn to(mut self, to: Address) -> Self { 22 | self.to = to; 23 | self 24 | } 25 | 26 | pub fn from(mut self, from: Address) -> Self { 27 | self.from = from; 28 | self 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/zks_wallet/wallet.rs: -------------------------------------------------------------------------------- 1 | use super::ZKSWalletError; 2 | use super::{ 3 | requests::transfer_request::TransferRequest, DeployRequest, DepositRequest, WithdrawRequest, 4 | }; 5 | use crate::zks_utils::{ 6 | DEFAULT_ERC20_DEPOSIT_GAS_LIMIT, DEPOSIT_GAS_PER_PUBDATA_LIMIT, ERA_MAINNET_CHAIN_ID, 7 | }; 8 | use crate::{ 9 | abi, 10 | contracts::main_contract::{MainContract, MainContractInstance}, 11 | eip712::Eip712Transaction, 12 | eip712::{hash_bytecode, Eip712Meta, Eip712TransactionRequest}, 13 | types::TransactionReceipt, 14 | zks_provider::ZKSProvider, 15 | zks_utils::{self, CONTRACT_DEPLOYER_ADDR, EIP712_TX_TYPE, ETHER_L1_ADDRESS, ETH_CHAIN_ID}, 16 | }; 17 | use ethers::{ 18 | abi::{decode, Abi, ParamType, Tokenizable}, 19 | prelude::{ 20 | encode_function_data, 21 | k256::{ 22 | ecdsa::{RecoveryId, Signature as RecoverableSignature}, 23 | schnorr::signature::hazmat::PrehashSigner, 24 | }, 25 | MiddlewareBuilder, SignerMiddleware, 26 | }, 27 | providers::Middleware, 28 | signers::{Signer, Wallet}, 29 | types::{ 30 | transaction::eip2718::TypedTransaction, Address, Bytes, Eip1559TransactionRequest, Log, 31 | Signature, H160, H256, U256, 32 | }, 33 | }; 34 | use lazy_static::lazy_static; 35 | use serde_json::{Map, Value}; 36 | use std::collections::HashMap; 37 | use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr, sync::Arc}; 38 | use zksync_web3_rs::core::abi::Tokenize; 39 | 40 | const RAW_ERC20_DEPOSIT_GAS_LIMIT: &str = include_str!("DepositERC20GasLimit.json"); 41 | 42 | lazy_static! { 43 | static ref ERC20_DEPOSIT_GAS_LIMITS: HashMap = { 44 | #![allow(clippy::expect_used)] 45 | let mut m = HashMap::new(); 46 | let raw: Map = serde_json::from_str(RAW_ERC20_DEPOSIT_GAS_LIMIT) 47 | .expect("Failed to parse DepositERC20GasLimit.json"); 48 | for (address, value) in raw.iter() { 49 | m.insert( 50 | address.to_owned(), 51 | value 52 | .as_u64() 53 | .expect("Failed to ERC20 deposit gas limit for address {address:?}"), 54 | ); 55 | } 56 | m 57 | }; 58 | } 59 | 60 | #[derive(Clone, Debug)] 61 | pub struct ZKSWallet 62 | where 63 | M: Middleware + Clone, 64 | D: PrehashSigner<(RecoverableSignature, RecoveryId)> + Clone, 65 | { 66 | /// Eth provider 67 | pub eth_provider: Option>>>, 68 | pub era_provider: Option>>>, 69 | pub l2_wallet: Wallet, 70 | pub l1_wallet: Wallet, 71 | } 72 | 73 | impl ZKSWallet 74 | where 75 | M: Middleware + 'static + Clone, 76 | D: PrehashSigner<(RecoverableSignature, RecoveryId)> + Sync + Send + Clone, 77 | { 78 | pub fn new( 79 | l2_wallet: Wallet, 80 | l1_wallet: Option>, 81 | era_provider: Option, 82 | eth_provider: Option, 83 | ) -> Result> { 84 | let l1_wallet = match l1_wallet { 85 | Some(wallet) => wallet, 86 | None => l2_wallet.clone().with_chain_id(ETH_CHAIN_ID), 87 | }; 88 | Ok(Self { 89 | l2_wallet: l2_wallet.clone(), 90 | l1_wallet: l1_wallet.clone(), 91 | era_provider: era_provider.map(|p| p.with_signer(l2_wallet).into()), 92 | eth_provider: eth_provider.map(|p| p.with_signer(l1_wallet).into()), 93 | }) 94 | } 95 | 96 | pub fn connect_eth_provider(mut self, eth_provider: M) -> Self { 97 | self.eth_provider = Some(eth_provider.with_signer(self.l1_wallet.clone()).into()); 98 | self 99 | } 100 | 101 | pub fn connect_era_provider(mut self, era_provider: M) -> Self { 102 | self.era_provider = Some(era_provider.with_signer(self.l2_wallet.clone()).into()); 103 | self 104 | } 105 | 106 | pub fn connect_eth_signer(mut self, eth_signer: SignerMiddleware>) -> Self { 107 | self.eth_provider = Some(eth_signer.into()); 108 | self 109 | } 110 | 111 | pub fn connect_era_signer(mut self, era_signer: SignerMiddleware>) -> Self { 112 | self.era_provider = Some(era_signer.into()); 113 | self 114 | } 115 | 116 | // pub fn connect_eth(&mut self, host: &str, port: u16) { 117 | // self.eth_provider = Provider::try_from(format!("http://{host}:{port}")).ok().map(|p| p.with_signer(self.wallet)); 118 | // } 119 | 120 | // pub fn connect_era(&mut self, host: &str, port: u16) { 121 | // self.era_provider = Provider::try_from(format!("http://{host}:{port}")).ok().map(|p| p.with_signer(self.wallet)); 122 | // } 123 | 124 | pub fn l2_address(&self) -> Address { 125 | self.l2_wallet.address() 126 | } 127 | 128 | pub fn l1_address(&self) -> Address { 129 | self.l1_wallet.address() 130 | } 131 | 132 | pub fn l2_chain_id(&self) -> u64 { 133 | self.l2_wallet.chain_id() 134 | } 135 | 136 | pub fn l1_chain_id(&self) -> u64 { 137 | self.l1_wallet.chain_id() 138 | } 139 | 140 | pub fn get_eth_provider( 141 | &self, 142 | ) -> Result>>, ZKSWalletError> { 143 | match &self.eth_provider { 144 | Some(eth_provider) => Ok(Arc::clone(eth_provider)), 145 | None => Err(ZKSWalletError::NoL1ProviderError()), 146 | } 147 | } 148 | 149 | pub fn get_era_provider( 150 | &self, 151 | ) -> Result>>, ZKSWalletError> { 152 | match &self.era_provider { 153 | Some(era_provider) => Ok(Arc::clone(era_provider)), 154 | None => Err(ZKSWalletError::NoL2ProviderError()), 155 | } 156 | } 157 | 158 | pub async fn eth_balance(&self) -> Result> 159 | where 160 | M: ZKSProvider, 161 | { 162 | match &self.eth_provider { 163 | // TODO: Should we have a balance_on_block method? 164 | Some(eth_provider) => Ok(eth_provider.get_balance(self.l1_address(), None).await?), 165 | None => Err(ZKSWalletError::CustomError("no eth provider".to_owned())), 166 | } 167 | } 168 | 169 | pub async fn era_balance(&self) -> Result> 170 | where 171 | M: ZKSProvider, 172 | { 173 | match &self.era_provider { 174 | // TODO: Should we have a balance_on_block method? 175 | Some(era_provider) => Ok(era_provider.get_balance(self.l2_address(), None).await?), 176 | None => Err(ZKSWalletError::CustomError("no era provider".to_owned())), 177 | } 178 | } 179 | 180 | pub async fn transfer( 181 | &self, 182 | request: &TransferRequest, 183 | // TODO: Support multiple-token transfers. 184 | _token: Option
, 185 | ) -> Result> 186 | where 187 | M: ZKSProvider, 188 | { 189 | let era_provider = self.get_era_provider()?; 190 | 191 | let mut transfer_request: Eip1559TransactionRequest = request.clone().into(); 192 | 193 | let fee = era_provider.estimate_fee(transfer_request.clone()).await?; 194 | transfer_request = transfer_request.max_priority_fee_per_gas(fee.max_priority_fee_per_gas); 195 | transfer_request = transfer_request.max_fee_per_gas(fee.max_fee_per_gas); 196 | 197 | let transaction: TypedTransaction = transfer_request.into(); 198 | 199 | // TODO: add block as an override. 200 | let transaction_receipt = era_provider 201 | .send_transaction(transaction, None) 202 | .await? 203 | .await? 204 | .ok_or(ZKSWalletError::CustomError( 205 | "No transaction receipt".to_owned(), 206 | ))?; 207 | 208 | Ok(transaction_receipt.transaction_hash) 209 | } 210 | 211 | pub async fn transfer_eip712( 212 | &self, 213 | request: &TransferRequest, 214 | // TODO: Support multiple-token transfers. 215 | _token: Option
, 216 | ) -> Result> 217 | where 218 | M: ZKSProvider, 219 | { 220 | let era_provider = self.get_era_provider()?; 221 | 222 | let transaction_receipt = era_provider 223 | .send_transaction_eip712(&self.l2_wallet, request.clone()) 224 | .await? 225 | .await? 226 | .ok_or(ZKSWalletError::CustomError( 227 | "No transaction receipt".to_owned(), 228 | ))?; 229 | 230 | Ok(transaction_receipt.transaction_hash) 231 | } 232 | 233 | pub async fn deposit(&self, request: &DepositRequest) -> Result> 234 | where 235 | M: ZKSProvider, 236 | { 237 | let to = request.to.unwrap_or(self.l2_address()); 238 | let call_data = Bytes::default(); 239 | let l2_gas_limit: U256 = request.l2_gas_limit; 240 | let l2_value = request.amount; 241 | let gas_per_pubdata_byte: U256 = request.gas_per_pubdata_byte; 242 | let gas_price = request 243 | .gas_price 244 | .unwrap_or(self.get_eth_provider()?.get_gas_price().await?); 245 | let gas_limit: U256 = request.gas_limit; 246 | let operator_tip: U256 = request.operator_tip; 247 | let base_cost = self 248 | .get_base_cost(gas_limit, gas_per_pubdata_byte, gas_price) 249 | .await?; 250 | let l1_value = base_cost + operator_tip + request.amount; 251 | // let factory_deps = []; 252 | let refund_recipient = self.l1_address(); 253 | // FIXME check base cost 254 | 255 | let receipt = if request.token == ETHER_L1_ADDRESS { 256 | let main_contract_address = self.get_era_provider()?.get_main_contract().await?; 257 | let main_contract = 258 | MainContractInstance::new(main_contract_address, self.get_eth_provider()?); 259 | 260 | main_contract 261 | .request_l2_transaction( 262 | to, 263 | l2_value, 264 | call_data, 265 | l2_gas_limit, 266 | gas_per_pubdata_byte, 267 | Default::default(), 268 | refund_recipient, 269 | gas_price, 270 | gas_limit, 271 | l1_value, 272 | ) 273 | .await? 274 | } else { 275 | self.deposit_erc20_token( 276 | request.token, 277 | request.amount().to_owned(), 278 | to, 279 | operator_tip, 280 | request.bridge_address, 281 | None, 282 | Some(gas_price), 283 | ) 284 | .await? 285 | }; 286 | 287 | Ok(receipt.transaction_hash) 288 | } 289 | 290 | async fn deposit_erc20_token( 291 | &self, 292 | l1_token_address: Address, 293 | amount: U256, 294 | to: Address, 295 | operator_tip: U256, 296 | bridge_address: Option
, 297 | max_fee_per_gas: Option, 298 | gas_price: Option, 299 | ) -> Result> 300 | where 301 | M: ZKSProvider, 302 | { 303 | let eth_provider = self.get_eth_provider()?; 304 | let era_provider = self.get_era_provider()?; 305 | 306 | let gas_limit: U256 = { 307 | let address_str = format!("{l1_token_address:?}"); 308 | let is_mainnet = 309 | self.get_era_provider()?.get_chainid().await? == ERA_MAINNET_CHAIN_ID.into(); 310 | if is_mainnet { 311 | (*ERC20_DEPOSIT_GAS_LIMITS) 312 | .get(&address_str) 313 | .unwrap_or(&DEFAULT_ERC20_DEPOSIT_GAS_LIMIT) 314 | .to_owned() 315 | } else { 316 | DEFAULT_ERC20_DEPOSIT_GAS_LIMIT 317 | } 318 | } 319 | .into(); 320 | 321 | // If the user has already provided max_fee_per_gas or gas_price, we will use 322 | // it to calculate the base cost for the transaction 323 | let gas_price = if let Some(max_fee_per_gas) = max_fee_per_gas { 324 | max_fee_per_gas 325 | } else if let Some(gas_price) = gas_price { 326 | gas_price 327 | } else { 328 | era_provider.get_gas_price().await? 329 | }; 330 | 331 | let l2_gas_limit = U256::from(3_000_000_u32); 332 | 333 | let base_cost: U256 = self 334 | .get_base_cost( 335 | l2_gas_limit, 336 | DEPOSIT_GAS_PER_PUBDATA_LIMIT.into(), 337 | gas_price, 338 | ) 339 | .await?; 340 | 341 | // ERC20 token, `msg.value` is used only for the fee. 342 | let value = base_cost + operator_tip; 343 | 344 | let data: Bytes = { 345 | let bridge_contract = abi::l1_bridge_contract(); 346 | 347 | #[allow(clippy::expect_used)] 348 | let contract_function = bridge_contract 349 | .function("deposit") 350 | .expect("failed to get deposit function parameters"); 351 | 352 | let params = ( 353 | to, 354 | l1_token_address, 355 | amount, 356 | l2_gas_limit, 357 | U256::from(DEPOSIT_GAS_PER_PUBDATA_LIMIT), 358 | ); 359 | 360 | #[allow(clippy::expect_used)] 361 | contract_function 362 | .encode_input(¶ms.into_tokens()) 363 | .expect("failed to encode deposit function parameters") 364 | .into() 365 | }; 366 | 367 | let chain_id = eth_provider.get_chainid().await?.as_u64(); 368 | 369 | let bridge_address: Address = match bridge_address { 370 | Some(address) => address, 371 | None => { 372 | let bridge_contracts = era_provider.get_bridge_contracts().await?; 373 | bridge_contracts.l1_erc20_default_bridge 374 | } 375 | }; 376 | 377 | let deposit_transaction = Eip1559TransactionRequest { 378 | from: Some(self.get_eth_provider()?.address()), 379 | to: Some(bridge_address.into()), 380 | gas: Some(gas_limit), 381 | value: Some(value), 382 | data: Some(data), 383 | nonce: None, 384 | access_list: Default::default(), 385 | max_priority_fee_per_gas: None, // FIXME 386 | max_fee_per_gas: None, // FIXME 387 | chain_id: Some(chain_id.into()), 388 | }; 389 | 390 | let _approve_tx_receipt = self 391 | .approve_erc20(bridge_address, amount, l1_token_address) 392 | .await?; 393 | let pending_transaction = eth_provider 394 | .send_transaction(deposit_transaction, None) 395 | .await?; 396 | 397 | pending_transaction 398 | .await? 399 | .ok_or(ZKSWalletError::CustomError( 400 | "no transaction receipt".to_owned(), 401 | )) 402 | } 403 | 404 | async fn approve_erc20( 405 | &self, 406 | bridge: Address, 407 | amount: U256, 408 | token: Address, 409 | ) -> Result> 410 | where 411 | M: ZKSProvider, 412 | { 413 | let provider = self.get_eth_provider()?; 414 | let function_signature = 415 | "function approve(address spender,uint256 amount) public virtual returns (bool)"; 416 | let parameters = [format!("{bridge:?}"), format!("{amount:?}")]; 417 | let response = provider 418 | .send( 419 | &self.l1_wallet, 420 | token, 421 | function_signature, 422 | Some(parameters.into()), 423 | None, 424 | ) 425 | .await?; 426 | 427 | response.await?.ok_or(ZKSWalletError::CustomError( 428 | "No transaction receipt for erc20 approval".to_owned(), 429 | )) 430 | } 431 | 432 | async fn get_base_cost( 433 | &self, 434 | gas_limit: U256, 435 | gas_per_pubdata_byte: U256, 436 | gas_price: U256, 437 | ) -> Result> 438 | where 439 | M: ZKSProvider, 440 | { 441 | let main_contract_address = self.get_era_provider()?.get_main_contract().await?; 442 | let main_contract = MainContract::new(main_contract_address, self.get_eth_provider()?); 443 | let base_cost: U256 = main_contract 444 | .l_2_transaction_base_cost(gas_price, gas_limit, gas_per_pubdata_byte) 445 | .call() 446 | .await?; 447 | 448 | Ok(base_cost) 449 | } 450 | 451 | pub async fn deploy_from_bytecode( 452 | &self, 453 | contract_bytecode: &[u8], 454 | contract_dependencies: Option>>, 455 | // TODO: accept constructor parameters. 456 | _constructor_parameters: Option, 457 | ) -> Result> 458 | where 459 | M: ZKSProvider, 460 | T: Tokenizable, 461 | { 462 | let era_provider = self.get_era_provider()?; 463 | 464 | let custom_data = Eip712Meta::new().factory_deps({ 465 | let mut factory_deps = Vec::new(); 466 | if let Some(contract_dependencies) = contract_dependencies { 467 | factory_deps.extend(contract_dependencies); 468 | } 469 | factory_deps.push(contract_bytecode.to_vec()); 470 | factory_deps 471 | }); 472 | 473 | let mut contract_deployer_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 474 | contract_deployer_path.push("src/abi/ContractDeployer.json"); 475 | let mut deploy_request = Eip712TransactionRequest::new() 476 | .r#type(EIP712_TX_TYPE) 477 | .from(self.l2_address()) 478 | .to(Address::from_str(CONTRACT_DEPLOYER_ADDR).map_err(|e| { 479 | ZKSWalletError::CustomError(format!("invalid contract deployer address: {e}")) 480 | })?) 481 | .chain_id(self.l2_chain_id()) 482 | .nonce( 483 | era_provider 484 | .get_transaction_count(self.l2_address(), None) 485 | .await?, 486 | ) 487 | .gas_price(era_provider.get_gas_price().await?) 488 | .max_fee_per_gas(era_provider.get_gas_price().await?) 489 | .data({ 490 | let contract_deployer = Abi::load(BufReader::new( 491 | File::open(contract_deployer_path).map_err(|e| { 492 | ZKSWalletError::CustomError(format!( 493 | "failed to open ContractDeployer abi: {e}" 494 | )) 495 | })?, 496 | )) 497 | .map_err(|e| { 498 | ZKSWalletError::CustomError(format!("failed to load ContractDeployer abi: {e}")) 499 | })?; 500 | let create = contract_deployer.function("create").map_err(|e| { 501 | ZKSWalletError::CustomError(format!("failed to get create function: {e}")) 502 | })?; 503 | // TODO: User could provide this instead of defaulting. 504 | let salt = [0_u8; 32]; 505 | let bytecode_hash = hash_bytecode(contract_bytecode)?; 506 | let call_data = Bytes::default(); 507 | 508 | encode_function_data(create, (salt, bytecode_hash, call_data))? 509 | }) 510 | .custom_data(custom_data.clone()); 511 | 512 | let fee = era_provider.estimate_fee(deploy_request.clone()).await?; 513 | deploy_request = deploy_request 514 | .max_priority_fee_per_gas(fee.max_priority_fee_per_gas) 515 | .max_fee_per_gas(fee.max_fee_per_gas) 516 | .gas_limit(fee.gas_limit); 517 | 518 | let signable_data: Eip712Transaction = deploy_request.clone().try_into()?; 519 | let signature: Signature = self.l2_wallet.sign_typed_data(&signable_data).await?; 520 | deploy_request = 521 | deploy_request.custom_data(custom_data.custom_signature(signature.to_vec())); 522 | 523 | let encoded_rlp = &*deploy_request.rlp_signed(signature)?; 524 | let pending_transaction = era_provider 525 | .send_raw_transaction([&[EIP712_TX_TYPE], encoded_rlp].concat().into()) 526 | .await?; 527 | 528 | // TODO: Should we wait here for the transaction to be confirmed on-chain? 529 | 530 | let transaction_receipt = pending_transaction 531 | .await? 532 | .ok_or(ZKSWalletError::CustomError( 533 | "no transaction receipt".to_owned(), 534 | ))?; 535 | 536 | let contract_address = 537 | transaction_receipt 538 | .contract_address 539 | .ok_or(ZKSWalletError::CustomError( 540 | "no contract address".to_owned(), 541 | ))?; 542 | 543 | Ok(contract_address) 544 | } 545 | 546 | pub async fn deploy(&self, request: &DeployRequest) -> Result> 547 | where 548 | M: ZKSProvider, 549 | { 550 | let era_provider = self.get_era_provider()?; 551 | 552 | let eip712_request: Eip712TransactionRequest = request.clone().try_into()?; 553 | 554 | let transaction_receipt = era_provider 555 | .send_transaction_eip712(&self.l2_wallet, eip712_request) 556 | .await? 557 | .await? 558 | .ok_or(ZKSWalletError::CustomError( 559 | "No transaction receipt".to_owned(), 560 | ))?; 561 | 562 | transaction_receipt 563 | .contract_address 564 | .ok_or(ZKSWalletError::CustomError( 565 | "No contract address".to_owned(), 566 | )) 567 | } 568 | 569 | pub async fn withdraw(&self, request: &WithdrawRequest) -> Result> 570 | where 571 | M: ZKSProvider, 572 | { 573 | let era_provider = self.get_era_provider()?; 574 | let transaction_receipt = era_provider 575 | .send_transaction_eip712(&self.l2_wallet, request.clone()) 576 | .await? 577 | .await? 578 | .ok_or(ZKSWalletError::CustomError( 579 | "No transaction receipt".to_owned(), 580 | ))?; 581 | 582 | Ok(transaction_receipt.transaction_hash) 583 | } 584 | 585 | pub async fn finalize_withdraw(&self, tx_hash: H256) -> Result> 586 | where 587 | M: ZKSProvider, 588 | { 589 | let era_provider = self.get_era_provider()?; 590 | let eth_provider = self.get_eth_provider()?; 591 | 592 | let withdrawal_receipt = era_provider.get_transaction_receipt(tx_hash).await?.ok_or( 593 | ZKSWalletError::CustomError("Error getting transaction receipt of withdraw".to_owned()), 594 | )?; 595 | 596 | let messenger_contract_address = Address::from_str(zks_utils::CONTRACTS_L1_MESSENGER_ADDR) 597 | .map_err(|error| { 598 | ZKSWalletError::CustomError(format!("failed to parse contract address: {error}")) 599 | })?; 600 | 601 | let logs: Vec = withdrawal_receipt 602 | .logs 603 | .into_iter() 604 | .filter(|log| { 605 | //log.topics[0] == topic && 606 | log.address == messenger_contract_address 607 | }) 608 | .collect(); 609 | 610 | // Get all the parameters needed to call the finalizeWithdrawal function on the main contract contract. 611 | let (_, l2_to_l1_log_index) = serde_json::from_value::>( 612 | withdrawal_receipt 613 | .other 614 | .get("l2ToL1Logs") 615 | .ok_or(ZKSWalletError::CustomError( 616 | "Field not present in receipt".to_owned(), 617 | ))? 618 | .clone(), 619 | ) 620 | .map_err(|err| { 621 | ZKSWalletError::CustomError(format!("Error getting logs in receipt: {err:?}")) 622 | })? 623 | .iter() 624 | .zip(0_u64..) 625 | .find(|(log, _)| { 626 | if let Some(sender) = log.get("sender") { 627 | sender == zks_utils::CONTRACTS_L1_MESSENGER_ADDR 628 | } else { 629 | false 630 | } 631 | }) 632 | .ok_or(ZKSWalletError::CustomError( 633 | "Error getting log index parameter".to_owned(), 634 | ))?; 635 | 636 | let filtered_log = logs 637 | .first() 638 | .ok_or(ZKSWalletError::CustomError( 639 | "Error getting log in receipt".to_owned(), 640 | ))? 641 | .clone(); 642 | let proof = era_provider 643 | .get_l2_to_l1_log_proof(tx_hash, Some(l2_to_l1_log_index)) 644 | .await? 645 | .ok_or(ZKSWalletError::CustomError( 646 | "Error getting proof parameter".to_owned(), 647 | ))?; 648 | let main_contract = era_provider.get_main_contract().await?; 649 | let merkle_proof: Vec = proof.merkle_proof; 650 | let l1_batch_number = era_provider.get_l1_batch_number().await?; 651 | let l2_message_index = U256::from(proof.id); 652 | 653 | let l2_tx_number_in_block: String = serde_json::from_value::( 654 | withdrawal_receipt 655 | .other 656 | .get("l1BatchTxIndex") 657 | .ok_or(ZKSWalletError::CustomError( 658 | "Field not present in receipt".to_owned(), 659 | ))? 660 | .clone(), 661 | ) 662 | .map_err(|err| ZKSWalletError::CustomError(format!("Failed to deserialize field {err}")))?; 663 | 664 | let message: Bytes = decode(&[ParamType::Bytes], &filtered_log.data) 665 | .map_err(|e| ZKSWalletError::CustomError(format!("failed to decode log data: {e}")))? 666 | .first() 667 | .ok_or(ZKSWalletError::CustomError( 668 | "Message not found in decoded data".to_owned(), 669 | ))? 670 | .clone() 671 | .into_bytes() 672 | .ok_or(ZKSWalletError::CustomError( 673 | "Could not convert message to bytes".to_owned(), 674 | ))? 675 | .into(); 676 | 677 | let parameters = [ 678 | format!("{l1_batch_number:?}"), 679 | format!("{l2_message_index:?}"), 680 | l2_tx_number_in_block, 681 | hex::encode(&message), 682 | format!("{merkle_proof:?}") 683 | .replace('"', "") 684 | .replace(' ', ""), 685 | ]; 686 | 687 | let function_signature = "function finalizeEthWithdrawal(uint256 _l2BlockNumber,uint256 _l2MessageIndex,uint16 _l2TxNumberInBlock,bytes calldata _message,bytes32[] calldata _merkleProof) external"; 688 | let transaction_receipt = eth_provider 689 | .send( 690 | &self.l1_wallet, 691 | main_contract, 692 | function_signature, 693 | Some(parameters.into()), 694 | None, 695 | ) 696 | .await? 697 | .await? 698 | .ok_or(ZKSWalletError::CustomError( 699 | "No transaction receipt".to_owned(), 700 | ))?; 701 | 702 | Ok(transaction_receipt.transaction_hash) 703 | } 704 | } 705 | --------------------------------------------------------------------------------