Skip to content

Commit

Permalink
use existing deployment nonce during storage migration
Browse files Browse the repository at this point in the history
  • Loading branch information
nbaztec committed Feb 3, 2025
1 parent 4c28d64 commit 2a5d490
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 12 deletions.
62 changes: 61 additions & 1 deletion crates/forge/tests/it/zk/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
use crate::{config::*, test_helpers::TEST_DATA_DEFAULT};
use forge::revm::primitives::SpecId;
use foundry_test_utils::Filter;
use foundry_test_utils::{
forgetest_async,
util::{self, OutputExt},

Check failure on line 7 in crates/forge/tests/it/zk/fork.rs

View workflow job for this annotation

GitHub Actions / zk-cargo-test

unused import: `OutputExt`
Filter, Fork, ZkSyncNode,
};

#[tokio::test(flavor = "multi_thread")]
async fn test_zk_setup_fork_failure() {
Expand All @@ -28,3 +32,59 @@ 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| {
util::initialize(prj.root());

// Has deployment nonce (1) and transaction nonce (2) on mainnet block #55159219
let test_address = "0x076d6da60aAAC6c97A8a0fE8057f9564203Ee545";

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 = 2;
uint128 constant TEST_ADDRESS_DEPLOYMENT_NONCE = 1;
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();

let node = ZkSyncNode::start_with_fork(Fork::new_with_block(
String::from("https://mainnet.era.zksync.io"),
55159219,
))
.await;

cmd.arg("script").args([
"ZkForkNonceTest",
"--zk-startup",
"./script/ForkNonce.s.sol",
"--rpc-url",
node.url().as_str(),
"--sender",
test_address,
]);

cmd.assert_success();
});
30 changes: 24 additions & 6 deletions crates/strategy/zksync/src/cheatcode/runner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,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 +883,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 +912,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 +954,36 @@ impl ZksyncCheatcodeInspectorStrategyRunner {
for address in data.db.persistent_accounts().into_iter().chain([data.env.tx.caller]) {
info!(?address, "importing to zk state");

// Re-use the deployment nonce from storage if present.

Check failure on line 957 in crates/strategy/zksync/src/cheatcode/runner/mod.rs

View workflow job for this annotation

GitHub Actions / codespell

Re-use ==> Reuse
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 = value.original_value.to_u256();
let (_tx_nonce, deployment_nonce) = decompose_full_nonce(full_nonce);
debug!(?address, ?deployment_nonce, "reuse existing deployment nonce");
deployment_nonce
} 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
50 changes: 46 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,14 +115,35 @@ 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 {
Fork { url, ..Default::default() }
}

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

/// In-memory anvil-zksync that is stopped when dropped.
pub struct ZkSyncNode {
port: u16,
_guard: tokio::sync::oneshot::Sender<()>,
}

impl ZkSyncNode {
/// Returns the server url.
/// Start anvil-zksync in memory, binding a random available port
///
/// The server is automatically stopped when the instance is dropped.
#[inline]
pub fn url(&self) -> String {
format!("http://127.0.0.1:{}", self.port)
Expand All @@ -128,14 +153,30 @@ impl ZkSyncNode {
///
/// 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 +188,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 +201,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 2a5d490

Please sign in to comment.