From e0ef89efeb24e8ac5c2dac4112a531e971553b5f Mon Sep 17 00:00:00 2001 From: Nacho Avecilla Date: Tue, 29 Aug 2023 09:54:14 -0300 Subject: [PATCH] Improve API consistency (#85) * Add withdraw request builder * New module for specific transaction requests * Improve return type on send functions * Delete duplicated code to get providers * Add function to provider to send eip712 transactions * Add from trait for eip712 transactions with every request type * Revert returning transaction hash from send functions * Add transfer request type * Add defaults for withdraw request * Update wallet to use every transaction type * Add deploy request implementation * Use deploy request in wallet deploy function * Add call request implementation * Fix unused imports * Fix deploy test * Fix clippy lints * Add request conversion error * Fix unused import * Update README and payment example * Return contract address in deploy function * Remove unnecesary sleeps in tests * Update request builder methods * Change return type to pending transaction * Return transaction ID in every wallet operation * Update README and payment example * Empty commit --- README.md | 24 +- examples/simple_payment/main.rs | 20 +- src/eip712/transaction_request.rs | 107 ++++++- src/lib.rs | 4 +- src/tests/provider_tests.rs | 114 +++----- src/tests/wallet_tests.rs | 213 +++++++------- src/zks_provider/mod.rs | 165 ++++++----- src/zks_wallet/errors.rs | 15 + src/zks_wallet/mod.rs | 11 +- src/zks_wallet/requests/call_request.rs | 81 ++++++ src/zks_wallet/requests/deploy_request.rs | 37 +++ .../{wallet => requests}/deposit_request.rs | 2 + src/zks_wallet/requests/mod.rs | 5 + src/zks_wallet/requests/transfer_request.rs | 43 +++ src/zks_wallet/requests/withdraw_request.rs | 30 ++ src/zks_wallet/wallet.rs | 267 +++++------------- 16 files changed, 676 insertions(+), 462 deletions(-) create mode 100644 src/zks_wallet/requests/call_request.rs create mode 100644 src/zks_wallet/requests/deploy_request.rs rename src/zks_wallet/{wallet => requests}/deposit_request.rs (98%) create mode 100644 src/zks_wallet/requests/mod.rs create mode 100644 src/zks_wallet/requests/transfer_request.rs create mode 100644 src/zks_wallet/requests/withdraw_request.rs diff --git a/README.md b/README.md index 684aedc..8d19a18 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ - [Importing dependencies](#importing-dependencies) - [Connecting to the zkSync Network](#connecting-to-the-zksync-network) - [Creating a ZK-Wallet](#creating-a-zk-wallet) - - [Creating a Payment Parameters](#creating-a-payment-parameters) + - [Creating a Payment Transaction](#creating-a-payment-transaction) - [Sending the Transaction](#sending-the-transaction) - [Checking zkSync account balance](#checking-zksync-account-balance) - [Simple Transfer Example](#simple-transfer-example) @@ -82,32 +82,40 @@ let wallet = zksync::Wallet::with_chain_id(private_key, zksync_era_chain_id); let zk_wallet = zksync::ZKSWallet::new(wallet, None, Some(provider), None).unwrap(); ``` -#### Creating a Payment Parameters +#### Creating a Payment Transaction -To create a payment transaction, you need to provide the receiver's address, and the amount to transfer. The sender address will be derived from the private key used to create the wallet. +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: ```rust use zksync::zks_provider::ZKSProvider; let receiver_address: zksync::Address = "0xa61464658AfeAf65CccaaFD3a512b69A83B77618".parse().unwrap(); let amount_to_transfer = zksync::U256::from(1); + +let mut payment_request = zksync::zks_wallet::TransferRequest::new(amount_to_transfer) + .to(receiver_address) + .from(sender_address); // Use zk_wallet.l2_address() method to send it from the wallet address. ``` #### Sending the Transaction -To send the payment transaction, you need to use the wallet and the transfer parameters. You can send the transaction using the following code: +To send the payment transaction, you need to use the wallet and the transfer request. You can send the transaction using the following code: > In case you are wondering, the transaction is signed in the `send_transaction` method inside the transfer process. ```rust -let pending_payment = - zk_wallet.transfer(receiver_address, amount_to_transfer, None).await.unwrap(); +let payment_transaction_id = + zk_wallet.transfer(payment_request, None).await.unwrap(); ``` -This will send the transaction to the node but the transaction will not be mined until we `await` on it. That will resolve to a `TransactionReceipt` confirming that the transfer went fine. +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: ```rust -let payment_response: zksync::TransactionReceipt = pending_payment.await.unwrap().unwrap(); +let payment_transaction_receipt = provider + .get_transaction_receipt(payment_transaction_id) + .await + .unwrap() + .unwrap(); ``` #### Checking zkSync account balance diff --git a/examples/simple_payment/main.rs b/examples/simple_payment/main.rs index 5823c42..dc47dd3 100644 --- a/examples/simple_payment/main.rs +++ b/examples/simple_payment/main.rs @@ -4,9 +4,9 @@ use ethers::{ prelude::k256::ecdsa::SigningKey, providers::{Middleware, Provider}, signers::{Signer, Wallet}, - types::{TransactionReceipt, U256}, + types::U256, }; -use zksync_web3_rs::ZKSWallet; +use zksync_web3_rs::{zks_wallet::TransferRequest, ZKSWallet}; // It is set so that the transaction is replay-protected (EIP-155) // https://era.zksync.io/docs/api/hardhat/testing.html#connect-wallet-to-local-nodes @@ -53,6 +53,11 @@ async fn main() { let zk_wallet = ZKSWallet::new(signer, None, Some(provider.clone()), None).unwrap(); + /* Payment transaction building */ + let payment_request = TransferRequest::new(amount).to(args.to).from(args.from); + + log::debug!("{:?}", payment_request); + /* Sending the payment transaction */ log::debug!( @@ -64,11 +69,14 @@ async fn main() { provider.get_balance(args.to, None).await.unwrap() ); - let pending_payment_transaction = zk_wallet.transfer(args.to, amount, None).await.unwrap(); + let payment_transaction_id = zk_wallet.transfer(&payment_request, None).await.unwrap(); + let payment_transaction_receipt = provider + .get_transaction_receipt(payment_transaction_id) + .await + .unwrap() + .unwrap(); - /* Waiting for the payment transaction */ - let payment_response: TransactionReceipt = pending_payment_transaction.await.unwrap().unwrap(); - log::info!("{:?}", payment_response); + log::info!("{:?}", payment_transaction_receipt); log::debug!( "Sender's balance after paying: {:?}", diff --git a/src/eip712/transaction_request.rs b/src/eip712/transaction_request.rs index 23d3e9b..ddefc2e 100644 --- a/src/eip712/transaction_request.rs +++ b/src/eip712/transaction_request.rs @@ -1,15 +1,21 @@ -use super::{rlp_append_option, Eip712Meta}; +use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr}; + +use super::{hash_bytecode, rlp_append_option, Eip712Meta}; use crate::{ - zks_utils::{EIP712_TX_TYPE, ERA_CHAIN_ID, MAX_PRIORITY_FEE_PER_GAS}, - zks_wallet::Overrides, + zks_utils::{ + self, CONTRACT_DEPLOYER_ADDR, EIP712_TX_TYPE, ERA_CHAIN_ID, MAX_PRIORITY_FEE_PER_GAS, + }, + zks_wallet::{DeployRequest, Overrides, TransferRequest, WithdrawRequest, ZKRequestError}, }; use ethers::{ + abi::{Abi, HumanReadableParser, ParseError}, types::{ transaction::{eip2930::AccessList, eip712::Eip712Error}, Address, Bytes, Signature, U256, }, utils::rlp::{Encodable, RlpStream}, }; +use ethers_contract::encode_function_data; use serde::{Deserialize, Serialize}; // TODO: Not all the fields are optional. This was copied from the JS implementation. @@ -233,3 +239,98 @@ impl Default for Eip712TransactionRequest { } } } + +impl TryFrom for Eip712TransactionRequest { + type Error = ZKRequestError; + + fn try_from(request: WithdrawRequest) -> Result { + let contract_address = + Address::from_str(zks_utils::CONTRACTS_L2_ETH_TOKEN_ADDR).map_err(|e| { + ZKRequestError::CustomError(format!("Error getting L2 ETH token address {e:?}")) + })?; + let function_signature = "function withdraw(address _l1Receiver) external payable override"; + let function = HumanReadableParser::parse_function(function_signature) + .map_err(ParseError::LexerError)?; + let function_args = function.decode_input(&zks_utils::encode_args( + &function, + &[format!("{:?}", request.to)], + )?)?; + let data: Bytes = function.encode_input(&function_args)?.into(); + + Ok(Eip712TransactionRequest::new() + .r#type(EIP712_TX_TYPE) + .to(contract_address) + .value(request.amount) + .from(request.from) + .data(data)) + } +} + +impl From for Eip712TransactionRequest { + fn from(request: TransferRequest) -> Self { + Eip712TransactionRequest::new() + .r#type(EIP712_TX_TYPE) + .to(request.to) + .value(request.amount) + .from(request.from) + } +} + +impl TryFrom for Eip712TransactionRequest { + type Error = ZKRequestError; + + fn try_from(request: DeployRequest) -> Result { + let mut contract_deployer_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + contract_deployer_path.push("src/abi/ContractDeployer.json"); + + let custom_data = Eip712Meta::new().factory_deps({ + let mut factory_deps = Vec::new(); + if let Some(factory_dependencies) = request.factory_deps { + factory_deps.extend(factory_dependencies); + } + factory_deps.push(request.contract_bytecode.clone()); + factory_deps + }); + + let contract_deployer = Abi::load(BufReader::new( + File::open(contract_deployer_path).map_err(|e| { + ZKRequestError::CustomError(format!( + "Error opening contract deployer abi file {e:?}" + )) + })?, + ))?; + let create = contract_deployer.function("create")?; + + // TODO: User could provide this instead of defaulting. + let salt = [0_u8; 32]; + let bytecode_hash = hash_bytecode(&request.contract_bytecode).map_err(|e| { + ZKRequestError::CustomError(format!("Error hashing contract bytecode {e:?}")) + })?; + let call_data: Bytes = match ( + request.contract_abi.constructor(), + request.constructor_parameters.is_empty(), + ) { + (None, false) => { + return Err(ZKRequestError::CustomError( + "Constructor not present".to_owned(), + )) + } + (None, true) | (Some(_), true) => Bytes::default(), + (Some(constructor), false) => { + zks_utils::encode_constructor_args(constructor, &request.constructor_parameters)? + .into() + } + }; + + let data = encode_function_data(create, (salt, bytecode_hash, call_data))?; + + let contract_deployer_address = Address::from_str(CONTRACT_DEPLOYER_ADDR).map_err(|e| { + ZKRequestError::CustomError(format!("Error getting contract deployer address {e:?}")) + })?; + Ok(Eip712TransactionRequest::new() + .r#type(EIP712_TX_TYPE) + .to(contract_deployer_address) + .custom_data(custom_data) + .data(data)) + } +} diff --git a/src/lib.rs b/src/lib.rs index e972b8d..9b0a2ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,9 +46,9 @@ pub mod contracts; pub mod eip712; pub mod zks_provider; pub mod zks_utils; - pub mod zks_wallet; -pub use zks_wallet::{DepositRequest, ZKSWallet, ZKSWalletError}; + +pub use zks_wallet::{ZKSWallet, ZKSWalletError}; // For macro expansions only, not public API. #[allow(unused_extern_crates)] diff --git a/src/tests/provider_tests.rs b/src/tests/provider_tests.rs index dfd03b9..c9a6a74 100644 --- a/src/tests/provider_tests.rs +++ b/src/tests/provider_tests.rs @@ -1,10 +1,11 @@ +#[cfg(test)] mod zks_provider_tests { use std::{collections::HashMap, fs::File, path::PathBuf, str::FromStr}; use crate::{ tests::utils::*, zks_provider::{types::TracerConfig, ZKSProvider}, - zks_wallet::ZKSWallet, + zks_wallet::{CallRequest, DeployRequest, TransferRequest, ZKSWallet}, }; use ethers::{ abi::Tokenize, @@ -356,20 +357,13 @@ mod zks_provider_tests { #[tokio::test] async fn test_provider_debug_trace_transaction() { let era_provider = era_provider(); - let zk_wallet = ZKSWallet::new(local_wallet(), None, Some(era_signer()), None).unwrap(); + let zk_wallet = + ZKSWallet::new(local_wallet(), None, Some(era_provider.clone()), None).unwrap(); - let transaction_hash = zk_wallet - .transfer( - Address::from_str("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049").unwrap(), - 1_u64.into(), - None, - ) - .await - .unwrap() - .await - .unwrap() - .unwrap() - .transaction_hash; + let transfer_request = TransferRequest::new(1_u64.into()) + .to(Address::from_str("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049").unwrap()) + .from(zk_wallet.l2_address()); + let transaction_hash = zk_wallet.transfer(&transfer_request, None).await.unwrap(); let invalid_transaction_hash: H256 = "0x84472204e445cb3cd5f3ce5e23abcc2892cda5e61b35855a7f0bb1562a6e30e7" .parse() @@ -753,18 +747,10 @@ mod zks_provider_tests { let zk_wallet = ZKSWallet::new(local_wallet(), None, Some(era_signer.clone()), None).unwrap(); - let transaction_hash = zk_wallet - .transfer( - Address::from_str("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049").unwrap(), - 1_i32.into(), - None, - ) - .await - .unwrap() - .await - .unwrap() - .unwrap() - .transaction_hash; + let transfer_request = TransferRequest::new(1_u64.into()) + .to(Address::from_str("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049").unwrap()) + .from(zk_wallet.l2_address()); + let transaction_hash = zk_wallet.transfer(&transfer_request, None).await.unwrap(); let invalid_transaction_hash: H256 = "0x84472204e445cb3cd5f3ce5e23abcc2892cda5e61b35855a7f0bb1562a6e30e7" .parse() @@ -807,22 +793,15 @@ mod zks_provider_tests { let contract: CompiledContract = serde_json::from_reader(File::open(contract_path).unwrap()).unwrap(); - let transaction_receipt = zk_wallet - .deploy( - contract.abi, - contract.bin.to_vec(), - vec!["0".to_owned()], - None, - ) + let deploy_request = + DeployRequest::with(contract.abi, contract.bin.to_vec(), vec!["0".to_owned()]) + .from(zk_wallet.l2_address()); + let contract_address = zk_wallet.deploy(&deploy_request).await.unwrap(); + let call_request = CallRequest::new(contract_address, "getValue()(uint256)".to_owned()); + let initial_value = ZKSProvider::call(&era_provider, &call_request) .await .unwrap(); - let contract_address = transaction_receipt.contract_address.unwrap(); - let initial_value = - ZKSProvider::call(&era_provider, contract_address, "getValue()(uint256)", None) - .await - .unwrap(); - assert_eq!(initial_value, U256::from(0_i32).into_tokens()); let value_to_set = String::from("10"); @@ -836,12 +815,12 @@ mod zks_provider_tests { ) .await .unwrap() + .await + .unwrap() + .unwrap(); + let set_value = ZKSProvider::call(&era_provider, &call_request) .await .unwrap(); - let set_value = - ZKSProvider::call(&era_provider, contract_address, "getValue()(uint256)", None) - .await - .unwrap(); assert_eq!( set_value, @@ -858,12 +837,12 @@ mod zks_provider_tests { ) .await .unwrap() + .await + .unwrap() + .unwrap(); + let incremented_value = ZKSProvider::call(&era_provider, &call_request) .await .unwrap(); - let incremented_value = - ZKSProvider::call(&era_provider, contract_address, "getValue()(uint256)", None) - .await - .unwrap(); assert_eq!( incremented_value, @@ -882,13 +861,10 @@ mod zks_provider_tests { let contract: CompiledContract = serde_json::from_reader(File::open(contract_path).unwrap()).unwrap(); - let transaction_receipt = zk_wallet - .deploy(contract.abi, contract.bin.to_vec(), vec![], None) - .await - .unwrap(); - - let contract_address = transaction_receipt.contract_address.unwrap(); - let output = ZKSProvider::call(&era_provider, contract_address, "str_out()(string)", None) + let deploy_request = DeployRequest::with(contract.abi, contract.bin.to_vec(), vec![]); + let contract_address = zk_wallet.deploy(&deploy_request).await.unwrap(); + let call_request = CallRequest::new(contract_address, "str_out()(string)".to_owned()); + let output = ZKSProvider::call(&era_provider, &call_request) .await .unwrap(); @@ -907,29 +883,19 @@ mod zks_provider_tests { let contract: CompiledContract = serde_json::from_reader(File::open(contract_path).unwrap()).unwrap(); - let transaction_receipt = zk_wallet - .deploy(contract.abi, contract.bin.to_vec(), vec![], None) + let deploy_request = DeployRequest::with(contract.abi, contract.bin.to_vec(), vec![]) + .from(zk_wallet.l2_address()); + let contract_address = zk_wallet.deploy(&deploy_request).await.unwrap(); + let call_request = CallRequest::new(contract_address, "plus_one(uint256)".to_owned()) + .function_parameters(vec!["1".to_owned()]); + let no_return_type_output = ZKSProvider::call(&era_provider, &call_request) .await .unwrap(); - let contract_address = transaction_receipt.contract_address.unwrap(); - let no_return_type_output = ZKSProvider::call( - &era_provider, - contract_address, - "plus_one(uint256)", - Some(vec!["1".to_owned()]), - ) - .await - .unwrap(); - - let known_return_type_output = ZKSProvider::call( - &era_provider, - contract_address, - "plus_one(uint256)(uint256)", - Some(vec!["1".to_owned()]), - ) - .await - .unwrap(); + let call_request = call_request.function_signature("plus_one(uint256)(uint256)".to_owned()); + let known_return_type_output = ZKSProvider::call(&era_provider, &call_request) + .await + .unwrap(); assert_eq!( no_return_type_output, diff --git a/src/tests/wallet_tests.rs b/src/tests/wallet_tests.rs index 71ba70e..8263406 100644 --- a/src/tests/wallet_tests.rs +++ b/src/tests/wallet_tests.rs @@ -1,8 +1,10 @@ mod zks_signer_tests { + use crate::tests::utils::*; use crate::zks_provider::ZKSProvider; - use crate::zks_utils::ETH_CHAIN_ID; - use crate::zks_wallet::ZKSWallet; - use crate::{tests::utils::*, DepositRequest}; + use crate::zks_utils::{ERA_CHAIN_ID, ETH_CHAIN_ID}; + use crate::zks_wallet::{ + CallRequest, DeployRequest, DepositRequest, TransferRequest, WithdrawRequest, ZKSWallet, + }; use ethers::abi::Tokenize; use ethers::providers::Middleware; use ethers::signers::{LocalWallet, Signer}; @@ -15,13 +17,17 @@ mod zks_signer_tests { #[tokio::test] async fn test_transfer() { + let sender_private_key = + "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"; let receiver_address: Address = "0xa61464658AfeAf65CccaaFD3a512b69A83B77618" .parse() .unwrap(); let amount_to_transfer: U256 = 1_i32.into(); let era_provider = era_provider(); - let wallet = local_wallet(); + let wallet = LocalWallet::from_str(sender_private_key) + .unwrap() + .with_chain_id(ERA_CHAIN_ID); let zk_wallet = ZKSWallet::new(wallet, None, Some(era_provider.clone()), None).unwrap(); let sender_balance_before = era_provider @@ -35,13 +41,14 @@ mod zks_signer_tests { println!("Sender balance before: {sender_balance_before}"); println!("Receiver balance before: {receiver_balance_before}"); - println!("Sender balance before: {sender_balance_before}"); - println!("Receiver balance before: {receiver_balance_before}"); - let receipt = zk_wallet - .transfer(receiver_address, amount_to_transfer, None) - .await - .unwrap() + let request = TransferRequest::new(amount_to_transfer) + .to(receiver_address) + .from(zk_wallet.l2_address()); + let tx_hash = zk_wallet.transfer(&request, None).await.unwrap(); + + let receipt = era_provider + .get_transaction_receipt(tx_hash) .await .unwrap() .unwrap(); @@ -75,9 +82,15 @@ mod zks_signer_tests { #[tokio::test] async fn test_deposit() { + let private_key = "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"; + let request = DepositRequest::new(parse_units("0.01", "ether").unwrap().into()); + println!("Amount: {}", request.amount); + let l1_provider = eth_provider(); let l2_provider = era_provider(); - let wallet = local_wallet(); + let wallet = LocalWallet::from_str(private_key) + .unwrap() + .with_chain_id(ERA_CHAIN_ID); let zk_wallet = ZKSWallet::new( wallet, None, @@ -91,9 +104,12 @@ mod zks_signer_tests { println!("L1 balance before: {l1_balance_before}"); println!("L2 balance before: {l2_balance_before}"); - let request = DepositRequest::new(parse_units("0.01", "ether").unwrap().into()); - println!("Amount: {}", request.amount); - let receipt = zk_wallet.deposit(&request).await.unwrap(); + let tx_hash = zk_wallet.deposit(&request).await.unwrap(); + let receipt = l1_provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert_eq!(receipt.status.unwrap(), 1_u8.into()); let _l2_receipt = l2_provider @@ -118,6 +134,7 @@ mod zks_signer_tests { #[tokio::test] async fn test_deposit_to_another_address() { + let private_key = "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"; let to: Address = "0xa61464658AfeAf65CccaaFD3a512b69A83B77618" .parse() .unwrap(); @@ -128,7 +145,7 @@ mod zks_signer_tests { let l1_provider = eth_provider(); let l2_provider = era_provider(); - let wallet = local_wallet(); + let wallet = LocalWallet::from_str(private_key).unwrap(); let zk_wallet = ZKSWallet::new( wallet, None, @@ -142,7 +159,12 @@ mod zks_signer_tests { println!("L1 balance before: {l1_balance_before}"); println!("L2 balance before: {l2_balance_before}"); - let receipt = zk_wallet.deposit(&request).await.unwrap(); + let tx_hash = zk_wallet.deposit(&request).await.unwrap(); + let receipt = l1_provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert_eq!(receipt.status.unwrap(), 1_u8.into()); let _l2_receipt = l2_provider @@ -167,13 +189,17 @@ mod zks_signer_tests { #[tokio::test] async fn test_transfer_eip712() { + let sender_private_key = + "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"; let receiver_address: Address = "0xa61464658AfeAf65CccaaFD3a512b69A83B77618" .parse() .unwrap(); let amount_to_transfer: U256 = 1_i32.into(); let era_provider = era_provider(); - let wallet = local_wallet(); + let wallet = LocalWallet::from_str(sender_private_key) + .unwrap() + .with_chain_id(ERA_CHAIN_ID); let zk_wallet = ZKSWallet::new(wallet, None, Some(era_provider.clone()), None).unwrap(); let sender_balance_before = era_provider @@ -188,14 +214,19 @@ mod zks_signer_tests { println!("Sender balance before: {sender_balance_before}"); println!("Receiver balance before: {receiver_balance_before}"); - let receipt = zk_wallet - .transfer_eip712(receiver_address, amount_to_transfer, None) + let transfer_request = TransferRequest::new(amount_to_transfer) + .to(receiver_address) + .from(zk_wallet.l2_address()); + let tx_hash = zk_wallet + .transfer_eip712(&transfer_request, None) .await - .unwrap() + .unwrap(); + + let receipt = era_provider + .get_transaction_receipt(tx_hash) .await .unwrap() .unwrap(); - assert_eq!(receipt.from, zk_wallet.l2_address()); assert_eq!(receipt.to.unwrap(), receiver_address); @@ -225,8 +256,12 @@ mod zks_signer_tests { #[tokio::test] async fn test_deploy_contract_with_constructor_arg_uint() { + let deployer_private_key = + "7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"; let era_provider = era_provider(); - let wallet = local_wallet(); + let wallet = LocalWallet::from_str(deployer_private_key) + .unwrap() + .with_chain_id(ERA_CHAIN_ID); let zk_wallet = ZKSWallet::new(wallet, None, Some(era_provider.clone()), None).unwrap(); let mut contract_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -234,17 +269,10 @@ mod zks_signer_tests { let contract: CompiledContract = serde_json::from_reader(File::open(contract_path).unwrap()).unwrap(); - let transaction_receipt = zk_wallet - .deploy( - contract.abi, - contract.bin.to_vec(), - vec!["10".to_owned()], - None, - ) - .await - .unwrap(); - - let contract_address = transaction_receipt.contract_address.unwrap(); + let deploy_request = + DeployRequest::with(contract.abi, contract.bin.to_vec(), vec!["10".to_owned()]) + .from(zk_wallet.l2_address()); + let contract_address = zk_wallet.deploy(&deploy_request).await.unwrap(); let deploy_result = era_provider.get_code(contract_address, None).await; assert!(deploy_result.is_ok()); @@ -252,8 +280,12 @@ mod zks_signer_tests { #[tokio::test] async fn test_deploy_contract_with_constructor_arg_string() { + let deployer_private_key = + "7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"; let era_provider = era_provider(); - let wallet = local_wallet(); + let wallet = LocalWallet::from_str(deployer_private_key) + .unwrap() + .with_chain_id(ERA_CHAIN_ID); let zk_wallet = ZKSWallet::new(wallet, None, Some(era_provider.clone()), None).unwrap(); let mut contract_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -261,17 +293,10 @@ mod zks_signer_tests { let contract: CompiledContract = serde_json::from_reader(File::open(contract_path).unwrap()).unwrap(); - let transaction_receipt = zk_wallet - .deploy( - contract.abi, - contract.bin.to_vec(), - vec!["Hey".to_owned()], - None, - ) - .await - .unwrap(); - - let contract_address = transaction_receipt.contract_address.unwrap(); + let deploy_request = + DeployRequest::with(contract.abi, contract.bin.to_vec(), vec!["Hey".to_owned()]) + .from(zk_wallet.l2_address()); + let contract_address = zk_wallet.deploy(&deploy_request).await.unwrap(); let deploy_result = era_provider.get_code(contract_address, None).await; assert!(deploy_result.is_ok()); @@ -279,8 +304,12 @@ mod zks_signer_tests { #[tokio::test] async fn test_deploy_contract_with_import() { + let deployer_private_key = + "7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"; let era_provider = era_provider(); - let wallet = local_wallet(); + let wallet = LocalWallet::from_str(deployer_private_key) + .unwrap() + .with_chain_id(ERA_CHAIN_ID); let zk_wallet = ZKSWallet::new(wallet, None, Some(era_provider.clone()), None).unwrap(); // Deploy imported contract first. @@ -289,17 +318,10 @@ mod zks_signer_tests { let counter_contract: CompiledContract = serde_json::from_reader(File::open(contract_path).unwrap()).unwrap(); - let transaction_receipt = zk_wallet - .deploy( - counter_contract.abi, - counter_contract.bin.to_vec(), - vec![], - None, - ) - .await - .unwrap(); - - let counter_contract_address = transaction_receipt.contract_address.unwrap(); + let deploy_request = + DeployRequest::with(counter_contract.abi, counter_contract.bin.to_vec(), vec![]) + .from(zk_wallet.l2_address()); + let counter_contract_address = zk_wallet.deploy(&deploy_request).await.unwrap(); let deploy_result = era_provider.get_code(counter_contract_address, None).await; assert!(deploy_result.is_ok()); @@ -311,32 +333,31 @@ mod zks_signer_tests { let import_contract: CompiledContract = serde_json::from_reader(File::open(contract_path).unwrap()).unwrap(); - let transaction_receipt = zk_wallet - .deploy( - import_contract.abi, - import_contract.bin.to_vec(), - vec![format!("{counter_contract_address:?}")], - None, - ) + let deploy_request = DeployRequest::with( + import_contract.abi, + import_contract.bin.to_vec(), + vec![format!("{counter_contract_address:?}")], + ) + .from(zk_wallet.l2_address()); + let import_contract_address = zk_wallet.deploy(&deploy_request).await.unwrap(); + let call_request = CallRequest::new( + import_contract_address, + "getCounterValue()(uint256)".to_owned(), + ); + let value = ZKSProvider::call(&era_provider, &call_request) .await .unwrap(); - let import_contract_address = transaction_receipt.contract_address.unwrap(); - let value = ZKSProvider::call( - &era_provider, - import_contract_address, - "getCounterValue()(uint256)", - None, - ) - .await - .unwrap(); - assert_eq!(value, U256::from(0_u64).into_tokens()); } #[tokio::test] async fn test_withdraw_to_same_address() { - let wallet = local_wallet(); + let sender_private_key = + "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"; + let wallet = LocalWallet::from_str(sender_private_key) + .unwrap() + .with_chain_id(ERA_CHAIN_ID); let zk_wallet = ZKSWallet::new(wallet, None, Some(era_provider()), Some(eth_provider())).unwrap(); @@ -349,18 +370,13 @@ mod zks_signer_tests { // Withdraw let amount_to_withdraw: U256 = parse_units(1_u8, "ether").unwrap().into(); - let tx_receipt = zk_wallet - .withdraw(amount_to_withdraw, zk_wallet.l1_address()) - .await - .unwrap() - .await - .unwrap() - .unwrap(); + let withdraw_request = WithdrawRequest::new(amount_to_withdraw).to(zk_wallet.l1_address()); + let tx_hash = zk_wallet.withdraw(&withdraw_request).await.unwrap(); let tx_receipt = zk_wallet .get_era_provider() .unwrap() - .wait_for_finalize(tx_receipt, None, None) + .wait_for_finalize(tx_hash, None, None) .await .unwrap(); assert_eq!( @@ -386,14 +402,15 @@ mod zks_signer_tests { "Check that L1 balance has not changed" ); + let tx_finalize_hash = zk_wallet.finalize_withdraw(tx_hash).await.unwrap(); + let tx_finalize_receipt = zk_wallet - .finalize_withdraw(tx_receipt.transaction_hash) - .await + .get_eth_provider() .unwrap() + .get_transaction_receipt(tx_finalize_hash) .await .unwrap() .unwrap(); - println!( "L1 Transaction hash: {:?}", tx_finalize_receipt.transaction_hash @@ -433,9 +450,13 @@ mod zks_signer_tests { #[tokio::test] async fn test_withdraw_to_other_address() { + let sender_private_key = + "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"; let receiver_private_key = "0xe667e57a9b8aaa6709e51ff7d093f1c5b73b63f9987e4ab4aa9a5c699e024ee8"; - let l2_wallet = local_wallet(); + let l2_wallet = LocalWallet::from_str(sender_private_key) + .unwrap() + .with_chain_id(ERA_CHAIN_ID); let l1_wallet = LocalWallet::from_str(receiver_private_key) .unwrap() @@ -457,14 +478,8 @@ mod zks_signer_tests { // Withdraw let amount_to_withdraw: U256 = parse_units(1_u8, "ether").unwrap().into(); - let tx_receipt = zk_wallet - .withdraw(amount_to_withdraw, zk_wallet.l1_address()) - .await - .unwrap() - .await - .unwrap() - .unwrap(); - + let withdraw_request = WithdrawRequest::new(amount_to_withdraw).to(zk_wallet.l1_address()); + let tx_receipt = zk_wallet.withdraw(&withdraw_request).await.unwrap(); let tx_receipt = zk_wallet .get_era_provider() .unwrap() @@ -494,14 +509,18 @@ mod zks_signer_tests { "Check that L1 balance has not changed" ); - let tx_finalize_receipt = zk_wallet + let tx_finalize_hash = zk_wallet .finalize_withdraw(tx_receipt.transaction_hash) .await + .unwrap(); + + let tx_finalize_receipt = zk_wallet + .get_eth_provider() .unwrap() + .get_transaction_receipt(tx_finalize_hash) .await .unwrap() .unwrap(); - println!( "L1 Transaction hash: {:?}", tx_finalize_receipt.transaction_hash diff --git a/src/zks_provider/mod.rs b/src/zks_provider/mod.rs index f570e30..5a95f60 100644 --- a/src/zks_provider/mod.rs +++ b/src/zks_provider/mod.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; use ethers::{ - abi::{encode, HumanReadableParser, Token, Tokenize}, + abi::{HumanReadableParser, Token, Tokenize}, prelude::{ k256::{ ecdsa::{RecoveryId, Signature as RecoverableSignature}, @@ -12,8 +12,8 @@ use ethers::{ signers::{Signer, Wallet}, types::{ transaction::{eip2718::TypedTransaction, eip712::Eip712Error}, - Address, BlockNumber, Eip1559TransactionRequest, Signature, TransactionReceipt, H256, U256, - U64, + Address, BlockNumber, Eip1559TransactionRequest, Signature, TransactionReceipt, TxHash, + H256, U256, U64, }, }; use ethers_contract::providers::PendingTransaction; @@ -27,10 +27,8 @@ use types::Fee; use crate::{ eip712::{Eip712Meta, Eip712Transaction, Eip712TransactionRequest}, - zks_utils::{ - self, is_precompile, DEFAULT_GAS, EIP712_TX_TYPE, MAX_FEE_PER_GAS, MAX_PRIORITY_FEE_PER_GAS, - }, - zks_wallet::Overrides, + zks_utils::{self, DEFAULT_GAS, EIP712_TX_TYPE, MAX_FEE_PER_GAS, MAX_PRIORITY_FEE_PER_GAS}, + zks_wallet::{CallRequest, Overrides}, }; use self::types::{ @@ -214,17 +212,21 @@ pub trait ZKSProvider { async fn wait_for_finalize( &self, - transaction_receipt: TransactionReceipt, + transaction_receipt: TxHash, polling_time_in_seconds: Option, timeout_in_seconds: Option, ) -> Result; - async fn call( + async fn call(&self, request: &CallRequest) -> Result, ProviderError>; + + async fn send_transaction_eip712( &self, - contract_address: Address, - function_signature: &str, - function_parameters: Option>, - ) -> Result, ProviderError>; + wallet: &Wallet, + transaction: T, + ) -> Result, ProviderError> + where + T: TryInto + Send + Sync + Debug, + D: PrehashSigner<(RecoverableSignature, RecoveryId)> + Send + Sync; } #[async_trait] @@ -446,9 +448,23 @@ impl ZKSProvider for SignerMiddleware( + &self, + wallet: &Wallet, + transaction: T, + ) -> Result, ProviderError> + where + T: TryInto + Sync + Send + Debug, + D: PrehashSigner<(RecoverableSignature, RecoveryId)> + Send + Sync, + { + self.inner() + .send_transaction_eip712(wallet, transaction) + .await + } + async fn wait_for_finalize( &self, - transaction_receipt: TransactionReceipt, + transaction_receipt: TxHash, polling_time_in_seconds: Option, timeout_in_seconds: Option, ) -> Result { @@ -461,19 +477,8 @@ impl ZKSProvider for SignerMiddleware>, - ) -> Result, ProviderError> { - ZKSProvider::call( - self.inner(), - contract_address, - function_signature, - function_parameters, - ) - .await + async fn call(&self, request: &CallRequest) -> Result, ProviderError> { + ZKSProvider::call(self.inner(), request).await } } @@ -686,6 +691,49 @@ impl ZKSProvider for Provider

{ .await } + async fn send_transaction_eip712( + &self, + wallet: &Wallet, + transaction: T, + ) -> Result, ProviderError> + where + T: TryInto + Sync + Send + Debug, + D: PrehashSigner<(RecoverableSignature, RecoveryId)> + Send + Sync, + { + let mut request: Eip712TransactionRequest = transaction.try_into().map_err(|_e| { + ProviderError::CustomError("error on send_transaction_eip712".to_owned()) + })?; + + request = request + .from(wallet.address()) + .chain_id(wallet.chain_id()) + .nonce(self.get_transaction_count(wallet.address(), None).await?) + .gas_price(self.get_gas_price().await?) + .max_fee_per_gas(self.get_gas_price().await?); + + let custom_data = request.clone().custom_data; + let fee = self.estimate_fee(request.clone()).await?; + request = request + .max_priority_fee_per_gas(fee.max_priority_fee_per_gas) + .max_fee_per_gas(fee.max_fee_per_gas) + .gas_limit(fee.gas_limit); + let signable_data: Eip712Transaction = request + .clone() + .try_into() + .map_err(|e: Eip712Error| ProviderError::CustomError(e.to_string()))?; + let signature: Signature = wallet + .sign_typed_data(&signable_data) + .await + .map_err(|e| ProviderError::CustomError(format!("error signing transaction: {e}")))?; + request = request.custom_data(custom_data.custom_signature(signature.to_vec())); + let encoded_rlp = &*request + .rlp_signed(signature) + .map_err(|e| ProviderError::CustomError(format!("Error in the rlp encoding {e}")))?; + + self.send_raw_transaction([&[EIP712_TX_TYPE], encoded_rlp].concat().into()) + .await + } + async fn send_eip712( &self, wallet: &Wallet, @@ -784,7 +832,7 @@ impl ZKSProvider for Provider

{ async fn wait_for_finalize( &self, - transaction_receipt: TransactionReceipt, + tx_hash: TxHash, polling_time_in_seconds: Option, timeout_in_seconds: Option, ) -> Result { @@ -792,6 +840,13 @@ impl ZKSProvider for Provider

{ let mut timer = tokio::time::interval(polling_time_in_seconds); let start = Instant::now(); + let transaction_receipt = + self.get_transaction_receipt(tx_hash) + .await? + .ok_or(ProviderError::CustomError( + "No transaction receipt".to_owned(), + ))?; + loop { timer.tick().await; @@ -818,52 +873,14 @@ impl ZKSProvider for Provider

{ } } - async fn call( - &self, - contract_address: Address, - function_signature: &str, - function_parameters: Option>, - ) -> Result, ProviderError> { - // Note: We couldn't implement ZKSWalletError::LexerError because ethers-rs's LexerError is not exposed. - let function = if contract_address == zks_utils::ECADD_PRECOMPILE_ADDRESS { - zks_utils::ec_add_function() - } else if contract_address == zks_utils::ECMUL_PRECOMPILE_ADDRESS { - zks_utils::ec_mul_function() - } else if contract_address == zks_utils::MODEXP_PRECOMPILE_ADDRESS { - zks_utils::mod_exp_function() - } else { - HumanReadableParser::parse_function(function_signature) - .map_err(|e| ProviderError::CustomError(e.to_string()))? - }; - let function_args = if let Some(function_args) = function_parameters { - function - .decode_input( - &zks_utils::encode_args(&function, &function_args) - .map_err(|e| ProviderError::CustomError(e.to_string()))?, - ) - .map_err(|e| ProviderError::CustomError(e.to_string()))? - } else { - vec![] - }; - - log::info!("{function_args:?}"); - - let request: Eip1559TransactionRequest = - Eip1559TransactionRequest::new().to(contract_address).data( - match (!function_args.is_empty(), is_precompile(contract_address)) { - // The contract to call is a precompile with arguments. - (true, true) => encode(&function_args), - // The contract to call is a regular contract with arguments. - (true, false) => function - .encode_input(&function_args) - .map_err(|e| ProviderError::CustomError(e.to_string()))?, - // The contract to call is a precompile without arguments. - (false, true) => Default::default(), - // The contract to call is a regular contract without arguments. - (false, false) => function.short_signature().into(), - }, - ); - + async fn call(&self, request: &CallRequest) -> Result, ProviderError> { + let function = request + .get_parsed_function() + .map_err(|e| ProviderError::CustomError(format!("Failed to parse function: {e}")))?; + let request: Eip1559TransactionRequest = request + .clone() + .try_into() + .map_err(|e| ProviderError::CustomError(format!("Failed to convert request: {e}")))?; let transaction: TypedTransaction = request.into(); let encoded_output = Middleware::call(self, &transaction, None).await?; diff --git a/src/zks_wallet/errors.rs b/src/zks_wallet/errors.rs index 449561e..317e456 100644 --- a/src/zks_wallet/errors.rs +++ b/src/zks_wallet/errors.rs @@ -1,4 +1,5 @@ use ethers::{ + abi::{Error, ParseError}, prelude::{ k256::{ ecdsa::{RecoveryId, Signature as RecoverableSignature}, @@ -36,6 +37,8 @@ where NoL2ProviderError(), #[error("Contract error: {0}")] ContractError(#[from] ContractError), + #[error("Contract error: {0}")] + RequestConversionError(#[from] ZKRequestError), #[error("{0}")] CustomError(String), #[error("Main contract error: {0}")] @@ -51,3 +54,15 @@ where Self::CustomError(format!("{value:?}")) } } + +#[derive(thiserror::Error, Debug)] +pub enum ZKRequestError { + #[error("Error parsing function: {0}")] + ParseFunctionError(#[from] ParseError), + #[error("ABI error: {0}")] + AbiError(#[from] AbiError), + #[error("Encoding or decoding error: {0}")] + Error(#[from] Error), + #[error("{0}")] + CustomError(String), +} diff --git a/src/zks_wallet/mod.rs b/src/zks_wallet/mod.rs index 312f5e8..f408b6c 100644 --- a/src/zks_wallet/mod.rs +++ b/src/zks_wallet/mod.rs @@ -1,11 +1,16 @@ mod errors; -pub use errors::ZKSWalletError; +pub use errors::{ZKRequestError, ZKSWalletError}; + +mod requests; +pub use requests::{ + call_request::CallRequest, deploy_request::DeployRequest, deposit_request::DepositRequest, + transfer_request::TransferRequest, withdraw_request::WithdrawRequest, +}; mod wallet; -use ethers::types::U256; -pub use wallet::deposit_request::DepositRequest; pub use wallet::ZKSWallet; +use ethers::types::U256; pub struct Overrides { pub value: Option, } diff --git a/src/zks_wallet/requests/call_request.rs b/src/zks_wallet/requests/call_request.rs new file mode 100644 index 0000000..90e5985 --- /dev/null +++ b/src/zks_wallet/requests/call_request.rs @@ -0,0 +1,81 @@ +use ethers::{ + abi::{encode, Function, HumanReadableParser, ParseError}, + types::{Address, Eip1559TransactionRequest}, +}; +use std::fmt::Debug; + +use crate::{ + zks_utils::{self, is_precompile}, + zks_wallet::errors::ZKRequestError, +}; + +#[derive(Clone, Debug)] +pub struct CallRequest { + pub to: Address, + pub function_signature: String, + pub function_parameters: Option>, +} + +impl CallRequest { + pub fn new(to: Address, function_signature: String) -> Self { + Self { + to, + function_signature, + function_parameters: None, + } + } + + pub fn function_parameters(mut self, function_parameters: Vec) -> Self { + self.function_parameters = Some(function_parameters); + self + } + + pub fn to(mut self, to: Address) -> Self { + self.to = to; + self + } + + pub fn function_signature(mut self, function_signature: String) -> Self { + self.function_signature = function_signature; + self + } + + pub fn get_parsed_function(&self) -> Result { + if self.to == zks_utils::ECADD_PRECOMPILE_ADDRESS { + Ok(zks_utils::ec_add_function()) + } else if self.to == zks_utils::ECMUL_PRECOMPILE_ADDRESS { + Ok(zks_utils::ec_mul_function()) + } else if self.to == zks_utils::MODEXP_PRECOMPILE_ADDRESS { + Ok(zks_utils::mod_exp_function()) + } else { + HumanReadableParser::parse_function(&self.function_signature) + .map_err(ParseError::LexerError) + } + } +} + +impl TryFrom for Eip1559TransactionRequest { + type Error = ZKRequestError; + + fn try_from(request: CallRequest) -> Result { + let function = request.get_parsed_function()?; + let function_args = if let Some(function_args) = request.function_parameters { + function.decode_input(&zks_utils::encode_args(&function, &function_args)?)? + } else { + vec![] + }; + + let data = match (!function_args.is_empty(), is_precompile(request.to)) { + // The contract to call is a precompile with arguments. + (true, true) => encode(&function_args), + // The contract to call is a regular contract with arguments. + (true, false) => function.encode_input(&function_args)?, + // The contract to call is a precompile without arguments. + (false, true) => Default::default(), + // The contract to call is a regular contract without arguments. + (false, false) => function.short_signature().into(), + }; + + Ok(Eip1559TransactionRequest::new().to(request.to).data(data)) + } +} diff --git a/src/zks_wallet/requests/deploy_request.rs b/src/zks_wallet/requests/deploy_request.rs new file mode 100644 index 0000000..26bb6db --- /dev/null +++ b/src/zks_wallet/requests/deploy_request.rs @@ -0,0 +1,37 @@ +use ethers::{abi::Abi, types::Address}; +use std::fmt::Debug; + +#[derive(Clone, Debug)] +pub struct DeployRequest { + pub contract_abi: Abi, + pub contract_bytecode: Vec, + pub constructor_parameters: Vec, + pub from: Address, + pub factory_deps: Option>>, +} + +impl DeployRequest { + pub fn with( + contract_abi: Abi, + contract_bytecode: Vec, + constructor_parameters: Vec, + ) -> Self { + Self { + contract_abi, + contract_bytecode, + constructor_parameters, + from: Default::default(), + factory_deps: None, + } + } + + pub fn from(mut self, from: Address) -> Self { + self.from = from; + self + } + + pub fn factory_deps(mut self, factory_deps: Vec>) -> Self { + self.factory_deps = Some(factory_deps); + self + } +} diff --git a/src/zks_wallet/wallet/deposit_request.rs b/src/zks_wallet/requests/deposit_request.rs similarity index 98% rename from src/zks_wallet/wallet/deposit_request.rs rename to src/zks_wallet/requests/deposit_request.rs index 7c76adf..324e5e3 100644 --- a/src/zks_wallet/wallet/deposit_request.rs +++ b/src/zks_wallet/requests/deposit_request.rs @@ -16,6 +16,8 @@ fn default_l2_gas_limit() -> U256 { fn default_gas_per_pubdata_byte() -> U256 { DEPOSIT_GAS_PER_PUBDATA_LIMIT.into() } + +#[derive(Clone, Debug)] pub struct DepositRequest { pub amount: U256, pub to: Option

, diff --git a/src/zks_wallet/requests/mod.rs b/src/zks_wallet/requests/mod.rs new file mode 100644 index 0000000..f19ae5b --- /dev/null +++ b/src/zks_wallet/requests/mod.rs @@ -0,0 +1,5 @@ +pub mod call_request; +pub mod deploy_request; +pub mod deposit_request; +pub mod transfer_request; +pub mod withdraw_request; diff --git a/src/zks_wallet/requests/transfer_request.rs b/src/zks_wallet/requests/transfer_request.rs new file mode 100644 index 0000000..7f8ffe9 --- /dev/null +++ b/src/zks_wallet/requests/transfer_request.rs @@ -0,0 +1,43 @@ +use ethers::types::{Address, Eip1559TransactionRequest, U256}; +use std::fmt::Debug; + +#[derive(Clone, Debug)] +pub struct TransferRequest { + pub amount: U256, + pub to: Address, + pub from: Address, +} + +impl TransferRequest { + pub fn new(amount: U256) -> Self { + Self { + amount, + to: Default::default(), + from: Default::default(), + } + } + + pub fn from(mut self, from: Address) -> Self { + self.from = from; + self + } + + pub fn to(mut self, to: Address) -> Self { + self.to = to; + self + } + + pub fn amount(mut self, amount: U256) -> Self { + self.amount = amount; + self + } +} + +impl From for Eip1559TransactionRequest { + fn from(request: TransferRequest) -> Eip1559TransactionRequest { + Eip1559TransactionRequest::new() + .to(request.to) + .value(request.amount) + .from(request.from) + } +} diff --git a/src/zks_wallet/requests/withdraw_request.rs b/src/zks_wallet/requests/withdraw_request.rs new file mode 100644 index 0000000..f126efb --- /dev/null +++ b/src/zks_wallet/requests/withdraw_request.rs @@ -0,0 +1,30 @@ +use std::fmt::Debug; + +use ethers::types::{Address, U256}; + +#[derive(Clone, Debug)] +pub struct WithdrawRequest { + pub amount: U256, + pub to: Address, + pub from: Address, +} + +impl WithdrawRequest { + pub fn new(amount: U256) -> Self { + Self { + amount, + to: Default::default(), + from: Default::default(), + } + } + + pub fn to(mut self, to: Address) -> Self { + self.to = to; + self + } + + pub fn from(mut self, from: Address) -> Self { + self.from = from; + self + } +} diff --git a/src/zks_wallet/wallet.rs b/src/zks_wallet/wallet.rs index 042141a..97ce940 100644 --- a/src/zks_wallet/wallet.rs +++ b/src/zks_wallet/wallet.rs @@ -1,8 +1,7 @@ -pub mod deposit_request; - -use self::deposit_request::DepositRequest; - -use super::{Overrides, ZKSWalletError}; +use super::{ + requests::transfer_request::TransferRequest, DeployRequest, DepositRequest, WithdrawRequest, + ZKSWalletError, +}; use crate::{ contracts::main_contract::{MainContract, MainContractInstance}, eip712::Eip712Transaction, @@ -18,23 +17,23 @@ use ethers::{ ecdsa::{RecoveryId, Signature as RecoverableSignature}, schnorr::signature::hazmat::PrehashSigner, }, - ContractError, MiddlewareBuilder, SignerMiddleware, + MiddlewareBuilder, SignerMiddleware, }, providers::Middleware, signers::{Signer, Wallet}, types::{ transaction::eip2718::TypedTransaction, Address, Bytes, Eip1559TransactionRequest, Log, - Signature, TransactionReceipt, H160, H256, U256, + Signature, H160, H256, U256, }, }; -use ethers_contract::providers::PendingTransaction; use serde_json::Value; use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr, sync::Arc}; +#[derive(Clone, Debug)] pub struct ZKSWallet where - M: Middleware, - D: PrehashSigner<(RecoverableSignature, RecoveryId)>, + M: Middleware + Clone, + D: PrehashSigner<(RecoverableSignature, RecoveryId)> + Clone, { /// Eth provider pub eth_provider: Option>>>, @@ -45,7 +44,7 @@ where impl ZKSWallet where - M: Middleware + 'static, + M: Middleware + 'static + Clone, D: PrehashSigner<(RecoverableSignature, RecoveryId)> + Sync + Send + Clone, { pub fn new( @@ -152,24 +151,16 @@ where pub async fn transfer( &self, - to: Address, - amount_to_transfer: U256, + request: &TransferRequest, // TODO: Support multiple-token transfers. _token: Option
, - ) -> Result::Provider>, ZKSWalletError> + ) -> Result> where M: ZKSProvider, { - let era_provider = match &self.era_provider { - Some(era_provider) => era_provider, - None => return Err(ZKSWalletError::CustomError("no era provider".to_owned())), - }; + let era_provider = self.get_era_provider()?; - let mut transfer_request = Eip1559TransactionRequest::new() - .from(self.l2_address()) - .to(to) - .value(amount_to_transfer) - .chain_id(self.l2_chain_id()); + let mut transfer_request: Eip1559TransactionRequest = request.clone().into(); let fee = era_provider.estimate_fee(transfer_request.clone()).await?; transfer_request = transfer_request.max_priority_fee_per_gas(fee.max_priority_fee_per_gas); @@ -178,59 +169,40 @@ where let transaction: TypedTransaction = transfer_request.into(); // TODO: add block as an override. - let pending_transaction = era_provider.send_transaction(transaction, None).await?; - Ok(pending_transaction) + let transaction_receipt = era_provider + .send_transaction(transaction, None) + .await? + .await? + .ok_or(ZKSWalletError::CustomError( + "No transaction receipt".to_owned(), + ))?; + + Ok(transaction_receipt.transaction_hash) } pub async fn transfer_eip712( &self, - to: Address, - amount_to_transfer: U256, + request: &TransferRequest, // TODO: Support multiple-token transfers. _token: Option
, - ) -> Result::Provider>, ZKSWalletError> + ) -> Result> where M: ZKSProvider, { - let era_provider = match &self.era_provider { - Some(era_provider) => era_provider, - None => return Err(ZKSWalletError::CustomError("no era provider".to_owned())), - }; - - let mut transfer_request = Eip712TransactionRequest::new() - .from(self.l2_address()) - .to(to) - .value(amount_to_transfer) - .nonce( - era_provider - .get_transaction_count(self.l2_address(), None) - .await?, - ) - .gas_price(era_provider.get_gas_price().await?); - - let fee = era_provider.estimate_fee(transfer_request.clone()).await?; - transfer_request = transfer_request - .max_priority_fee_per_gas(fee.max_priority_fee_per_gas) - .max_fee_per_gas(fee.max_fee_per_gas) - .gas_limit(fee.gas_limit); + let era_provider = self.get_era_provider()?; - let signable_data: Eip712Transaction = transfer_request.clone().try_into()?; - let signature: Signature = self.l2_wallet.sign_typed_data(&signable_data).await?; - transfer_request = - transfer_request.custom_data(Eip712Meta::new().custom_signature(signature.to_vec())); - - let encoded_rlp = &*transfer_request.rlp_signed(signature)?; - let pending_transaction = era_provider - .send_raw_transaction([&[EIP712_TX_TYPE], encoded_rlp].concat().into()) - .await?; + let transaction_receipt = era_provider + .send_transaction_eip712(&self.l2_wallet, request.clone()) + .await? + .await? + .ok_or(ZKSWalletError::CustomError( + "No transaction receipt".to_owned(), + ))?; - Ok(pending_transaction) + Ok(transaction_receipt.transaction_hash) } - pub async fn deposit( - &self, - request: &DepositRequest, - ) -> Result> + pub async fn deposit(&self, request: &DepositRequest) -> Result> where M: ZKSProvider, { @@ -273,7 +245,7 @@ where ) .await?; - Ok(receipt) + Ok(receipt.transaction_hash) } async fn get_base_cost( @@ -299,16 +271,14 @@ where &self, contract_bytecode: &[u8], contract_dependencies: Option>>, + // TODO: accept constructor parameters. _constructor_parameters: Option, ) -> Result> where M: ZKSProvider, T: Tokenizable, { - let era_provider = match &self.era_provider { - Some(era_provider) => era_provider, - None => return Err(ZKSWalletError::CustomError("no era provider".to_owned())), - }; + let era_provider = self.get_era_provider()?; let custom_data = Eip712Meta::new().factory_deps({ let mut factory_deps = Vec::new(); @@ -392,147 +362,51 @@ where Ok(contract_address) } - pub async fn deploy( - &self, - contract_abi: Abi, - contract_bytecode: Vec, - constructor_parameters: Vec, - factory_dependencies: Option>>, - ) -> Result> + pub async fn deploy(&self, request: &DeployRequest) -> Result> where M: ZKSProvider, { - let era_provider = match &self.era_provider { - Some(era_provider) => era_provider, - None => return Err(ZKSWalletError::CustomError("no era provider".to_owned())), - }; - - let custom_data = Eip712Meta::new().factory_deps({ - let mut factory_deps = Vec::new(); - if let Some(factory_dependencies) = factory_dependencies { - factory_deps.extend(factory_dependencies); - } - factory_deps.push(contract_bytecode.clone()); - factory_deps - }); - - let mut contract_deployer_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - contract_deployer_path.push("src/abi/ContractDeployer.json"); - let mut deploy_request = Eip712TransactionRequest::new() - .r#type(EIP712_TX_TYPE) - .from(self.l2_address()) - .to(Address::from_str(CONTRACT_DEPLOYER_ADDR).map_err(|e| { - ZKSWalletError::CustomError(format!("invalid contract deployer address: {e}")) - })?) - .chain_id(self.l2_chain_id()) - .nonce( - era_provider - .get_transaction_count(self.l2_address(), None) - .await?, - ) - .gas_price(era_provider.get_gas_price().await?) - .max_fee_per_gas(era_provider.get_gas_price().await?) - .data({ - let contract_deployer = Abi::load(BufReader::new( - File::open(contract_deployer_path).map_err(|e| { - ZKSWalletError::CustomError(format!( - "failed to open ContractDeployer abi: {e}" - )) - })?, - )) - .map_err(|e| { - ZKSWalletError::CustomError(format!("failed to load ContractDeployer abi: {e}")) - })?; - let create = contract_deployer.function("create").map_err(|e| { - ZKSWalletError::CustomError(format!("failed to get create function: {e}")) - })?; - // TODO: User could provide this instead of defaulting. - let salt = [0_u8; 32]; - let bytecode_hash = hash_bytecode(&contract_bytecode)?; - let call_data: Bytes = match ( - contract_abi.constructor(), - constructor_parameters.is_empty(), - ) { - (None, false) => return Err(ContractError::::ConstructorError.into()), - (None, true) | (Some(_), true) => Bytes::default(), - (Some(constructor), false) => { - zks_utils::encode_constructor_args(constructor, &constructor_parameters)? - .into() - } - }; + let era_provider = self.get_era_provider()?; - encode_function_data(create, (salt, bytecode_hash, call_data))? - }) - .custom_data(custom_data.clone()); + let eip712_request: Eip712TransactionRequest = request.clone().try_into()?; - let fee = era_provider.estimate_fee(deploy_request.clone()).await?; - deploy_request = deploy_request - .max_priority_fee_per_gas(fee.max_priority_fee_per_gas) - .max_fee_per_gas(fee.max_fee_per_gas) - .gas_limit(fee.gas_limit); - - let signable_data: Eip712Transaction = deploy_request.clone().try_into()?; - let signature: Signature = self.l2_wallet.sign_typed_data(&signable_data).await?; - let encoded_rlp = &*deploy_request.rlp_signed(signature)?; - let pending_transaction = era_provider - .send_raw_transaction([&[EIP712_TX_TYPE], encoded_rlp].concat().into()) - .await?; - - pending_transaction + let transaction_receipt = era_provider + .send_transaction_eip712(&self.l2_wallet, eip712_request) + .await? .await? .ok_or(ZKSWalletError::CustomError( - "no transaction receipt".to_owned(), + "No transaction receipt".to_owned(), + ))?; + + transaction_receipt + .contract_address + .ok_or(ZKSWalletError::CustomError( + "No contract address".to_owned(), )) } - pub async fn withdraw( - &self, - amount: U256, - to: Address, - ) -> Result::ZKProvider>, ZKSWalletError> + pub async fn withdraw(&self, request: &WithdrawRequest) -> Result> where M: ZKSProvider, { - let era_provider = match &self.era_provider { - Some(era_provider) => era_provider, - None => return Err(ZKSWalletError::CustomError("no era provider".to_owned())), - }; - - let contract_address = - Address::from_str(zks_utils::CONTRACTS_L2_ETH_TOKEN_ADDR).map_err(|error| { - ZKSWalletError::CustomError(format!("failed to parse contract address: {error}")) - })?; - let function_signature = "function withdraw(address _l1Receiver) external payable override"; - let response = era_provider - .send_eip712( - &self.l2_wallet, - contract_address, - function_signature, - Some([format!("{to:?}")].into()), - Some(Overrides { - value: Some(amount), - }), - ) - .await; + let era_provider = self.get_era_provider()?; + let transaction_receipt = era_provider + .send_transaction_eip712(&self.l2_wallet, request.clone()) + .await? + .await? + .ok_or(ZKSWalletError::CustomError( + "No transaction receipt".to_owned(), + ))?; - response.map_err(|e| ZKSWalletError::CustomError(format!("Error calling withdraw: {e}"))) + Ok(transaction_receipt.transaction_hash) } - pub async fn finalize_withdraw( - &self, - tx_hash: H256, - ) -> Result::Provider>, ZKSWalletError> + pub async fn finalize_withdraw(&self, tx_hash: H256) -> Result> where M: ZKSProvider, { - let (era_provider, eth_provider) = match (&self.era_provider, &self.eth_provider) { - (Some(era_provider), Some(eth_provider)) => (era_provider, eth_provider), - _ => { - return Err(ZKSWalletError::CustomError( - "Both era and eth providers are necessary".to_owned(), - )) - } - }; + let era_provider = self.get_era_provider()?; + let eth_provider = self.get_eth_provider()?; let withdrawal_receipt = era_provider.get_transaction_receipt(tx_hash).await?.ok_or( ZKSWalletError::CustomError("Error getting transaction receipt of withdraw".to_owned()), @@ -630,7 +504,7 @@ where ]; let function_signature = "function finalizeEthWithdrawal(uint256 _l2BlockNumber,uint256 _l2MessageIndex,uint16 _l2TxNumberInBlock,bytes calldata _message,bytes32[] calldata _merkleProof) external"; - let response = eth_provider + let transaction_receipt = eth_provider .send( &self.l1_wallet, main_contract, @@ -638,9 +512,12 @@ where Some(parameters.into()), None, ) - .await; - response.map_err(|e| { - ZKSWalletError::CustomError(format!("Error calling finalizeWithdrawal: {e}")) - }) + .await? + .await? + .ok_or(ZKSWalletError::CustomError( + "No transaction receipt".to_owned(), + ))?; + + Ok(transaction_receipt.transaction_hash) } }