Skip to content

Commit

Permalink
Merge pull request #16 from Moonsong-Labs/aon/feat-add-vm-warp-cheatcode
Browse files Browse the repository at this point in the history
Add vm.warp cheatcode
  • Loading branch information
aon authored Nov 22, 2023
2 parents a0a0e8c + 8e25b75 commit a2e6888
Show file tree
Hide file tree
Showing 12 changed files with 439 additions and 84 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ jobs:
uses: actions/configure-pages@v3

- name: Install mdbook
run: cargo +nightly install mdbook
run: cargo install mdbook

- name: Generate rust docs
run: |
echo "Generating docs..."
cargo +nightly doc --no-deps
cargo doc --no-deps
- name: Make index.html
run: echo '<!DOCTYPE HTML>
<html lang="en-US">
Expand Down
28 changes: 28 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,31 @@ jobs:

- name: Run tests
run: cargo nextest run

smoke-test:
name: smoke-test
runs-on: ubuntu-22.04-github-hosted-16core
env:
TEST_REPO_DIR: test-repo

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
path: ${{ env.TEST_REPO_DIR }}
ref: ${{ github.event.pull_request.head.sha }}

- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly-2023-07-23

- name: Run smoke-test
env:
TEST_REPO: ${{ github.event.repository.name }}
TEST_REPO_DIR: "../${{ env.TEST_REPO_DIR }}"
RUST_BACKTRACE: full
run: |
git clone https://github.com/matter-labs/zkfoundry-smoke-test
cd zkfoundry-smoke-test
./smoke-test.sh
11 changes: 11 additions & 0 deletions e2e-tests/contracts/TestCheatcodes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,15 @@ contract TestCheatcodes {
(bool success, ) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature("setNonce(address,uint64)", account, nonce));
require(success, "setNonce failed");
}

function warp(uint256 timestamp) external {
uint256 initialTimestamp = block.timestamp;
require(timestamp != initialTimestamp, "timestamp must be different than current block timestamp");

(bool success, ) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature("warp(uint256)", timestamp));
require(success, "warp failed");

uint256 finalTimestamp = block.timestamp;
require(finalTimestamp == timestamp, "timestamp was not changed");
}
}
23 changes: 23 additions & 0 deletions e2e-tests/test/cheatcodes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,27 @@ describe("Cheatcodes", function () {
// Assert
expect(receipt.status).to.eq(1);
});

it("Should test vm.warp", async function () {
// Arrange
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);

const timeIncreaseInMS = 123;
let expectedTimestamp: number = await provider.send("config_getCurrentTimestamp", []);
expectedTimestamp += timeIncreaseInMS;

// Act
const cheatcodes = await deployContract(deployer, "TestCheatcodes", []);
const tx = await cheatcodes.warp(expectedTimestamp, {
gasLimit: 1000000,
});
expectedTimestamp += 2; // New transaction will add two blocks
const receipt = await tx.wait();

// Assert
expect(receipt.status).to.eq(1);
const newBlockTimestamp = (await provider.getBlock("latest")).timestamp;
expect(newBlockTimestamp).to.equal(expectedTimestamp);
});
});
1 change: 0 additions & 1 deletion rust-toolchain

This file was deleted.

3 changes: 3 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[toolchain]
channel = "nightly-2023-07-23"
components = ["rustfmt", "clippy"]
24 changes: 10 additions & 14 deletions src/bootloader_debug.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::sync::Arc;

