Skip to content

Commit

Permalink
refactor: Brush up repo for publishing (pt. 3) (#60)
Browse files Browse the repository at this point in the history
# What ❔

- Documents all public items, applies the corresponding lint.
- Applies more clippy lints.
- Makes snapshot management private. Makes some more types / traits
private as well (e.g., `PtrOp` trait).
- Exposes `Snapshot` and `StorageChange` types.
- Re-exports `zksync_vm2_interface` as the `interface` module instead of
re-exporting its items.
- Removes potential UB during `offset_from` calls.

## Why ❔

Part of preparations for publishing the VM.
  • Loading branch information
slowli authored Sep 18, 2024
1 parent cd6136c commit 6f9d832
Show file tree
Hide file tree
Showing 64 changed files with 1,062 additions and 632 deletions.
2 changes: 2 additions & 0 deletions .clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Configures additional identifiers excempt from `clippy::doc_markdown` lint
doc-valid-idents = ["EraVM", "ZKsync", ".."]
35 changes: 34 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:

env:
CARGO_TERM_COLOR: always
# Nightly Rust necessary for building docs.
RUST_NIGHTLY_VERSION: nightly-2024-08-01

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand All @@ -32,7 +34,7 @@ jobs:
uses: Swatinem/rust-cache@v2

- name: Install cargo-afl
run: cargo install cargo-afl --version=^0.15 --force
run: cargo install cargo-afl --version=^0.15 --locked --force

- name: Build project
run: |
Expand Down Expand Up @@ -65,3 +67,34 @@ jobs:
run: |
AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 \
cargo afl fuzz -i tests/afl-fuzz/in -o tests/afl-fuzz/out -V 60 target/debug/zksync_vm2_afl_fuzz
document:
needs:
- build_and_test
if: github.event_name == 'push' && github.ref_type == 'branch'
permissions:
contents: write
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4

- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_NIGHTLY_VERSION }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2

