From dbb13e7f1ffb9f74a2d1b87a7188fcb66dfb36e5 Mon Sep 17 00:00:00 2001 From: Juan Rigada <62958725+Jrigada@users.noreply.github.com> Date: Fri, 4 Oct 2024 11:11:53 -0300 Subject: [PATCH] feat: Add paymaster parameters to broadcasting flow (#596) * Return actual gas used from zkvm transactions * Remove debug print * add paymaster to broadcasting * Clippy improvement and add fork to cargo deny * Update gas_params function to take into account paymaster, improve testing * explicit check of paymaster * extract variable is_paymaster_used * Update crates/zksync/core/src/vm/runner.rs Co-authored-by: Nisheeth Barthwal * change variable name --------- Co-authored-by: elfedy Co-authored-by: Nisheeth Barthwal --- Cargo.lock | 14 ++++---- Cargo.toml | 2 +- crates/cheatcodes/Cargo.toml | 1 + crates/cheatcodes/src/inspector.rs | 32 ++++++++++++++++--- .../forge/tests/fixtures/zk/Paymaster.t.sol | 12 +++++-- crates/script/src/broadcast.rs | 6 +++- crates/script/src/simulate.rs | 11 +++++-- crates/script/src/transaction.rs | 2 ++ crates/zksync/core/src/lib.rs | 8 +++-- crates/zksync/core/src/utils.rs | 2 +- crates/zksync/core/src/vm/runner.rs | 30 +++++++++++------ deny.toml | 1 + 12 files changed, 89 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 130014be4..24e109f31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -931,9 +931,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "arbitrary" @@ -4910,6 +4910,7 @@ dependencies = [ "toml 0.8.15", "tracing", "walkdir", + "zksync-web3-rs", "zksync_types", ] @@ -8084,7 +8085,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.71", @@ -9219,7 +9220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", - "heck 0.5.0", + "heck 0.4.1", "itertools 0.12.1", "log", "multimap", @@ -13907,9 +13908,10 @@ dependencies = [ [[package]] name = "zksync-web3-rs" -version = "0.1.1" -source = "git+https://github.com/lambdaclass/zksync-web3-rs.git?rev=2da644f5b8fc48a129e80fe0653e5334701c059b#2da644f5b8fc48a129e80fe0653e5334701c059b" +version = "0.2.1" +source = "git+https://github.com/jrigada/zksync-web3-rs.git?rev=bc3e6d3e75b7ff3747ff2dccefa9fb74d770931b#bc3e6d3e75b7ff3747ff2dccefa9fb74d770931b" dependencies = [ + "anyhow", "async-trait", "clap", "env_logger 0.10.2", diff --git a/Cargo.toml b/Cargo.toml index 58beb0ba8..7f7f33324 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -217,7 +217,7 @@ alloy-trie = "0.4.1" ## zksync era_test_node = { git="https://github.com/matter-labs/era-test-node.git" , rev = "2041018903aa7e00e74c450f8c0baf799c056d86" } -zksync-web3-rs = {git = "https://github.com/lambdaclass/zksync-web3-rs.git", rev = "2da644f5b8fc48a129e80fe0653e5334701c059b"} +zksync-web3-rs = {git = "https://github.com/jrigada/zksync-web3-rs.git", rev = "bc3e6d3e75b7ff3747ff2dccefa9fb74d770931b"} zksync_basic_types = { git = "https://github.com/matter-labs/zksync-era.git", rev = "0d51cd6f3e65eef1bda981fe96f3026d8e12156d" } zksync_types = { git = "https://github.com/matter-labs/zksync-era.git", rev = "0d51cd6f3e65eef1bda981fe96f3026d8e12156d" } zksync_state = { git = "https://github.com/matter-labs/zksync-era.git", rev = "0d51cd6f3e65eef1bda981fe96f3026d8e12156d" } diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index a177d798e..a602fecea 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -24,6 +24,7 @@ foundry-evm-core.workspace = true foundry-wallets.workspace = true foundry-zksync-core.workspace = true foundry-zksync-compiler.workspace = true +zksync-web3-rs.workspace = true zksync_types.workspace = true diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 2ae18a78f..538084d6c 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -36,7 +36,7 @@ use foundry_evm_core::{ }; use foundry_zksync_compiler::{DualCompiledContract, DualCompiledContracts}; use foundry_zksync_core::{ - convert::{ConvertH160, ConvertH256, ConvertRU256, ConvertU256}, + convert::{ConvertAddress, ConvertH160, ConvertH256, ConvertRU256, ConvertU256}, get_account_code_key, get_balance_key, get_nonce_key, Call, ZkPaymasterData, ZkTransactionMetadata, DEFAULT_CREATE2_DEPLOYER_ZKSYNC, }; @@ -69,6 +69,7 @@ use zksync_types::{ H256, KNOWN_CODES_STORAGE_ADDRESS, L2_BASE_TOKEN_ADDRESS, NONCE_HOLDER_ADDRESS, SYSTEM_CONTEXT_ADDRESS, }; +use zksync_web3_rs::eip712::PaymasterParams; mod utils; @@ -842,6 +843,11 @@ impl Cheatcodes { None }; let rpc = ecx_inner.db.active_fork_url(); + let paymaster_params = + self.paymaster_params.clone().map(|paymaster_data| PaymasterParams { + paymaster: paymaster_data.address.to_h160(), + paymaster_input: paymaster_data.input.to_vec(), + }); if let Some(factory_deps) = zk_tx { let mut batched = foundry_zksync_core::vm::batch_factory_dependencies(factory_deps); @@ -859,7 +865,10 @@ impl Cheatcodes { nonce: Some(nonce), ..Default::default() }, - zk_tx: Some(ZkTransactionMetadata { factory_deps }), + zk_tx: Some(ZkTransactionMetadata { + factory_deps, + paymaster_data: paymaster_params.clone(), + }), }); //update nonce for each tx @@ -881,7 +890,9 @@ impl Cheatcodes { }, ..Default::default() }, - zk_tx: zk_tx.map(ZkTransactionMetadata::new), + zk_tx: zk_tx.map(|factory_deps| { + ZkTransactionMetadata::new(factory_deps, paymaster_params) + }), }); input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); @@ -1428,11 +1439,22 @@ impl Cheatcodes { ecx_inner.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); let zk_tx = if self.use_zk_vm { + let paymaster_params = + self.paymaster_params.clone().map(|paymaster_data| PaymasterParams { + paymaster: paymaster_data.address.to_h160(), + paymaster_input: paymaster_data.input.to_vec(), + }); // We shouldn't need factory_deps for CALLs if call.target_address == DEFAULT_CREATE2_DEPLOYER_ZKSYNC { - Some(ZkTransactionMetadata { factory_deps: factory_deps.clone() }) + Some(ZkTransactionMetadata { + factory_deps: factory_deps.clone(), + paymaster_data: paymaster_params, + }) } else { - Some(ZkTransactionMetadata { factory_deps: Default::default() }) + Some(ZkTransactionMetadata { + factory_deps: Default::default(), + paymaster_data: paymaster_params, + }) } } else { None diff --git a/crates/forge/tests/fixtures/zk/Paymaster.t.sol b/crates/forge/tests/fixtures/zk/Paymaster.t.sol index 8b661e1f4..1807f0d7e 100644 --- a/crates/forge/tests/fixtures/zk/Paymaster.t.sol +++ b/crates/forge/tests/fixtures/zk/Paymaster.t.sol @@ -17,9 +17,6 @@ contract TestPaymasterFlow is Test { bob = makeAddr("Bob"); do_stuff = new DoStuff(); paymaster = new MyPaymaster(); - - // A small amount is needed for initial tx processing - vm.deal(alice, 1 ether); vm.deal(address(paymaster), 10 ether); // Encode paymaster input @@ -29,6 +26,7 @@ contract TestPaymasterFlow is Test { function testCallWithPaymaster() public { vm.deal(address(do_stuff), 1 ether); require(address(do_stuff).balance == 1 ether, "Balance is not 1 ether"); + require(address(alice).balance == 0, "Balance is not 0 ether"); uint256 alice_balance = address(alice).balance; (bool success,) = address(vm).call( @@ -71,6 +69,14 @@ contract TestPaymasterFlow is Test { require(address(alice).balance == alice_balance, "Balance is not the same"); require(address(paymaster).balance < paymaster_balance, "Paymaster balance is not less"); } + + function testFailTransactionFailsWhenNotUsingPaymaster() public { + vm.deal(address(do_stuff), 1 ether); + require(address(alice).balance == 0, "Balance is not 0 ether"); + vm.prank(alice, alice); + + do_stuff.do_stuff(bob); + } } contract DoStuff { diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index f93418053..4675c1446 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -60,7 +60,11 @@ async fn convert_to_zksync( tx: WithOtherFields, zk: &ZkTransaction, ) -> Result<(Eip712TransactionRequest, Eip712Transaction)> { - let custom_data = Eip712Meta::new().factory_deps(zk.factory_deps.clone()); + let mut custom_data = Eip712Meta::new().factory_deps(zk.factory_deps.clone()); + + if let Some(paymaster_params) = &zk.paymaster_data { + custom_data = custom_data.paymaster_params(paymaster_params.clone()); + } let gas_price = match tx.gas_price() { Some(price) => price, diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index f81725428..6bbd8358e 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -144,7 +144,10 @@ impl PreSimulationState { &self.execution_artifacts.decoder, created_contracts, is_fixed_gas_limit, - zk.map(|zk_tx| ZkTransaction { factory_deps: zk_tx.factory_deps }), + zk.map(|zk_tx| ZkTransaction { + factory_deps: zk_tx.factory_deps, + paymaster_data: zk_tx.paymaster_data, + }), )?; eyre::Ok((Some(tx), result.traces)) @@ -228,8 +231,10 @@ impl PreSimulationState { .into_iter() .map(|btx| { let mut tx = TransactionWithMetadata::from_tx_request(btx.transaction); - tx.zk = - btx.zk_tx.map(|metadata| ZkTransaction { factory_deps: metadata.factory_deps }); + tx.zk = btx.zk_tx.map(|metadata| ZkTransaction { + factory_deps: metadata.factory_deps, + paymaster_data: metadata.paymaster_data, + }); tx.rpc = btx.rpc.expect("missing broadcastable tx rpc url"); tx }) diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs index 299cc6a3c..3123b281a 100644 --- a/crates/script/src/transaction.rs +++ b/crates/script/src/transaction.rs @@ -10,6 +10,7 @@ use itertools::Itertools; use revm_inspectors::tracing::types::CallKind; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +use zksync_web3_rs::eip712::PaymasterParams; #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -24,6 +25,7 @@ pub struct AdditionalContract { #[serde(rename_all = "camelCase")] pub struct ZkTransaction { pub factory_deps: Vec>, + pub paymaster_data: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] diff --git a/crates/zksync/core/src/lib.rs b/crates/zksync/core/src/lib.rs index b9fdd7432..3c00e6a57 100644 --- a/crates/zksync/core/src/lib.rs +++ b/crates/zksync/core/src/lib.rs @@ -41,7 +41,7 @@ pub use zksync_types::{ }; pub use zksync_utils::bytecode::hash_bytecode; use zksync_web3_rs::{ - eip712::{Eip712Meta, Eip712Transaction, Eip712TransactionRequest}, + eip712::{Eip712Meta, Eip712Transaction, Eip712TransactionRequest, PaymasterParams}, zks_provider::types::Fee, zks_utils::EIP712_TX_TYPE, }; @@ -88,12 +88,14 @@ pub struct ZkPaymasterData { pub struct ZkTransactionMetadata { /// Factory Deps for ZK transactions. pub factory_deps: Vec>, + /// Paymaster data for ZK transactions. + pub paymaster_data: Option, } impl ZkTransactionMetadata { /// Create a new [`ZkTransactionMetadata`] with the given factory deps - pub fn new(factory_deps: Vec>) -> Self { - Self { factory_deps } + pub fn new(factory_deps: Vec>, paymaster_data: Option) -> Self { + Self { factory_deps, paymaster_data } } } diff --git a/crates/zksync/core/src/utils.rs b/crates/zksync/core/src/utils.rs index 0b50544a9..01e5272ec 100644 --- a/crates/zksync/core/src/utils.rs +++ b/crates/zksync/core/src/utils.rs @@ -123,7 +123,7 @@ pub fn fix_l2_gas_limit( value: U256, balance: U256, ) -> U256 { - let gas_limit = if gas_price.is_zero() { + let gas_limit = if gas_price.is_zero() || balance <= value { proposed_gas_limit } else { let max_gas_limit = balance.saturating_sub(value).div_mod(gas_price).0; diff --git a/crates/zksync/core/src/vm/runner.rs b/crates/zksync/core/src/vm/runner.rs index 4b719731a..1f563c940 100644 --- a/crates/zksync/core/src/vm/runner.rs +++ b/crates/zksync/core/src/vm/runner.rs @@ -46,7 +46,7 @@ where TransactTo::Create => (CONTRACT_DEPLOYER_ADDRESS, true), }; - let (gas_limit, max_fee_per_gas) = gas_params(&mut ecx, caller); + let (gas_limit, max_fee_per_gas) = gas_params(&mut ecx, caller, &PaymasterParams::default()); debug!(?gas_limit, ?max_fee_per_gas, "tx gas parameters"); let tx = L2Tx::new( transact_to, @@ -134,9 +134,6 @@ where let calldata = encode_create_params(&call.scheme, contract.zk_bytecode_hash, constructor_input); let nonce = ZKVMData::new(ecx).get_tx_nonce(caller); - let (gas_limit, max_fee_per_gas) = gas_params(ecx, caller); - info!(?gas_limit, ?max_fee_per_gas, "tx gas parameters"); - let paymaster_params = if let Some(paymaster_data) = &ccx.paymaster_data { PaymasterParams { paymaster: paymaster_data.address.to_h160(), @@ -146,6 +143,9 @@ where PaymasterParams::default() }; + let (gas_limit, max_fee_per_gas) = gas_params(ecx, caller, &paymaster_params); + info!(?gas_limit, ?max_fee_per_gas, "tx gas parameters"); + let tx = L2Tx::new( CONTRACT_DEPLOYER_ADDRESS, calldata, @@ -193,9 +193,6 @@ where let caller = ecx.env.tx.caller; let nonce: zksync_types::Nonce = ZKVMData::new(ecx).get_tx_nonce(caller); - let (gas_limit, max_fee_per_gas) = gas_params(ecx, caller); - info!(?gas_limit, ?max_fee_per_gas, "tx gas parameters"); - let paymaster_params = if let Some(paymaster_data) = &ccx.paymaster_data { PaymasterParams { paymaster: paymaster_data.address.to_h160(), @@ -205,6 +202,9 @@ where PaymasterParams::default() }; + let (gas_limit, max_fee_per_gas) = gas_params(ecx, caller, &paymaster_params); + info!(?gas_limit, ?max_fee_per_gas, "tx gas parameters"); + let tx = L2Tx::new( call.bytecode_address.to_h160(), call.input.to_vec(), @@ -248,7 +248,11 @@ where } /// Assign gas parameters that satisfy zkSync's fee model. -fn gas_params(ecx: &mut EvmContext, caller: Address) -> (U256, U256) +fn gas_params( + ecx: &mut EvmContext, + caller: Address, + paymaster_params: &PaymasterParams, +) -> (U256, U256) where DB: Database, ::Error: Debug, @@ -259,7 +263,15 @@ where error!("balance is 0 for {caller:?}, transaction will fail"); } let max_fee_per_gas = fix_l2_gas_price(ecx.env.tx.gas_price.to_u256()); - let gas_limit = fix_l2_gas_limit(ecx.env.tx.gas_limit.into(), max_fee_per_gas, value, balance); + + let use_paymaster = !paymaster_params.paymaster.is_zero(); + + // We check if the paymaster is set, if it is not set, we use the proposed gas limit + let gas_limit = if use_paymaster { + ecx.env.tx.gas_limit.into() + } else { + fix_l2_gas_limit(ecx.env.tx.gas_limit.into(), max_fee_per_gas, value, balance) + }; (gas_limit, max_fee_per_gas) } diff --git a/deny.toml b/deny.toml index 2ff800c63..63f526f0e 100644 --- a/deny.toml +++ b/deny.toml @@ -112,6 +112,7 @@ allow-git = [ "https://github.com/Moonsong-Labs/foundry-zksync-fork-db", "https://github.com/Moonsong-Labs/block-explorers", "https://github.com/RustCrypto/hashes", + "https://github.com/jrigada/zksync-web3-rs.git", ] [sources.allow-org]