use multivm::vm_virtual_blocks::{
constants::BOOTLOADER_HEAP_PAGE, BootloaderState, DynTracer, ExecutionEndTracer,
ExecutionProcessing, HistoryMode, SimpleMemory, VmExecutionStopReason, VmTracer, ZkSyncVmState,
use multivm::vm_latest::{
constants::BOOTLOADER_HEAP_PAGE, BootloaderState, DynTracer, HistoryMode, SimpleMemory,
VmExecutionStopReason, VmTracer, ZkSyncVmState,
};
use once_cell::sync::OnceCell;
use zksync_basic_types::U256;
Expand Down Expand Up @@ -83,7 +83,13 @@ pub struct BootloaderDebugTracer {

impl<S, H: HistoryMode> DynTracer<S, H> for BootloaderDebugTracer {}

impl<S: WriteStorage, H: HistoryMode> ExecutionProcessing<S, H> for BootloaderDebugTracer {
fn load_debug_slot<H: HistoryMode>(memory: &SimpleMemory<H>, slot: usize) -> U256 {
memory
.read_slot(BOOTLOADER_HEAP_PAGE as usize, DEBUG_START_SLOT + slot)
.value
}

impl<S: WriteStorage, H: HistoryMode> VmTracer<S, H> for BootloaderDebugTracer {
fn after_vm_execution(
&mut self,
state: &mut ZkSyncVmState<S, H>,
Expand All @@ -96,16 +102,6 @@ impl<S: WriteStorage, H: HistoryMode> ExecutionProcessing<S, H> for BootloaderDe
}
}

fn load_debug_slot<H: HistoryMode>(memory: &SimpleMemory<H>, slot: usize) -> U256 {
memory
.read_slot(BOOTLOADER_HEAP_PAGE as usize, DEBUG_START_SLOT + slot)
.value
}

impl<H: HistoryMode> ExecutionEndTracer<H> for BootloaderDebugTracer {}

impl<S: WriteStorage, H: HistoryMode> VmTracer<S, H> for BootloaderDebugTracer {}

impl BootloaderDebug {
pub fn load_from_memory<H: HistoryMode>(
memory: &SimpleMemory<H>,
Expand Down
108 changes: 73 additions & 35 deletions src/cheatcodes.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
use crate::{node::InMemoryNodeInner, utils::bytecode_to_factory_dep};
use anyhow::{anyhow, Result};
use crate::{
node::{BlockContext, InMemoryNodeInner},
utils::bytecode_to_factory_dep,
};
use ethers::{abi::AbiDecode, prelude::abigen};
use multivm::{
interface::L1BatchEnv,
vm_1_3_2::zk_evm_1_3_3::{
tracing::{AfterExecutionData, BeforeExecutionData, VmLocalStateData},
tracing::{BeforeExecutionData, VmLocalStateData},
zkevm_opcode_defs::all::Opcode,
zkevm_opcode_defs::{FatPointer, CALL_IMPLICIT_CALLDATA_FAT_PTR_REGISTER},
},
vm_virtual_blocks::{
DynTracer, ExecutionEndTracer, ExecutionProcessing, HistoryMode, SimpleMemory, VmTracer,
},
vm_latest::{DynTracer, HistoryMode, SimpleMemory, VmTracer},
};
use std::{
fmt::Debug,
sync::{Arc, Mutex, RwLock},
};
use std::sync::{Arc, RwLock};
use zksync_basic_types::{H160, H256};
use zksync_basic_types::{AccountTreeId, H160, H256};
use zksync_state::{StoragePtr, WriteStorage};
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},
StorageKey,
};
use zksync_utils::{h256_to_u256, u256_to_h256};

Expand All @@ -27,11 +33,12 @@ const CHEATCODE_ADDRESS: H160 = H160([

#[derive(Clone, Debug, Default)]
pub struct CheatcodeTracer<F> {
factory_deps: F,
node_ctx: F,
}

pub trait FactoryDeps {
fn store_factory_dep(&mut self, hash: H256, bytecode: Vec<u8>) -> Result<()>;
pub trait NodeCtx {
fn set_time(&mut self, time: u64);
fn store_factory_dep(&mut self, hash: H256, bytecode: Vec<u8>);
}

abigen!(
Expand All @@ -40,10 +47,11 @@ abigen!(
function deal(address who, uint256 newBalance)
function etch(address who, bytes calldata code)
function setNonce(address account, uint64 nonce)
function warp(uint256 timestamp)
]"#
);

impl<F: FactoryDeps, S: WriteStorage, H: HistoryMode> DynTracer<S, H> for CheatcodeTracer<F> {
impl<F: NodeCtx, S: WriteStorage, H: HistoryMode> DynTracer<S, H> for CheatcodeTracer<F> {
fn before_execution(
&mut self,
state: VmLocalStateData<'_>,
Expand Down Expand Up @@ -86,16 +94,11 @@ impl<F: FactoryDeps, S: WriteStorage, H: HistoryMode> DynTracer<S, H> for Cheatc
}
}

impl<F: FactoryDeps + Send, S: WriteStorage, H: HistoryMode> VmTracer<S, H> for CheatcodeTracer<F> {}
impl<F: FactoryDeps, H: HistoryMode> ExecutionEndTracer<H> for CheatcodeTracer<F> {}
impl<F: FactoryDeps, S: WriteStorage, H: HistoryMode> ExecutionProcessing<S, H>
for CheatcodeTracer<F>
{
}
impl<F: NodeCtx + Send, S: WriteStorage, H: HistoryMode> VmTracer<S, H> for CheatcodeTracer<F> {}

impl<F: FactoryDeps> CheatcodeTracer<F> {
pub fn new(factory_deps: F) -> Self {
Self { factory_deps }
impl<F: NodeCtx> CheatcodeTracer<F> {
pub fn new(node_ctx: F) -> Self {
Self { node_ctx }
}

fn dispatch_cheatcode<S: WriteStorage, H: HistoryMode>(
Expand All @@ -119,7 +122,7 @@ impl<F: FactoryDeps> CheatcodeTracer<F> {
let code_key = get_code_key(&who);
let (hash, code) = bytecode_to_factory_dep(code.0.into());
let hash = u256_to_h256(hash);
if let Err(err) = self.factory_deps.store_factory_dep(
self.node_ctx.store_factory_dep(
hash,
code.iter()
.flat_map(|entry| {
Expand All @@ -128,13 +131,7 @@ impl<F: FactoryDeps> CheatcodeTracer<F> {
bytes.to_vec()
})
.collect(),
) {
tracing::error!(
"Etch cheatcode failed, failed to store factory dep: {:?}",
err
);
return;
}
);
storage.borrow_mut().set_value(code_key, hash);
}
SetNonce(SetNonceCall { account, nonce }) => {
Expand Down Expand Up @@ -170,17 +167,58 @@ impl<F: FactoryDeps> CheatcodeTracer<F> {
);
storage.set_value(nonce_key, u256_to_h256(enforced_full_nonce));
}
Warp(WarpCall { timestamp }) => {
tracing::info!("Setting block timestamp {}", timestamp);
self.node_ctx.set_time(timestamp.as_u64());

let key = StorageKey::new(
AccountTreeId::new(zksync_types::SYSTEM_CONTEXT_ADDRESS),
zksync_types::CURRENT_VIRTUAL_BLOCK_INFO_POSITION,
);
let mut storage = storage.borrow_mut();
let (block_number, _) = unpack_block_info(h256_to_u256(storage.read_value(&key)));
storage.set_value(
key,
u256_to_h256(pack_block_info(block_number, timestamp.as_u64())),
);
}
};
}
}

impl<T> FactoryDeps for Arc<RwLock<InMemoryNodeInner<T>>> {
fn store_factory_dep(&mut self, hash: H256, bytecode: Vec<u8>) -> Result<()> {
self.try_write()
.map_err(|e| anyhow!(format!("Failed to grab write lock: {e}")))?
pub struct CheatcodeNodeContext<T> {
pub in_memory_node_inner: Arc<RwLock<InMemoryNodeInner<T>>>,
pub batch_env: Arc<Mutex<L1BatchEnv>>,
pub block_ctx: Arc<Mutex<BlockContext>>,
}

impl<T> CheatcodeNodeContext<T> {
pub fn new(
in_memory_node_inner: Arc<RwLock<InMemoryNodeInner<T>>>,
batch_env: Arc<Mutex<L1BatchEnv>>,
block_ctx: Arc<Mutex<BlockContext>>,
) -> Self {
Self {
in_memory_node_inner,
batch_env,
block_ctx,
}
}
}

impl<T> NodeCtx for CheatcodeNodeContext<T> {
fn set_time(&mut self, time: u64) {
self.in_memory_node_inner.write().unwrap().current_timestamp = time;
self.batch_env.lock().unwrap().timestamp = time + 1;
self.block_ctx.lock().unwrap().timestamp = time + 1;
}

fn store_factory_dep(&mut self, hash: H256, bytecode: Vec<u8>) {
self.in_memory_node_inner
.write()
.unwrap()
.fork_storage
.store_factory_dep(hash, bytecode);
Ok(())
.store_factory_dep(hash, bytecode)
}
}

Expand Down
24 changes: 13 additions & 11 deletions src/node/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> EthNamespa

Ok(Some(block))
}
None => Err(into_jsrpc_error(Web3Error::NoBlock)),
None => Ok(None),
}
})
}
Expand Down Expand Up @@ -451,7 +451,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> EthNamespa

Ok(Some(block))
}
None => Err(into_jsrpc_error(Web3Error::NoBlock)),
None => Ok(None),
}
})
}
Expand Down Expand Up @@ -814,7 +814,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> EthNamespa
///
/// A `BoxFuture` containing a `jsonrpc_core::Result` that resolves to an array of logs, block hashes, or transaction hashes,
/// depending on the filter type, which occurred since last poll.
/// * Filters created with `eth_newFilter` return [Log] objects.
/// * Filters created with `eth_newFilter` return [zksync_types::api::Log] objects.
/// * Filters created with `eth_newBlockFilter` return block hashes.
/// * Filters created with `eth_newPendingTransactionFilter` return transaction hashes.
fn get_filter_changes(&self, id: U256) -> RpcResult<FilterChanges> {
Expand Down Expand Up @@ -1365,13 +1365,15 @@ mod tests {
}

#[tokio::test]
async fn test_get_block_by_hash_produces_no_block_error_for_non_existing_block() {
async fn test_get_block_by_hash_returns_none_for_non_existing_block() {
let node = InMemoryNode::<HttpForkSource>::default();

let expected_err = into_jsrpc_error(Web3Error::NoBlock);
let result = node.get_block_by_hash(H256::repeat_byte(0x01), false).await;
let result = node
.get_block_by_hash(H256::repeat_byte(0x01), false)
.await
.expect("failed fetching block by hash");

assert_eq!(expected_err, result.unwrap_err());
assert!(result.is_none());
}

#[tokio::test]
Expand Down Expand Up @@ -1514,15 +1516,15 @@ mod tests {
}

#[tokio::test]
async fn test_get_block_by_number_produces_no_block_error_for_non_existing_block() {
async fn test_get_block_by_number_returns_none_for_non_existing_block() {
let node = InMemoryNode::<HttpForkSource>::default();

let expected_err = into_jsrpc_error(Web3Error::NoBlock);
let result = node
.get_block_by_number(BlockNumber::Number(U64::from(42)), false)
.await;
.await
.expect("failed fetching block by number");

assert_eq!(expected_err, result.unwrap_err());
assert!(result.is_none());
}

#[tokio::test]
Expand Down
Loading

0 comments on commit a2e6888

Please sign in to comment.