- name: Build docs
run: |
cargo clean --doc && \
cargo rustdoc -p zksync_vm2_interface -- -Z unstable-options --enable-index-page && \
cargo rustdoc -p zksync_vm2 -- -Z unstable-options --enable-index-page
- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: target/doc
single-commit: true
13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,16 @@ zk_evm = { git = "https://github.com/matter-labs/era-zk_evm.git", rev = "b7caa02
# Dependencies within the workspace
zksync_vm2_interface = { version = "0.1.0", path = "crates/vm2-interface" }
zksync_vm2 = { version = "0.1.0", path = "crates/vm2" }

[workspace.lints.rust]
missing_docs = "warn"
missing_debug_implementations = "warn"
unreachable_pub = "warn"

[workspace.lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
must_use_candidate = "allow"
module_name_repetitions = "allow"
inline_always = "allow"
struct_field_names = "allow"
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# High-Performance ZKsync Era VM (EraVM)

[![Build Status](https://github.com/matter-labs/vm2/actions/workflows/ci.yml/badge.svg)](https://github.com/matter-labs/vm2/actions)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%2FApache--2.0-blue)](https://github.com/matter-labs/vm2#license)

A high-performance rewrite of the out-of-circuit VM for ZKsync Era (aka EraVM).

See [Era docs](https://github.com/matter-labs/zksync-era/tree/main/docs/specs/zk_evm) for the VM overview and formal specification.
Expand Down
3 changes: 3 additions & 0 deletions crates/vm2-interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ categories.workspace = true

[dependencies]
primitive-types.workspace = true

[lints]
workspace = true
6 changes: 6 additions & 0 deletions crates/vm2-interface/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Stable Interface for ZKsync Era VM

[![Build Status](https://github.com/matter-labs/vm2/actions/workflows/ci.yml/badge.svg)](https://github.com/matter-labs/vm2/actions)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%2FApache--2.0-blue)](https://github.com/matter-labs/vm2#license)

**Documentation:**
[![crate docs (main)](https://img.shields.io/badge/main-yellow.svg?label=docs)](https://matter-labs.github.io/vm2/zksync_vm2_interface/)

This library provides a stable interface for EraVM. It defines an interface for tracers that will never change but may be extended.

## License
Expand Down
4 changes: 2 additions & 2 deletions crates/vm2-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
//! With any sane design it would be trivial to take a tracer written for version 1 and
//! update it to work with version 2. However, then it can no longer be used with VM1.
//!
//! This exact thing caused us a lot of trouble when we put many versions of zk_evm in multivm.
//! This exact thing caused us a lot of trouble when we put many versions of `zk_evm` in `multivm`.
//!
//! ## How do I add a new feature to the interface?
//!
//! Do not change the existing traits. In fact, you should delete existing code in the new
//! version that you publish and import it from the previous version instead.
//!
//! This is how you would add a new method to StateInterface and a new opcode.
//! This is how you would add a new method to [`StateInterface`] and a new opcode.
//!
//! ```
//! # use zksync_vm2_interface as zksync_vm2_interface_v1;
Expand Down
116 changes: 95 additions & 21 deletions crates/vm2-interface/src/state_interface.rs
Original file line number Diff line number Diff line change
@@ -1,106 +1,164 @@
use primitive_types::{H160, U256};

/// Public interface of the VM state. Encompasses both read and write methods.
pub trait StateInterface {
/// Reads a register with the specified zero-based index. Returns a value together with a pointer flag.
fn read_register(&self, register: u8) -> (U256, bool);
/// Sets a register with the specified zero-based index
fn set_register(&mut self, register: u8, value: U256, is_pointer: bool);

/// Returns a mutable handle to the current call frame.
fn current_frame(&mut self) -> impl CallframeInterface + '_;
/// Returns the total number of call frames.
fn number_of_callframes(&self) -> usize;
/// Returns a mutable handle to a call frame with the specified index, where
/// zero is the current frame, one is the frame before that etc.
fn callframe(&mut self, n: usize) -> impl CallframeInterface + '_;

fn read_heap_byte(&self, heap: HeapId, index: u32) -> u8;
/// Reads an entire `U256` word in the big-endian order from the specified heap page / `index`
/// Reads a single byte from the specified heap at the specified 0-based offset.
fn read_heap_byte(&self, heap: HeapId, offset: u32) -> u8;
/// Reads an entire `U256` word in the big-endian order from the specified heap / `offset`
/// (which is the index of the most significant byte of the read value).
fn read_heap_u256(&self, heap: HeapId, index: u32) -> U256;
/// Writes an entire `U256` word in the big-endian order to the specified heap page at the specified `index`
fn read_heap_u256(&self, heap: HeapId, offset: u32) -> U256;
/// Writes an entire `U256` word in the big-endian order to the specified heap at the specified `offset`
/// (which is the index of the most significant byte of the written value).
fn write_heap_u256(&mut self, heap: HeapId, index: u32, value: U256);
fn write_heap_u256(&mut self, heap: HeapId, offset: u32, value: U256);

/// Returns current execution flags.
fn flags(&self) -> Flags;
/// Sets current execution flags.
fn set_flags(&mut self, flags: Flags);

/// Returns the currently set 0-based transaction number.
fn transaction_number(&self) -> u16;
/// Sets the current transaction number.
fn set_transaction_number(&mut self, value: u16);

/// Returns the value of the context register.
fn context_u128_register(&self) -> u128;
/// Sets the value of the context register.
fn set_context_u128_register(&mut self, value: u128);

/// Iterates over storage slots read or written during VM execution.
fn get_storage_state(&self) -> impl Iterator<Item = ((H160, U256), U256)>;

/// Iterates over all transient storage slots set during VM execution.
fn get_transient_storage_state(&self) -> impl Iterator<Item = ((H160, U256), U256)>;
/// Gets value of the specified transient storage slot.
fn get_transient_storage(&self, address: H160, slot: U256) -> U256;
/// Sets value of the specified transient storage slot.
fn write_transient_storage(&mut self, address: H160, slot: U256, value: U256);

/// Iterates over events emitted during VM execution.
fn events(&self) -> impl Iterator<Item = Event>;
/// Iterates over L2-to-L1 logs emitted during VM execution.
fn l2_to_l1_logs(&self) -> impl Iterator<Item = L2ToL1Log>;

/// Gets the current amount of published pubdata.
fn pubdata(&self) -> i32;
/// Sets the current amount of published pubdata.
fn set_pubdata(&mut self, value: i32);
}

/// VM execution flags. See the EraVM reference for more details.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Flags {
/// "Less than" flag.
pub less_than: bool,
/// "Equal" flag.
pub equal: bool,
/// "Greater than" flag.
pub greater: bool,
}

/// Public interface of an EraVM call frame.
pub trait CallframeInterface {
/// Address of the storage context associated with this frame. For delegate calls, this address is inherited from the calling contract;
/// otherwise, it's the same as [`Self::code_address()`].
fn address(&self) -> H160;
/// Sets the address of the executing contract.
fn set_address(&mut self, address: H160);
/// Address of the contract being executed.
fn code_address(&self) -> H160;
/// Sets the address of the contract being executed. Does not cause the contract at the specified address get loaded per se, just updates
/// the value used internally by the VM (e.g., returned by the [`CodeAddress`](crate::opcodes::CodeAddress) opcode).
fn set_code_address(&mut self, address: H160);
/// Address of the calling contract. Respects delegate and mimic calls.
fn caller(&self) -> H160;
/// Sets the address of the calling contract.
fn set_caller(&mut self, address: H160);

/// During panic and arbitrary code execution this returns None.
/// Returns the current program counter (i.e., 0-based index of the instruction being executed).
/// During panic this returns `None`.
fn program_counter(&self) -> Option<u16>;

/// Sets the program counter.
/// The VM will execute an invalid instruction if you jump out of the program.
fn set_program_counter(&mut self, value: u16);

/// Returns the program counter that the parent frame should continue from if this frame fails.
fn exception_handler(&self) -> u16;
/// Sets the exception handler as specified [above](Self::exception_handler()).
fn set_exception_handler(&mut self, value: u16);

/// Checks whether the call is static.
fn is_static(&self) -> bool;
/// Checks whether the call is executed in kernel mode.
fn is_kernel(&self) -> bool;

/// Returns the remaining amount of gas.
fn gas(&self) -> u32;
/// Sets the remaining amount of gas.
fn set_gas(&mut self, new_gas: u32);
/// Additional gas provided for the duration of this callframe.
fn stipend(&self) -> u32;

/// Returns the context value for this call. This context is accessible via [`ContextU128`](crate::opcodes::ContextU128) opcode.
fn context_u128(&self) -> u128;
/// Sets the context value for this call.
fn set_context_u128(&mut self, value: u128);

/// Checks whether this frame corresponds to a near call.
fn is_near_call(&self) -> bool;

/// Reads the specified stack slot. Returns a value together with a pointer flag.
fn read_stack(&self, index: u16) -> (U256, bool);
/// Sets the value and pointer flag for the specified stack slot.
fn write_stack(&mut self, index: u16, value: U256, is_pointer: bool);

/// Returns the stack pointer.
fn stack_pointer(&self) -> u16;
/// Sets the stack pointer.
fn set_stack_pointer(&mut self, value: u16);

/// Returns ID of the main heap used in this call.
fn heap(&self) -> HeapId;
/// Returns the main heap boundary (number of paid bytes).
fn heap_bound(&self) -> u32;
/// Sets the main heap boundary.
fn set_heap_bound(&mut self, value: u32);

/// Returns ID of the auxiliary heap used in this call.
fn aux_heap(&self) -> HeapId;
/// Returns the auxiliary heap boundary (number of paid bytes).
fn aux_heap_bound(&self) -> u32;
/// Sets the auxiliary heap boundary.
fn set_aux_heap_bound(&mut self, value: u32);

fn read_code_page(&self, slot: u16) -> U256;
/// Reads a word from the bytecode of the executing contract.
fn read_contract_code(&self, slot: u16) -> U256;
}

/// Identifier of a VM heap page.
/// Identifier of a VM heap.
///
/// EraVM docs sometimes refer to heaps as *heap pages*; docs in these crate don't to avoid confusion with internal heap structure.
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct HeapId(u32);

impl HeapId {
/// Identifier of the calldata heap page used by the first executed program (i.e., the bootloader).
/// Identifier of the calldata heap used by the first executed program (i.e., the bootloader).
pub const FIRST_CALLDATA: Self = Self(1);
/// Identifier of the heap page used by the first executed program (i.e., the bootloader).
/// Identifier of the heap used by the first executed program (i.e., the bootloader).
pub const FIRST: Self = Self(2);
/// Identifier of the auxiliary heap page used by the first executed program (i.e., the bootloader)
/// Identifier of the auxiliary heap used by the first executed program (i.e., the bootloader)
pub const FIRST_AUX: Self = Self(3);

/// Only for dealing with external data structures, never use internally.
Expand All @@ -109,33 +167,49 @@ impl HeapId {
Self(value)
}

/// Converts this ID to an integer value.
pub const fn as_u32(self) -> u32 {
self.0
}
}

/// Event emitted by EraVM.
///
/// There is no address field because nobody is interested in events that don't come
/// from the event writer, so we simply do not record events coming frome anywhere else.
#[derive(Clone, PartialEq, Debug)]
/// from the event writer, so we simply do not record events coming from anywhere else.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Event {
/// Event key.
pub key: U256,
/// Event value.
pub value: U256,
/// Is this event first in a chain of events?
pub is_first: bool,
/// Shard identifier (currently, always set to 0).
pub shard_id: u8,
/// 0-based index of a transaction that has emitted this event.
pub tx_number: u16,
}

#[derive(Debug)]
/// L2-to-L1 log emitted by EraVM.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct L2ToL1Log {
/// Log key.
pub key: U256,
/// Log value.
pub value: U256,
/// Is this a service log?
pub is_service: bool,
/// Address of the contract that has emitted this log.
pub address: H160,
/// Shard identifier (currently, always set to 0).
pub shard_id: u8,
/// 0-based index of a transaction that has emitted this event.
pub tx_number: u16,
}

#[cfg(test)]
#[derive(Debug)]
pub struct DummyState;

#[cfg(test)]
Expand Down Expand Up @@ -212,11 +286,11 @@ impl StateInterface for DummyState {
unimplemented!()
}

fn events(&self) -> impl Iterator<Item = crate::Event> {
fn events(&self) -> impl Iterator<Item = Event> {
std::iter::empty()
}

fn l2_to_l1_logs(&self) -> impl Iterator<Item = crate::L2ToL1Log> {
fn l2_to_l1_logs(&self) -> impl Iterator<Item = L2ToL1Log> {
std::iter::empty()
}

Expand Down Expand Up @@ -319,7 +393,7 @@ impl CallframeInterface for DummyState {
unimplemented!()
}

fn heap(&self) -> crate::HeapId {
fn heap(&self) -> HeapId {
unimplemented!()
}

Expand All @@ -331,7 +405,7 @@ impl CallframeInterface for DummyState {
unimplemented!()
}

fn aux_heap(&self) -> crate::HeapId {
fn aux_heap(&self) -> HeapId {
unimplemented!()
}

Expand All @@ -343,7 +417,7 @@ impl CallframeInterface for DummyState {
unimplemented!()
}

fn read_code_page(&self, _: u16) -> U256 {
fn read_contract_code(&self, _: u16) -> U256 {
unimplemented!()
}
}
Loading

0 comments on commit 6f9d832

Please sign in to comment.