Skip to content

Commit a2e6888

Browse files
authored
Merge pull request #16 from Moonsong-Labs/aon/feat-add-vm-warp-cheatcode
Add vm.warp cheatcode
2 parents a0a0e8c + 8e25b75 commit a2e6888

12 files changed

+439
-84
lines changed

.github/workflows/docs.yaml

+4-2
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@ jobs:
3838
uses: actions/configure-pages@v3
3939

4040
- name: Install mdbook
41-
run: cargo +nightly install mdbook
41+
run: cargo install mdbook
42+
4243
- name: Generate rust docs
4344
run: |
4445
echo "Generating docs..."
45-
cargo +nightly doc --no-deps
46+
cargo doc --no-deps
47+
4648
- name: Make index.html
4749
run: echo '<!DOCTYPE HTML>
4850
<html lang="en-US">

.github/workflows/tests.yaml

+28
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,31 @@ jobs:
3131

3232
- name: Run tests
3333
run: cargo nextest run
34+
35+
smoke-test:
36+
name: smoke-test
37+
runs-on: ubuntu-22.04-github-hosted-16core
38+
env:
39+
TEST_REPO_DIR: test-repo
40+
41+
steps:
42+
- name: Checkout code
43+
uses: actions/checkout@v4
44+
with:
45+
path: ${{ env.TEST_REPO_DIR }}
46+
ref: ${{ github.event.pull_request.head.sha }}
47+
48+
- name: Install Rust
49+
uses: actions-rust-lang/setup-rust-toolchain@v1
50+
with:
51+
toolchain: nightly-2023-07-23
52+
53+
- name: Run smoke-test
54+
env:
55+
TEST_REPO: ${{ github.event.repository.name }}
56+
TEST_REPO_DIR: "../${{ env.TEST_REPO_DIR }}"
57+
RUST_BACKTRACE: full
58+
run: |
59+
git clone https://github.com/matter-labs/zkfoundry-smoke-test
60+
cd zkfoundry-smoke-test
61+
./smoke-test.sh

e2e-tests/contracts/TestCheatcodes.sol

+11
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,15 @@ contract TestCheatcodes {
2525
(bool success, ) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature("setNonce(address,uint64)", account, nonce));
2626
require(success, "setNonce failed");
2727
}
28+
29+
function warp(uint256 timestamp) external {
30+
uint256 initialTimestamp = block.timestamp;
31+
require(timestamp != initialTimestamp, "timestamp must be different than current block timestamp");
32+
33+
(bool success, ) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature("warp(uint256)", timestamp));
34+
require(success, "warp failed");
35+
36+
uint256 finalTimestamp = block.timestamp;
37+
require(finalTimestamp == timestamp, "timestamp was not changed");
38+
}
2839
}

e2e-tests/test/cheatcodes.test.ts

+23
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,27 @@ describe("Cheatcodes", function () {
8080
// Assert
8181
expect(receipt.status).to.eq(1);
8282
});
83+
84+
it("Should test vm.warp", async function () {
85+
// Arrange
86+
const wallet = new Wallet(RichAccounts[0].PrivateKey);
87+
const deployer = new Deployer(hre, wallet);
88+
89+
const timeIncreaseInMS = 123;
90+
let expectedTimestamp: number = await provider.send("config_getCurrentTimestamp", []);
91+
expectedTimestamp += timeIncreaseInMS;
92+
93+
// Act
94+
const cheatcodes = await deployContract(deployer, "TestCheatcodes", []);
95+
const tx = await cheatcodes.warp(expectedTimestamp, {
96+
gasLimit: 1000000,
97+
});
98+
expectedTimestamp += 2; // New transaction will add two blocks
99+
const receipt = await tx.wait();
100+
101+
// Assert
102+
expect(receipt.status).to.eq(1);
103+
const newBlockTimestamp = (await provider.getBlock("latest")).timestamp;
104+
expect(newBlockTimestamp).to.equal(expectedTimestamp);
105+
});
83106
});

rust-toolchain

-1
This file was deleted.

rust-toolchain.toml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[toolchain]
2+
channel = "nightly-2023-07-23"
3+
components = ["rustfmt", "clippy"]

