Skip to content

Commit

Permalink
fix: use existing deployment nonce during storage migration (#895)
Browse files Browse the repository at this point in the history
* use existing deployment nonce during storage migration
  • Loading branch information
nbaztec authored Feb 4, 2025
1 parent 5c61abd commit 857d5d2
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 21 deletions.
76 changes: 75 additions & 1 deletion crates/forge/tests/it/zk/fork.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
//! Fork tests.
use crate::{config::*, test_helpers::TEST_DATA_DEFAULT};
use alloy_provider::Provider;
use forge::revm::primitives::SpecId;
use foundry_test_utils::Filter;
use foundry_common::provider::try_get_zksync_http_provider;
use foundry_test_utils::{
forgetest_async,
util::{self},
Filter, ZkSyncNode,
};
use foundry_zksync_core::state::{get_nonce_storage, new_full_nonce};

#[tokio::test(flavor = "multi_thread")]
async fn test_zk_setup_fork_failure() {
Expand All @@ -28,3 +35,70 @@ async fn test_zk_consistent_storage_migration_after_fork() {

TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await;
}

forgetest_async!(test_zk_consistent_nonce_migration_after_fork, |prj, cmd| {
let test_address = alloy_primitives::address!("076d6da60aAAC6c97A8a0fE8057f9564203Ee545");
let transaction_nonce = 2;
let deployment_nonce = 1;

let node = ZkSyncNode::start().await;
// set nonce
let (nonce_key_addr, nonce_key_slot) = get_nonce_storage(test_address);
let full_nonce = new_full_nonce(transaction_nonce, deployment_nonce);
let result = try_get_zksync_http_provider(node.url())
.unwrap()
.raw_request::<_, bool>(
"anvil_setStorageAt".into(),
(nonce_key_addr, nonce_key_slot, full_nonce),
)
.await
.unwrap();
assert!(result, "failed setting nonce on anvil-zksync");

// prepare script
util::initialize(prj.root());
prj.add_script(
"ZkForkNonceTest.s.sol",
format!(r#"
import "forge-std/Script.sol";
import "forge-std/Test.sol";
interface VmExt {{
function zkGetTransactionNonce(
address account
) external view returns (uint64 nonce);
function zkGetDeploymentNonce(
address account
) external view returns (uint64 nonce);
}}
contract ZkForkNonceTest is Script {{
VmExt internal constant vmExt = VmExt(VM_ADDRESS);
address constant TEST_ADDRESS = {test_address};
uint128 constant TEST_ADDRESS_TRANSACTION_NONCE = {transaction_nonce};
uint128 constant TEST_ADDRESS_DEPLOYMENT_NONCE = {deployment_nonce};
function run() external {{
require(TEST_ADDRESS_TRANSACTION_NONCE == vmExt.zkGetTransactionNonce(TEST_ADDRESS), "failed matching transaction nonce");
require(TEST_ADDRESS_DEPLOYMENT_NONCE == vmExt.zkGetDeploymentNonce(TEST_ADDRESS), "failed matching deployment nonce");
}}
}}
"#).as_str(),
)
.unwrap();

cmd.arg("script").args([
"ZkForkNonceTest",
"--zk-startup",
"./script/ForkNonce.s.sol",
"--no-storage-caching", // prevents rpc caching
"--rpc-url",
node.url().as_str(),
// set address as sender to be migrated on startup, so storage is read immediately
"--sender",
test_address.to_string().as_str(),
]);

cmd.assert_success();
});
2 changes: 1 addition & 1 deletion crates/forge/tests/it/zk/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ async fn test_zk_logs_work_in_create() {
Some(vec![
"print".into(),
"outer print".into(),
"0xF9E9ba9Ed9B96AB918c74B21dD0f1D5f2ac38a30".into(),
"0xB5c1DF089600415B21FB76bf89900Adb575947c8".into(),
"print".into(),
"0xff".into(),
"print".into(),
Expand Down
8 changes: 4 additions & 4 deletions crates/forge/tests/it/zk/traces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ use itertools::Itertools;
use serde::Deserialize;

const ADDRESS_ZK_TRACE_TEST: Address = address!("7fa9385be102ac3eac297483dd6233d62b3e1496");
const ADDRESS_ADDER: Address = address!("f9e9ba9ed9b96ab918c74b21dd0f1d5f2ac38a30");
const ADDRESS_NUMBER: Address = address!("f232f12e115391c535fd519b00efadf042fc8be5");
const ADDRESS_FIRST_INNER_NUMBER: Address = address!("ed570f3f91621894e001df0fb70bfbd123d3c8ad");
const ADDRESS_SECOND_INNER_NUMBER: Address = address!("abceaeac3d3a2ac3dcffd7a60ca00a3fac9490ca");
const ADDRESS_ADDER: Address = address!("b5c1df089600415b21fb76bf89900adb575947c8");
const ADDRESS_NUMBER: Address = address!("d6a7a38ee698efae2f48f3a62dc7a71c3c0930a1");
const ADDRESS_FIRST_INNER_NUMBER: Address = address!("89c74b24fb24dda42a8465ee0f9ede2c1308deeb");
const ADDRESS_SECOND_INNER_NUMBER: Address = address!("9359008843d2c083a14e9c17cde01893938047fa");
const ADDRESS_CONSOLE: Address = address!("000000000000000000636f6e736f6c652e6c6f67");
const SELECTOR_TEST_CALL: Bytes = Bytes::from_static(hex!("0d3282c4").as_slice());
const SELECTOR_TEST_CREATE: Bytes = Bytes::from_static(hex!("61bdc916").as_slice());
Expand Down
42 changes: 32 additions & 10 deletions crates/strategy/zksync/src/cheatcode/runner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ use foundry_evm::{
use foundry_evm_core::backend::DatabaseExt;
use foundry_zksync_core::{
convert::{ConvertAddress, ConvertH160, ConvertH256, ConvertRU256, ConvertU256},
get_account_code_key, get_balance_key, get_nonce_key, PaymasterParams, ZkTransactionMetadata,
ACCOUNT_CODE_STORAGE_ADDRESS, CONTRACT_DEPLOYER_ADDRESS, DEFAULT_CREATE2_DEPLOYER_ZKSYNC,
KNOWN_CODES_STORAGE_ADDRESS, L2_BASE_TOKEN_ADDRESS, NONCE_HOLDER_ADDRESS,
ZKSYNC_TRANSACTION_OTHER_FIELDS_KEY,
get_account_code_key, get_balance_key, get_nonce_key,
state::parse_full_nonce,
PaymasterParams, ZkTransactionMetadata, ACCOUNT_CODE_STORAGE_ADDRESS,
CONTRACT_DEPLOYER_ADDRESS, DEFAULT_CREATE2_DEPLOYER_ZKSYNC, KNOWN_CODES_STORAGE_ADDRESS,
L2_BASE_TOKEN_ADDRESS, NONCE_HOLDER_ADDRESS, ZKSYNC_TRANSACTION_OTHER_FIELDS_KEY,
};
use itertools::Itertools;
use revm::{
Expand All @@ -38,7 +39,7 @@ use revm::{
SignedAuthorization, KECCAK_EMPTY,
},
};
use tracing::{debug, error, info};
use tracing::{debug, error, info, trace, warn};
use zksync_types::{
block::{pack_block_info, unpack_block_info},
utils::{decompose_full_nonce, nonces_to_full_nonce},
Expand Down Expand Up @@ -883,7 +884,10 @@ impl ZksyncCheatcodeInspectorStrategyRunner {

let balance = data.sload(balance_account, balance_key).unwrap_or_default().data;
let full_nonce = data.sload(nonce_account, nonce_key).unwrap_or_default();
let (tx_nonce, _deployment_nonce) = decompose_full_nonce(full_nonce.to_u256());
let (tx_nonce, deployment_nonce) = decompose_full_nonce(full_nonce.to_u256());
if !deployment_nonce.is_zero() {
warn!(?address, ?deployment_nonce, "discarding ZKsync deployment nonce for EVM context, might cause inconsistencies");
}
let nonce = tx_nonce.as_u64();

let account_code_key = get_account_code_key(address);
Expand All @@ -909,7 +913,7 @@ impl ZksyncCheatcodeInspectorStrategyRunner {
let _ = std::mem::replace(&mut account.info.nonce, nonce);

if test_contract.map(|addr| addr == address).unwrap_or_default() {
tracing::trace!(?address, "ignoring code translation for test contract");
trace!(?address, "ignoring code translation for test contract");
} else {
account.info.code_hash = code_hash;
account.info.code.clone_from(&code);
Expand Down Expand Up @@ -951,21 +955,39 @@ impl ZksyncCheatcodeInspectorStrategyRunner {
for address in data.db.persistent_accounts().into_iter().chain([data.env.tx.caller]) {
info!(?address, "importing to zk state");

// Reuse the deployment nonce from storage if present.
let deployment_nonce = {
let nonce_key = get_nonce_key(address);
let nonce_addr = NONCE_HOLDER_ADDRESS.to_address();
let account = journaled_account(data, nonce_addr).expect("failed to load account");
if let Some(value) = account.storage.get(&nonce_key) {
let full_nonce = parse_full_nonce(value.original_value);
debug!(
?address,
deployment_nonce = full_nonce.deploy_nonce,
"reuse existing deployment nonce"
);
full_nonce.deploy_nonce.into()
} else {
zksync_types::U256::zero()
}
};

let account = journaled_account(data, address).expect("failed to load account");
let info = &account.info;

let balance_key = get_balance_key(address);
l2_eth_storage.insert(balance_key, EvmStorageSlot::new(info.balance));

// TODO we need to find a proper way to handle deploy nonces instead of replicating
let full_nonce = nonces_to_full_nonce(info.nonce.into(), info.nonce.into());
debug!(?address, ?deployment_nonce, transaction_nonce=?info.nonce, "attempting to fit EVM nonce to ZKsync nonces, might cause inconsistencies");
let full_nonce = nonces_to_full_nonce(info.nonce.into(), deployment_nonce);

let nonce_key = get_nonce_key(address);
nonce_storage.insert(nonce_key, EvmStorageSlot::new(full_nonce.to_ru256()));

if test_contract.map(|test_address| address == test_address).unwrap_or_default() {
// avoid migrating test contract code
tracing::trace!(?address, "ignoring code translation for test contract");
trace!(?address, "ignoring code translation for test contract");
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion crates/test-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub use script::{ScriptOutcome, ScriptTester};

// TODO: remove once anvil supports zksync node
mod zksync;
pub use zksync::ZkSyncNode;
pub use zksync::{Fork, ZkSyncNode};

// re-exports for convenience
pub use foundry_compilers;
Expand Down
48 changes: 44 additions & 4 deletions crates/test-utils/src/zksync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
use std::{net::SocketAddr, str::FromStr};

use anvil_zksync_api_server::NodeServerBuilder;
use anvil_zksync_config::{types::SystemContractsOptions, TestNodeConfig};
use anvil_zksync_config::{
types::{CacheConfig, SystemContractsOptions},
TestNodeConfig,
};
use anvil_zksync_core::{
fork::ForkDetails,
node::{
BlockProducer, BlockSealer, BlockSealerMode, ImpersonationManager, InMemoryNode,
TimestampManager, TxPool,
Expand Down Expand Up @@ -111,6 +115,25 @@ const RICH_WALLETS: [(&str, &str, &str); 10] = [
),
];

/// Represents fork config for [ZkSyncNode].
#[derive(Debug, Default)]
pub struct Fork {
url: String,
block: Option<u64>,
}

impl Fork {
/// Create a fork config with the provided url and the latest block.
pub fn new(url: String) -> Self {
Self { url, ..Default::default() }
}

/// Create a fork config with the provided url and block.
pub fn new_with_block(url: String, block: u64) -> Self {
Self { url, block: Some(block) }
}
}

/// In-memory anvil-zksync that is stopped when dropped.
pub struct ZkSyncNode {
port: u16,
Expand All @@ -124,18 +147,34 @@ impl ZkSyncNode {
format!("http://127.0.0.1:{}", self.port)
}

/// Start anvil-zksync in memory, binding a random available port
/// Start anvil-zksync in memory, binding a random available port.
///
/// The server is automatically stopped when the instance is dropped.
pub async fn start() -> Self {
Self::start_inner(None).await
}

/// Start anvil-zksync in memory, binding a random available port and with the provided fork url
/// and block.
///
/// The server is automatically stopped when the instance is dropped.
pub async fn start_with_fork(fork: Fork) -> Self {
Self::start_inner(Some(fork)).await
}

async fn start_inner(fork: Option<Fork>) -> Self {
let (_guard, guard_rx) = tokio::sync::oneshot::channel::<()>();
let (port_tx, port) = tokio::sync::oneshot::channel();

let fork = fork.map(|fork| {
ForkDetails::from_url(fork.url, fork.block, CacheConfig::Memory)
.expect("failed building ForkDetails")
});
std::thread::spawn(move || {
// We need to spawn a thread since `run_inner` future is not `Send`.
let runtime =
tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
runtime.block_on(Self::run_inner(port_tx, guard_rx));
runtime.block_on(Self::run_inner(port_tx, guard_rx, fork));
});

// wait for server to start
Expand All @@ -147,6 +186,7 @@ impl ZkSyncNode {
async fn run_inner(
port_tx: tokio::sync::oneshot::Sender<u16>,
stop_guard: tokio::sync::oneshot::Receiver<()>,
fork: Option<ForkDetails>,
) {
const MAX_TRANSACTIONS: usize = 100; // Not that important for testing purposes.

Expand All @@ -159,7 +199,7 @@ impl ZkSyncNode {
let block_sealer = BlockSealer::new(sealing_mode);

let node: InMemoryNode = InMemoryNode::new(
None,
fork,
None,
&config,
time,
Expand Down

0 comments on commit 857d5d2

Please sign in to comment.