Skip to content

Commit

Permalink
fix: allow deployment in scripting (#238)
Browse files Browse the repository at this point in the history
Co-authored-by: MexicanAce <[email protected]>
Co-authored-by: Nisheeth Barthwal <[email protected]>
  • Loading branch information
3 people authored Jan 26, 2024
1 parent d5c65ec commit c9bb3ca
Show file tree
Hide file tree
Showing 19 changed files with 353 additions and 143 deletions.
11 changes: 6 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ pub struct BroadcastableTransaction {
pub rpc: Option<RpcUrl>,
/// The transaction to broadcast.
pub transaction: TypedTransaction,

/// Additional factory deps of the tx
pub factory_deps: Vec<Vec<u8>>,
}

/// List of transactions that can be broadcasted.
Expand Down Expand Up @@ -783,6 +786,7 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {

let tx = BroadcastableTransaction {
rpc: data.db.active_fork_url(),
factory_deps: vec![],
transaction: TypedTransaction::Legacy(TransactionRequest {
from: Some(broadcast.new_origin.to_ethers()),
to: Some(NameOrAddress::Address(call.contract.to_ethers())),
Expand Down Expand Up @@ -1147,6 +1151,7 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {

let tx = BroadcastableTransaction {
rpc: data.db.active_fork_url(),
factory_deps: vec![],
transaction: TypedTransaction::Legacy(TransactionRequest {
from: Some(broadcast.new_origin.to_ethers()),
to: to.map(|a| NameOrAddress::Address(a.to_ethers())),
Expand Down
7 changes: 7 additions & 0 deletions crates/common/src/zk_utils/conversion_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ pub fn address_to_h160(i: Address) -> H160 {
H160::from(i.0 .0)
}

/// Convert address to h256
pub fn address_to_h256(i: &Address) -> H256 {
let mut buffer = [0u8; 32];
buffer[12..].copy_from_slice(i.as_slice());
H256(buffer)
}

/// Convert h160 to address
pub fn h160_to_address(i: H160) -> Address {
i.as_fixed_bytes().into()
Expand Down
21 changes: 21 additions & 0 deletions crates/common/src/zk_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use foundry_config::Chain;
use multivm::vm_latest::TracerPointer;
use std::{collections::HashMap, num::ParseIntError};
use url::Url;
use zksync_basic_types::U256;
use zksync_types::{StorageKey, StorageValue};
use zksync_web3_rs::types::H256;
/// Utils for conversion between zksync types and revm types
Expand Down Expand Up @@ -148,6 +149,26 @@ pub fn decode_hex(s: &str) -> std::result::Result<Vec<u8>, ParseIntError> {
(0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16)).collect()
}

/// Fixes the gas price to be minimum of 0.26GWei which is above the block base fee on L2.
/// This is required so the bootloader does not throw an error if baseFee < gasPrice.
///
/// TODO: Remove this later to allow for dynamic gas prices that work in both tests and scripts.
/// This is extremely unstable right now - when the gas price changes either of the following can
/// happen:
/// * The scripts can fail if the balance is not enough for gas * MAGIC_VALUE
/// * The tests/deploy can fail if MAGIC_VALUE is too low
pub fn fix_l2_gas_price(gas_price: U256) -> U256 {
U256::max(gas_price, U256::from(260_000_000))
}

/// Fixes the gas limit to be maxmium of 2^31, which is below the VM gas limit of 2^32.
/// This is required so the bootloader does not throw an error for not having enough gas.
///
/// TODO: Remove this later to allow for dynamic gas prices that work in both tests and scripts.
pub fn fix_l2_gas_limit(gas_limit: U256) -> U256 {
U256::min(gas_limit, U256::from(u32::MAX >> 1))
}

/// Recorded storage modifications.
#[derive(Default, Debug, Clone)]
pub struct StorageModifications {
Expand Down
84 changes: 74 additions & 10 deletions crates/era-cheatcodes/src/cheatcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ use zksync_types::{
block::{pack_block_info, unpack_block_info},
get_code_key, get_nonce_key,
utils::{decompose_full_nonce, nonces_to_full_nonce, storage_key_for_eth_balance},
LogQuery, StorageKey, Timestamp, ACCOUNT_CODE_STORAGE_ADDRESS, MSG_VALUE_SIMULATOR_ADDRESS,
LogQuery, StorageKey, Timestamp, ACCOUNT_CODE_STORAGE_ADDRESS,
};
use zksync_utils::{bytecode::CompressedBytecodeInfo, h256_to_u256, u256_to_h256};

Expand Down Expand Up @@ -93,6 +93,33 @@ const INTERNAL_CONTRACT_ADDRESSES: [H160; 20] = [
H160::zero(),
];

//same as above, except without
// CONTRACT_DEPLOYER_ADDRESS
// MSG_VALUE_SIMULATOR_ADDRESS
// and with
// CHEATCODE_ADDRESS
const BROADCAST_IGNORED_CONTRACTS: [H160; 19] = [
zksync_types::BOOTLOADER_ADDRESS,
zksync_types::ACCOUNT_CODE_STORAGE_ADDRESS,
zksync_types::NONCE_HOLDER_ADDRESS,
zksync_types::KNOWN_CODES_STORAGE_ADDRESS,
zksync_types::IMMUTABLE_SIMULATOR_STORAGE_ADDRESS,
zksync_types::CONTRACT_FORCE_DEPLOYER_ADDRESS,
zksync_types::L1_MESSENGER_ADDRESS,
zksync_types::KECCAK256_PRECOMPILE_ADDRESS,
zksync_types::L2_ETH_TOKEN_ADDRESS,
zksync_types::SYSTEM_CONTEXT_ADDRESS,
zksync_types::BOOTLOADER_UTILITIES_ADDRESS,
zksync_types::EVENT_WRITER_ADDRESS,
zksync_types::COMPRESSOR_ADDRESS,
zksync_types::COMPLEX_UPGRADER_ADDRESS,
zksync_types::ECRECOVER_PRECOMPILE_ADDRESS,
zksync_types::SHA256_PRECOMPILE_ADDRESS,
zksync_types::MINT_AND_BURN_ADDRESS,
CHEATCODE_ADDRESS,
H160::zero(),
];

#[derive(Debug, Clone)]
struct EraEnv {
l1_batch_env: L1BatchEnv,
Expand Down Expand Up @@ -453,21 +480,54 @@ impl<S: DatabaseExt + Send, H: HistoryMode> DynTracer<EraDb<S>, SimpleMemory<H>>
.expect("callstack before the current");

if state.vm_local_state.callstack.depth() == broadcast.depth &&
prev_cs.this_address == broadcast.original_caller
prev_cs.this_address == broadcast.original_caller &&
!BROADCAST_IGNORED_CONTRACTS.contains(&current.code_address)
{
self.one_time_actions.push(FinishCycleOneTimeActions::SetOrigin {
origin: broadcast.new_origin,
});

let new_origin = broadcast.new_origin;
let handle = &mut storage.borrow_mut();
let (nonce, _) = Self::get_nonce(new_origin, handle);
let revm_db_for_era = &handle.storage_handle;
let rpc = revm_db_for_era.db.lock().unwrap().active_fork_url();

let gas_limit = current.ergs_remaining;
let calldata = get_calldata(&state, memory);

let is_deployment =
current.code_address == zksync_types::CONTRACT_DEPLOYER_ADDRESS;
let factory_deps = if is_deployment {
let test_contract_hash = handle.read_value(&StorageKey::new(
AccountTreeId::new(zksync_types::ACCOUNT_CODE_STORAGE_ADDRESS),
TEST_ADDRESS.into(),
));

let (value, to) = if current.code_address == MSG_VALUE_SIMULATOR_ADDRESS {
self.get_modified_bytecodes(vec![])
.into_iter()
.filter(|(k, _)| k != &test_contract_hash)
.map(|(_, v)| v)
.collect::<Vec<_>>()
} else {
vec![]
};

// used to determine whether the nonce should be decreased, since
// zkevm updates the nonce for the _sender_ already when we run the
// script/test function therefore, we should fix it and obtain the nonce
// that resembles the one to be used on-chain
let is_sender_also_caller =
new_origin == self.config.evm_opts.sender.to_h160();
let nonce_offset = if is_sender_also_caller { 1 } else { 0 };

let nonce = Self::get_nonce(new_origin, handle)
.0
.saturating_sub(nonce_offset.into());

let _gas_limit = current.ergs_remaining;

let (value, to) = if current.code_address ==
zksync_types::MSG_VALUE_SIMULATOR_ADDRESS
{
//when some eth is sent to an address in zkevm the call is replaced
// with a call to MsgValueSimulator, which does a mimic_call later
// to the original destination
Expand All @@ -494,15 +554,16 @@ impl<S: DatabaseExt + Send, H: HistoryMode> DynTracer<EraDb<S>, SimpleMemory<H>>

let tx = BroadcastableTransaction {
rpc,
factory_deps,
transaction:
ethers::types::transaction::eip2718::TypedTransaction::Legacy(
TransactionRequest {
from: Some(new_origin),
to: Some(ethers::types::NameOrAddress::Address(to)),
//FIXME: set only if set manually by user
gas: Some(gas_limit.into()),
//TODO: set only if set manually by user in script
gas: None,
value,
data: Some(get_calldata(&state, memory).into()),
data: Some(calldata.into()),
nonce: Some(nonce),
..Default::default()
},
Expand All @@ -511,12 +572,15 @@ impl<S: DatabaseExt + Send, H: HistoryMode> DynTracer<EraDb<S>, SimpleMemory<H>>
tracing::debug!(?tx, "storing for broadcast");

self.broadcastable_transactions.write().unwrap().push_back(tx);
//FIXME: detect if this is a deployment and increase the other nonce too
self.set_nonce(new_origin, (Some(nonce + 1), None), handle);

// we increase the nonce so that future calls will have the nonce
// increased, simulating the previous tx being executed
self.set_nonce(new_origin, (Some(nonce + 1 + nonce_offset), None), handle);
}
}
return
}

if current.code_page.0 == 0 || current.ergs_remaining == 0 {
tracing::error!("cheatcode triggered, but no calldata or ergs available");
return
Expand Down
9 changes: 5 additions & 4 deletions crates/evm/core/src/era_revm/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ use revm::{
use zksync_basic_types::{web3::signing::keccak256, AccountTreeId, L2ChainId, H160, H256, U256};
use zksync_state::ReadStorage;
use zksync_types::{
block::unpack_block_info, get_code_key, get_system_context_init_logs, StorageKey, StorageLog,
StorageLogKind, ACCOUNT_CODE_STORAGE_ADDRESS, L2_ETH_TOKEN_ADDRESS, NONCE_HOLDER_ADDRESS,
block::unpack_block_info, get_code_key, get_system_context_init_logs,
utils::decompose_full_nonce, StorageKey, StorageLog, StorageLogKind,
ACCOUNT_CODE_STORAGE_ADDRESS, L2_ETH_TOKEN_ADDRESS, NONCE_HOLDER_ADDRESS,
SYSTEM_CONTEXT_ADDRESS, SYSTEM_CONTEXT_BLOCK_INFO_POSITION,
SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION,
};
Expand Down Expand Up @@ -135,8 +136,8 @@ where

let nonce_storage =
self.read_storage_internal(NONCE_HOLDER_ADDRESS, h256_to_u256(storage_idx));
let nonces: [u8; 8] = nonce_storage.as_fixed_bytes()[24..32].try_into().unwrap();
u64::from_be_bytes(nonces)
let (tx_nonce, _deploy_nonce) = decompose_full_nonce(h256_to_u256(nonce_storage));
tx_nonce.as_u64()
}

fn read_storage_internal(&self, address: H160, idx: U256) -> H256 {
Expand Down
8 changes: 3 additions & 5 deletions crates/evm/core/src/era_revm/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use zksync_types::{
use zksync_utils::{h256_to_account_address, u256_to_h256};

use foundry_common::{
fix_l2_gas_limit, fix_l2_gas_price,
zk_utils::{
conversion_utils::{h160_to_address, h256_to_h160, h256_to_revm_u256, revm_u256_to_u256},
factory_deps::PackedEraBytecode,
Expand Down Expand Up @@ -66,11 +67,8 @@ pub fn encode_deploy_params_create(
/// Extract the zkSync Fee based off the Revm transaction.
pub fn tx_env_to_fee(tx_env: &TxEnv) -> Fee {
Fee {
// Currently zkSync doesn't allow gas limits larger than u32.
gas_limit: U256::min(tx_env.gas_limit.into(), U256::from(2147483640)),
// Block base fee on L2 is 0.25 GWei - make sure that the max_fee_per_gas is set to higher
// value.
max_fee_per_gas: U256::max(revm_u256_to_u256(tx_env.gas_price), U256::from(260_000_000)),
gas_limit: fix_l2_gas_limit(tx_env.gas_limit.into()),
max_fee_per_gas: fix_l2_gas_price(revm_u256_to_u256(tx_env.gas_price)),
max_priority_fee_per_gas: revm_u256_to_u256(tx_env.gas_priority_fee.unwrap_or_default()),
gas_per_pubdata_limit: U256::from(800),
}
Expand Down
48 changes: 46 additions & 2 deletions crates/evm/evm/src/executors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ use alloy_json_abi::{Function, JsonAbi as Abi};
use alloy_primitives::{Address, Bytes, FixedBytes, B256, U256};
use ethers_core::types::{Log, H256};
use ethers_signers::LocalWallet;
use foundry_common::{abi::IntoFunction, conversion_utils::address_to_h160, evm::Breakpoints};
use foundry_common::{
abi::IntoFunction,
conversion_utils::{
address_to_h160, h160_to_address, h256_to_revm_u256, revm_u256_to_u256, u256_to_revm_u256,
},
evm::Breakpoints,
fix_l2_gas_limit, fix_l2_gas_price,
};
use foundry_evm_core::{
backend::{Backend, DatabaseError, DatabaseExt, DatabaseResult, FuzzBackendWrapper},
constants::{CALLER, CHEATCODE_ADDRESS},
Expand All @@ -33,7 +40,10 @@ use revm::{
};
use std::collections::BTreeMap;
use zksync_types::{
get_nonce_key,
utils::{decompose_full_nonce, nonces_to_full_nonce},
AccountTreeId, StorageKey, ACCOUNT_CODE_STORAGE_ADDRESS, KNOWN_CODES_STORAGE_ADDRESS,
NONCE_HOLDER_ADDRESS,
};
use zksync_utils::bytecode::hash_bytecode;

Expand Down Expand Up @@ -155,11 +165,22 @@ impl Executor {
Ok(self.backend.basic_ref(address)?.map(|acc| acc.balance).unwrap_or_default())
}

/// Set the nonce of an account.
/// Set the nonce of an account. We additionally update the nonce in the NONCE_HOLDER storage.
pub fn set_nonce(&mut self, address: Address, nonce: u64) -> DatabaseResult<&mut Self> {
let mut account = self.backend.basic_ref(address)?.unwrap_or_default();
account.nonce = nonce;

let nonce_storage_slot = h256_to_revm_u256(*get_nonce_key(&address_to_h160(address)).key());
let nonce_holder_address = h160_to_address(NONCE_HOLDER_ADDRESS);
let full_nonce = self.backend.storage_ref(nonce_holder_address, nonce_storage_slot)?;
let (_, deploy_nonce) = decompose_full_nonce(revm_u256_to_u256(full_nonce));
let new_full_nonce = nonces_to_full_nonce(nonce.into(), deploy_nonce);

self.backend.insert_account_storage(
nonce_holder_address,
nonce_storage_slot,
u256_to_revm_u256(new_full_nonce),
)?;
self.backend.insert_account_info(address, account);
Ok(self)
}
Expand Down Expand Up @@ -610,6 +631,29 @@ impl Executor {
},
}
}

/// Adjust the gas parameters of an executor for ZKSync.
/// zksync vm allows max gas limit to be u32, and additionally the account balance must be able
/// to pay for the gas + value. Hence we cap the gas limit what the caller can actually pay.
pub fn adjust_zksync_gas_parameters(&mut self) {
let tx_env = &self.env.tx;
let caller_balance = self.get_balance(tx_env.caller).unwrap_or_default();
let min_gas_price =
u256_to_revm_u256(fix_l2_gas_price(revm_u256_to_u256(tx_env.gas_price)));
let max_allowed_gas_limit = caller_balance
.saturating_sub(tx_env.value)
.saturating_sub(U256::from(1))
.wrapping_div(min_gas_price);
let adjusted_gas_limit =
u256_to_revm_u256(fix_l2_gas_limit(revm_u256_to_u256(max_allowed_gas_limit)));
let gas_limit = U256::from(tx_env.gas_limit).min(adjusted_gas_limit);

debug!(
"calculated new gas parameters for caller {:?}, gas_limit={} gas_price={}",
tx_env.caller, gas_limit, min_gas_price
);
self.set_gas_limit(U256::from(gas_limit));
}
}

/// Represents the context after an execution error occurred.
Expand Down
1 change: 1 addition & 0 deletions crates/evm/evm/src/inspectors/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ impl<DB: DatabaseExt + Send> AsTracerPointer<StorageView<RevmDatabaseForEra<DB>>
CheatcodeTracer::new(
self.cheatcodes.as_ref().map(|c| c.config.clone()).unwrap_or_default(),
self.storage_modifications.clone(),
//TODO: dedicated InspectorStack field
self.cheatcodes
.as_ref()
.map(|c| c.broadcastable_transactions.clone())
Expand Down
Loading

0 comments on commit c9bb3ca

Please sign in to comment.