Skip to content

Commit

Permalink
Feat: refactore evm-tests (#40)
Browse files Browse the repository at this point in the history
* Extend tests output and refactore tests runner

* Refactored CLI for recursive tests and extended information

* Set Shanghai spec as default

* Extend output for tests

* evm-tests: extend state statistics for failed results

* evm-tests: extended debug info

* Extend gas cost analyzer

* Extend tests and fixes for Cancun hard fork

* Fix: Apply CREATE storage reset - changed logic. GAS cost for MCOPY - improved calculation

* EIP-3607 implementation. DeepCall bug fixes. KZG-boilerplate

* KZG precompile

* Removev debug info

* Fix: tests for EIP-3860 (#41)

undefined

* ForkSpec string error. Refactored usage as constantn USIZE_MAX

* Remove already fixed tests from skipping list

* Added KzgInput
  • Loading branch information
mrLSD authored May 28, 2024
1 parent da568d3 commit 1133628
Show file tree
Hide file tree
Showing 23 changed files with 5,255 additions and 183 deletions.
68 changes: 34 additions & 34 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,52 +35,52 @@ name = "loop"
harness = false

[features]
default = ["std"]
default = ["std", "force-debug"]
std = [
"ethereum/std",
"log/std",
"primitive-types/std",
"rlp/std",
"sha3/std",
"environmental/std",
"scale-codec/std",
"scale-info/std",
"serde/std",
"evm-core/std",
"evm-gasometer/std",
"evm-runtime/std",
"ethereum/std",
"log/std",
"primitive-types/std",
"rlp/std",
"sha3/std",
"environmental/std",
"scale-codec/std",
"scale-info/std",
"serde/std",
"evm-core/std",
"evm-gasometer/std",
"evm-runtime/std",
]
with-codec = [
"scale-codec",
"scale-info",
"primitive-types/codec",
"primitive-types/scale-info",
"ethereum/with-codec",
"evm-core/with-codec",
"scale-codec",
"scale-info",
"primitive-types/codec",
"primitive-types/scale-info",
"ethereum/with-codec",
"evm-core/with-codec",
]
with-serde = [
"serde",
"primitive-types/impl-serde",
"evm-core/with-serde",
"ethereum/with-serde",
"serde",
"primitive-types/impl-serde",
"evm-core/with-serde",
"ethereum/with-serde",
]
tracing = [
"environmental",
"evm-core/tracing",
"evm-gasometer/tracing",
"evm-runtime/tracing",
"environmental",
"evm-core/tracing",
"evm-gasometer/tracing",
"evm-runtime/tracing",
]
force-debug = [
"evm-core/force-debug",
"evm-gasometer/force-debug",
"evm-core/force-debug",
"evm-gasometer/force-debug",
]
create-fixed = []

[workspace]
members = [
"core",
"gasometer",
"runtime",
"fuzzer",
"evm-tests/jsontests"
"core",
"gasometer",
"runtime",
"fuzzer",
"evm-tests/jsontests"
]
4 changes: 2 additions & 2 deletions core/src/eval/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,15 @@ macro_rules! op3_u256_fn {

macro_rules! as_usize_or_fail {
( $v:expr ) => {{
if $v > U256::from(usize::MAX) {
if $v > crate::utils::USIZE_MAX {
return Control::Exit(ExitFatal::NotSupported.into());
}

$v.as_usize()
}};

( $v:expr, $reason:expr ) => {{
if $v > U256::from(usize::MAX) {
if $v > crate::utils::USIZE_MAX {
return Control::Exit($reason.into());
}

Expand Down
3 changes: 3 additions & 0 deletions core/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub const BLOB_GASPRICE_UPDATE_FRACTION: u64 = 3338477;
/// First version of the blob.
pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;

/// Precalculated `usize::MAX` for `U256`
pub const USIZE_MAX: U256 = U256([usize::MAX as u64, 0, 0, 0]);

/// Calculates the `excess_blob_gas` from the parent header's `blob_gas_used` and `excess_blob_gas`.
///
/// See also [the EIP-4844 helpers]<https://eips.ethereum.org/EIPS/eip-4844#helpers>
Expand Down
5 changes: 4 additions & 1 deletion evm-tests/ethcore-builtin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ libsecp256k1 = "0.7"
ripemd = { version = "0.1", default-features = false }
sha2 = { version = "0.10.0", default-features = false }
eth_pairings = { git = "https://github.com/matter-labs/eip1962.git", default-features = false, features = ["eip_2537"], rev = "ece6cbabc41948db4200e41f0bfdab7ab94c7af8" }
hex-literal = "0.4"
c-kzg = "1.0"
derive_more = "0.99"
hex = "0.4.3"

[dev-dependencies]
hex-literal = "0.4"
maplit = "1.0.2"
Binary file added evm-tests/ethcore-builtin/assets/g1_points.bin
Binary file not shown.
Binary file added evm-tests/ethcore-builtin/assets/g2_points.bin
Binary file not shown.
4,163 changes: 4,163 additions & 0 deletions evm-tests/ethcore-builtin/assets/trusted_setup.txt

Large diffs are not rendered by default.

229 changes: 229 additions & 0 deletions evm-tests/ethcore-builtin/src/kzg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
use c_kzg::{Bytes32, Bytes48, KzgProof, KzgSettings, BYTES_PER_G1_POINT, BYTES_PER_G2_POINT};
use core::convert::TryInto;
use core::hash::{Hash, Hasher};
use derive_more::{AsMut, AsRef, Deref, DerefMut};
use hex_literal::hex;
use sha2::Digest;
use std::convert::TryFrom;
use std::rc::Rc;

pub const RETURN_VALUE: &[u8; 64] = &hex!(
"0000000000000000000000000000000000000000000000000000000000001000"
"73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"
);

/// Number of G1 Points.
const NUM_G1_POINTS: usize = 4096;

/// Number of G2 Points.
const NUM_G2_POINTS: usize = 65;

/// A newtype over list of G1 point from kzg trusted setup.
#[derive(Debug, Clone, PartialEq, AsRef, AsMut, Deref, DerefMut)]
#[repr(transparent)]
struct G1Points(pub [[u8; BYTES_PER_G1_POINT]; NUM_G1_POINTS]);

impl Default for G1Points {
fn default() -> Self {
Self([[0; BYTES_PER_G1_POINT]; NUM_G1_POINTS])
}
}

/// A newtype over list of G2 point from kzg trusted setup.
#[derive(Debug, Clone, Eq, PartialEq, AsRef, AsMut, Deref, DerefMut)]
#[repr(transparent)]
struct G2Points(pub [[u8; BYTES_PER_G2_POINT]; NUM_G2_POINTS]);

impl Default for G2Points {
fn default() -> Self {
Self([[0; BYTES_PER_G2_POINT]; NUM_G2_POINTS])
}
}

/// Default G1 points.
const G1_POINTS: &G1Points = {
const BYTES: &[u8] = include_bytes!("../assets/g1_points.bin");
assert!(BYTES.len() == core::mem::size_of::<G1Points>());
unsafe { &*BYTES.as_ptr().cast::<G1Points>() }
};

/// Default G2 points.
const G2_POINTS: &G2Points = {
const BYTES: &[u8] = include_bytes!("../assets/g2_points.bin");
assert!(BYTES.len() == core::mem::size_of::<G2Points>());
unsafe { &*BYTES.as_ptr().cast::<G2Points>() }
};

/// Parses the contents of a KZG trusted setup file into a list of G1 and G2 points.
///
/// These can then be used to create a KZG settings object with
/// [`KzgSettings::load_trusted_setup`](c_kzg::KzgSettings::load_trusted_setup).
#[allow(dead_code)]
fn parse_kzg_trusted_setup(
trusted_setup: &str,
) -> Result<(Box<G1Points>, Box<G2Points>), &'static str> {
let mut lines = trusted_setup.lines();

// load number of points
let n_g1 = lines
.next()
.ok_or("KzgFileFormatError")?
.parse::<usize>()
.map_err(|_| "KzgParseError")?;
let n_g2 = lines
.next()
.ok_or("KzgFileFormatError")?
.parse::<usize>()
.map_err(|_| "KzgParseError")?;

if n_g1 != NUM_G1_POINTS {
return Err("KzgMismatchedNumberOfPoints");
}

if n_g2 != NUM_G2_POINTS {
return Err("KzgMismatchedNumberOfPoints");
}

// load g1 points
let mut g1_points = Box::<G1Points>::default();
for bytes in &mut g1_points.0 {
let line = lines.next().ok_or("KzgFileFormatError")?;
hex::decode_to_slice(line, bytes).map_err(|_| "KzgParseError")?;
}

// load g2 points
let mut g2_points = Box::<G2Points>::default();
for bytes in &mut g2_points.0 {
let line = lines.next().ok_or("KzgFileFormatError")?;
hex::decode_to_slice(line, bytes).map_err(|_| "KzgParseError")?;
}

if lines.next().is_some() {
return Err("KzgFileFormatError");
}

Ok((g1_points, g2_points))
}

/// KZG Settings that allow us to specify a custom trusted setup.
/// or use hardcoded default settings.
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub enum EnvKzgSettings {
/// Default mainnet trusted setup
#[default]
Default,
/// Custom trusted setup.
Custom(Rc<KzgSettings>),
}

// Implement PartialEq and Hash manually because `c_kzg::KzgSettings` does not implement them
impl PartialEq for EnvKzgSettings {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Default, Self::Default) => true,
(Self::Custom(a), Self::Custom(b)) => Rc::ptr_eq(a, b),
_ => false,
}
}
}

impl Hash for EnvKzgSettings {
fn hash<H: Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
Self::Default => {}
Self::Custom(settings) => Rc::as_ptr(settings).hash(state),
}
}
}

impl EnvKzgSettings {
/// Return set KZG settings.
///
/// In will initialize the default settings if it is not already loaded.
pub fn get(&self) -> Rc<KzgSettings> {
match self {
Self::Default => {
let res = KzgSettings::load_trusted_setup(G1_POINTS.as_ref(), G2_POINTS.as_ref())
.expect("failed to load default trusted setup");
Rc::new(res)
}
Self::Custom(settings) => settings.clone(),
}
}
}

/// `VERSIONED_HASH_VERSION_KZG ++ sha256(commitment)[1..]`
#[inline]
pub fn kzg_to_versioned_hash(commitment: &[u8]) -> [u8; 32] {
const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
let mut hash: [u8; 32] = sha2::Sha256::digest(commitment).into();
hash[0] = VERSIONED_HASH_VERSION_KZG;
hash
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct KzgInput {
commitment: Bytes48,
z: Bytes32,
y: Bytes32,
proof: Bytes48,
}

impl KzgInput {
#[inline]
pub fn verify_kzg_proof(&self, kzg_settings: &KzgSettings) -> bool {
KzgProof::verify_kzg_proof(
&self.commitment,
&self.z,
&self.y,
&self.proof,
kzg_settings,
)
.unwrap_or(false)
}
}

impl TryFrom<&[u8]> for KzgInput {
type Error = &'static str;

fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
if input.len() != 192 {
return Err("BlobInvalidInputLength");
}
// Verify commitment matches versioned_hash
let versioned_hash = &input[..32];
let commitment = &input[96..144];
if kzg_to_versioned_hash(commitment) != versioned_hash {
return Err("BlobMismatchedVersion");
}
let commitment = *as_bytes48(commitment);
let z = *as_bytes32(&input[32..64]);
let y = *as_bytes32(&input[64..96]);
let proof = *as_bytes48(&input[144..192]);
Ok(Self {
commitment,
z,
y,
proof,
})
}
}

#[inline]
fn as_array<const N: usize>(bytes: &[u8]) -> &[u8; N] {
bytes.try_into().expect("slice with incorrect length")
}

#[inline]
fn as_bytes32(bytes: &[u8]) -> &Bytes32 {
// SAFETY: `#[repr(C)] Bytes32([u8; 32])`
unsafe { &*as_array::<32>(bytes).as_ptr().cast() }
}

#[inline]
fn as_bytes48(bytes: &[u8]) -> &Bytes48 {
// SAFETY: `#[repr(C)] Bytes48([u8; 48])`
unsafe { &*as_array::<48>(bytes).as_ptr().cast() }
}
Loading

0 comments on commit 1133628

Please sign in to comment.