src/bootloader_debug.rs

+10-14
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use std::sync::Arc;
22

3-
use multivm::vm_virtual_blocks::{
4-
constants::BOOTLOADER_HEAP_PAGE, BootloaderState, DynTracer, ExecutionEndTracer,
5-
ExecutionProcessing, HistoryMode, SimpleMemory, VmExecutionStopReason, VmTracer, ZkSyncVmState,
3+
use multivm::vm_latest::{
4+
constants::BOOTLOADER_HEAP_PAGE, BootloaderState, DynTracer, HistoryMode, SimpleMemory,
5+
VmExecutionStopReason, VmTracer, ZkSyncVmState,
66
};
77
use once_cell::sync::OnceCell;
88
use zksync_basic_types::U256;
@@ -83,7 +83,13 @@ pub struct BootloaderDebugTracer {
8383

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

86-
impl<S: WriteStorage, H: HistoryMode> ExecutionProcessing<S, H> for BootloaderDebugTracer {
86+
fn load_debug_slot<H: HistoryMode>(memory: &SimpleMemory<H>, slot: usize) -> U256 {
87+
memory
88+
.read_slot(BOOTLOADER_HEAP_PAGE as usize, DEBUG_START_SLOT + slot)
89+
.value
90+
}
91+
92+
impl<S: WriteStorage, H: HistoryMode> VmTracer<S, H> for BootloaderDebugTracer {
8793
fn after_vm_execution(
8894
&mut self,
8995
state: &mut ZkSyncVmState<S, H>,
@@ -96,16 +102,6 @@ impl<S: WriteStorage, H: HistoryMode> ExecutionProcessing<S, H> for BootloaderDe
96102
}
97103
}
98104

99-
fn load_debug_slot<H: HistoryMode>(memory: &SimpleMemory<H>, slot: usize) -> U256 {
100-
memory
101-
.read_slot(BOOTLOADER_HEAP_PAGE as usize, DEBUG_START_SLOT + slot)
102-
.value
103-
}
104-
105-
impl<H: HistoryMode> ExecutionEndTracer<H> for BootloaderDebugTracer {}
106-
107-
impl<S: WriteStorage, H: HistoryMode> VmTracer<S, H> for BootloaderDebugTracer {}
108-
109105
impl BootloaderDebug {
110106
pub fn load_from_memory<H: HistoryMode>(
111107
memory: &SimpleMemory<H>,

src/cheatcodes.rs

+73-35
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
1-
use crate::{node::InMemoryNodeInner, utils::bytecode_to_factory_dep};
2-
use anyhow::{anyhow, Result};
1+
use crate::{
2+
node::{BlockContext, InMemoryNodeInner},
3+
utils::bytecode_to_factory_dep,
4+
};
35
use ethers::{abi::AbiDecode, prelude::abigen};
46
use multivm::{
7+
interface::L1BatchEnv,
58
vm_1_3_2::zk_evm_1_3_3::{
6-
tracing::{AfterExecutionData, BeforeExecutionData, VmLocalStateData},
9+
tracing::{BeforeExecutionData, VmLocalStateData},
710
zkevm_opcode_defs::all::Opcode,
811
zkevm_opcode_defs::{FatPointer, CALL_IMPLICIT_CALLDATA_FAT_PTR_REGISTER},
912
},
10-
vm_virtual_blocks::{
11-
DynTracer, ExecutionEndTracer, ExecutionProcessing, HistoryMode, SimpleMemory, VmTracer,
12-
},
13+
vm_latest::{DynTracer, HistoryMode, SimpleMemory, VmTracer},
14+
};
15+
use std::{
16+
fmt::Debug,
17+
sync::{Arc, Mutex, RwLock},
1318
};
14-
use std::sync::{Arc, RwLock};
15-
use zksync_basic_types::{H160, H256};
19+
use zksync_basic_types::{AccountTreeId, H160, H256};
1620
use zksync_state::{StoragePtr, WriteStorage};
1721
use zksync_types::{
22+
block::{pack_block_info, unpack_block_info},
1823
get_code_key, get_nonce_key,
1924
utils::{decompose_full_nonce, nonces_to_full_nonce, storage_key_for_eth_balance},
25+
StorageKey,
2026
};
2127
use zksync_utils::{h256_to_u256, u256_to_h256};
2228

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

2834
#[derive(Clone, Debug, Default)]
2935
pub struct CheatcodeTracer<F> {
30-
factory_deps: F,
36+
node_ctx: F,
3137
}
3238

33-
pub trait FactoryDeps {
34-
fn store_factory_dep(&mut self, hash: H256, bytecode: Vec<u8>) -> Result<()>;
39+
pub trait NodeCtx {
40+
fn set_time(&mut self, time: u64);
41+
fn store_factory_dep(&mut self, hash: H256, bytecode: Vec<u8>);
3542
}
3643

3744
abigen!(
@@ -40,10 +47,11 @@ abigen!(
4047
function deal(address who, uint256 newBalance)
4148
function etch(address who, bytes calldata code)
4249
function setNonce(address account, uint64 nonce)
50+
function warp(uint256 timestamp)
4351
]"#
4452
);
4553

46-
impl<F: FactoryDeps, S: WriteStorage, H: HistoryMode> DynTracer<S, H> for CheatcodeTracer<F> {
54+
impl<F: NodeCtx, S: WriteStorage, H: HistoryMode> DynTracer<S, H> for CheatcodeTracer<F> {
4755
fn before_execution(
4856
&mut self,
4957
state: VmLocalStateData<'_>,
@@ -86,16 +94,11 @@ impl<F: FactoryDeps, S: WriteStorage, H: HistoryMode> DynTracer<S, H> for Cheatc
8694
}
8795
}
8896

89-
impl<F: FactoryDeps + Send, S: WriteStorage, H: HistoryMode> VmTracer<S, H> for CheatcodeTracer<F> {}
90-
impl<F: FactoryDeps, H: HistoryMode> ExecutionEndTracer<H> for CheatcodeTracer<F> {}
91-
impl<F: FactoryDeps, S: WriteStorage, H: HistoryMode> ExecutionProcessing<S, H>
92-
for CheatcodeTracer<F>
93-
{
94-
}
97+
impl<F: NodeCtx + Send, S: WriteStorage, H: HistoryMode> VmTracer<S, H> for CheatcodeTracer<F> {}
9598

96-
impl<F: FactoryDeps> CheatcodeTracer<F> {
97-
pub fn new(factory_deps: F) -> Self {
98-
Self { factory_deps }
99+
impl<F: NodeCtx> CheatcodeTracer<F> {
100+
pub fn new(node_ctx: F) -> Self {
101+
Self { node_ctx }
99102
}
100103

101104
fn dispatch_cheatcode<S: WriteStorage, H: HistoryMode>(
@@ -119,7 +122,7 @@ impl<F: FactoryDeps> CheatcodeTracer<F> {
119122
let code_key = get_code_key(&who);
120123
let (hash, code) = bytecode_to_factory_dep(code.0.into());
121124
let hash = u256_to_h256(hash);
122-
if let Err(err) = self.factory_deps.store_factory_dep(
125+
self.node_ctx.store_factory_dep(
123126
hash,
124127
code.iter()
125128
.flat_map(|entry| {
@@ -128,13 +131,7 @@ impl<F: FactoryDeps> CheatcodeTracer<F> {
128131
bytes.to_vec()
129132
})
130133
.collect(),
131-
) {
132-
tracing::error!(
133-
"Etch cheatcode failed, failed to store factory dep: {:?}",
134-
err
135-
);
136-
return;
137-
}
134+
);
138135
storage.borrow_mut().set_value(code_key, hash);
139136
}
140137
SetNonce(SetNonceCall { account, nonce }) => {
@@ -170,17 +167,58 @@ impl<F: FactoryDeps> CheatcodeTracer<F> {
170167
);
171168
storage.set_value(nonce_key, u256_to_h256(enforced_full_nonce));
172169
}
170+
Warp(WarpCall { timestamp }) => {
171+
tracing::info!("Setting block timestamp {}", timestamp);
172+
self.node_ctx.set_time(timestamp.as_u64());
173+
174+
let key = StorageKey::new(
175+
AccountTreeId::new(zksync_types::SYSTEM_CONTEXT_ADDRESS),
176+
zksync_types::CURRENT_VIRTUAL_BLOCK_INFO_POSITION,
177+
);
178+
let mut storage = storage.borrow_mut();
179+
let (block_number, _) = unpack_block_info(h256_to_u256(storage.read_value(&key)));
180+
storage.set_value(
181+
key,
182+
u256_to_h256(pack_block_info(block_number, timestamp.as_u64())),
183+
);
184+
}
173185
};
174186
}
175187
}
176188

177-
impl<T> FactoryDeps for Arc<RwLock<InMemoryNodeInner<T>>> {
178-
fn store_factory_dep(&mut self, hash: H256, bytecode: Vec<u8>) -> Result<()> {
179-
self.try_write()
180-
.map_err(|e| anyhow!(format!("Failed to grab write lock: {e}")))?
189+
pub struct CheatcodeNodeContext<T> {
190+
pub in_memory_node_inner: Arc<RwLock<InMemoryNodeInner<T>>>,
191+
pub batch_env: Arc<Mutex<L1BatchEnv>>,
192+
pub block_ctx: Arc<Mutex<BlockContext>>,
193+
}
194+
195+
impl<T> CheatcodeNodeContext<T> {
196+
pub fn new(
197+
in_memory_node_inner: Arc<RwLock<InMemoryNodeInner<T>>>,
198+
batch_env: Arc<Mutex<L1BatchEnv>>,
199+
block_ctx: Arc<Mutex<BlockContext>>,
200+
) -> Self {
201+
Self {
202+
in_memory_node_inner,
203+
batch_env,
204+
block_ctx,
205+
}
206+
}
207+
}
208+
209+
impl<T> NodeCtx for CheatcodeNodeContext<T> {
210+
fn set_time(&mut self, time: u64) {
211+
self.in_memory_node_inner.write().unwrap().current_timestamp = time;
212+
self.batch_env.lock().unwrap().timestamp = time + 1;
213+
self.block_ctx.lock().unwrap().timestamp = time + 1;
214+
}
215+
216+
fn store_factory_dep(&mut self, hash: H256, bytecode: Vec<u8>) {
217+
self.in_memory_node_inner
218+
.write()
219+
.unwrap()
181220
.fork_storage
182-
.store_factory_dep(hash, bytecode);
183-
Ok(())
221+
.store_factory_dep(hash, bytecode)
184222
}
185223
}
186224

src/node/eth.rs

+13-11
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> EthNamespa
223223

224224
Ok(Some(block))
225225
}
226-
None => Err(into_jsrpc_error(Web3Error::NoBlock)),
226+
None => Ok(None),
227227
}
228228
})
229229
}
@@ -451,7 +451,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> EthNamespa
451451

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

13671367
#[tokio::test]
1368-
async fn test_get_block_by_hash_produces_no_block_error_for_non_existing_block() {
1368+
async fn test_get_block_by_hash_returns_none_for_non_existing_block() {
13691369
let node = InMemoryNode::<HttpForkSource>::default();
13701370

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

1374-
assert_eq!(expected_err, result.unwrap_err());
1376+
assert!(result.is_none());
13751377
}
13761378

13771379
#[tokio::test]
@@ -1514,15 +1516,15 @@ mod tests {
15141516
}
15151517

15161518
#[tokio::test]
1517-
async fn test_get_block_by_number_produces_no_block_error_for_non_existing_block() {
1519+
async fn test_get_block_by_number_returns_none_for_non_existing_block() {
15181520
let node = InMemoryNode::<HttpForkSource>::default();
15191521

1520-
let expected_err = into_jsrpc_error(Web3Error::NoBlock);
15211522
let result = node
15221523
.get_block_by_number(BlockNumber::Number(U64::from(42)), false)
1523-
.await;
1524+
.await
1525+
.expect("failed fetching block by number");
15241526

1525-
assert_eq!(expected_err, result.unwrap_err());
1527+
assert!(result.is_none());
15261528
}
15271529

15281530
#[tokio::test]

0 commit comments

Comments
 (0)