diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e829433d4..b17047f7e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -27,15 +27,11 @@ jobs: run: cargo build --no-default-features --verbose - name: Build for feature (tracing) run: cargo build --features tracing --verbose - - name: Run tests - run: cargo test --verbose - jsontests: + tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: recursive - name: Run tests - run: | - cd evm-tests/jsontests - cargo test --release + run: cargo test --all --verbose diff --git a/.gitmodules b/.gitmodules index 922479e36..4cfd76952 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ -[submodule "evm-tests"] - path = evm-tests - url = https://github.com/aurora-is-near/evm-tests.git +[submodule "evm-tests/jsontests/res/ethtests"] + path = evm-tests/jsontests/res/ethtests + url = https://github.com/ethereum/tests + tag = v12.3 diff --git a/Cargo.toml b/Cargo.toml index d581189e2..8a563eac5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,4 +82,5 @@ members = [ "gasometer", "runtime", "fuzzer", + "evm-tests/jsontests" ] diff --git a/evm-tests b/evm-tests deleted file mode 160000 index b35875ca2..000000000 --- a/evm-tests +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b35875ca23e1d9fdd17a6c36532c0aace62397f8 diff --git a/evm-tests/EIP-152/Cargo.toml b/evm-tests/EIP-152/Cargo.toml new file mode 100644 index 000000000..bafe65732 --- /dev/null +++ b/evm-tests/EIP-152/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "eip-152" +version = "0.1.0" +authors = ["Parity Technologies "] +repository = "https://github.com/openethereum/openethereum" +documentation = "https://docs.rs/eip-152" +readme = "README.md" +description = "eip-512 blake2 F compression function" +keywords = ["eip-152", "eip152", "eip"] +license = "GPL-3.0" +edition = "2018" + +[dependencies] +arrayref = "0.3.5" + +[dev-dependencies] +criterion = "0.3" +rustc-hex = "2.1.0" + +[[bench]] +name = "bench" +harness = false diff --git a/evm-tests/EIP-152/LICENSE b/evm-tests/EIP-152/LICENSE new file mode 100644 index 000000000..09df722dc --- /dev/null +++ b/evm-tests/EIP-152/LICENSE @@ -0,0 +1,25 @@ +This program is copyright 2020 Parity Technologies Limited and its licensors. + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + +Some portions of the program (“the Software”) are Copyright (c) 2018 Jack O'Connor +and the following relates solely to such portions: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/evm-tests/EIP-152/benches/bench.rs b/evm-tests/EIP-152/benches/bench.rs new file mode 100644 index 000000000..f537399e4 --- /dev/null +++ b/evm-tests/EIP-152/benches/bench.rs @@ -0,0 +1,214 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use eip_152::portable; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +mod avx { + use criterion::{black_box, BenchmarkId, Criterion, Throughput}; + use std::mem; + use std::sync::atomic::{AtomicPtr, Ordering}; + + use eip_152::{avx2, portable}; + + type FnRaw = *mut (); + type Blake2bF = fn(&mut [u64; 8], [u64; 16], [u64; 2], bool, usize); + + static FN: AtomicPtr<()> = AtomicPtr::new(detect as FnRaw); + + fn detect(state: &mut [u64; 8], message: [u64; 16], count: [u64; 2], f: bool, rounds: usize) { + let fun = if is_x86_feature_detected!("avx2") { + avx2::compress as FnRaw + } else { + portable::compress as FnRaw + }; + FN.store(fun as FnRaw, Ordering::Relaxed); + unsafe { mem::transmute::(fun)(state, message, count, f, rounds) } + } + + pub fn avx_ifunc_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("avx2_ifunc"); + + for rounds in [12, 50, 100].iter() { + group.throughput(Throughput::Elements(*rounds as u64)); + group.bench_with_input(BenchmarkId::new("rounds", rounds), &rounds, |b, rounds| { + let mut state = [ + 0x6a09e667f2bdc948_u64, + 0xbb67ae8584caa73b_u64, + 0x3c6ef372fe94f82b_u64, + 0xa54ff53a5f1d36f1_u64, + 0x510e527fade682d1_u64, + 0x9b05688c2b3e6c1f_u64, + 0x1f83d9abfb41bd6b_u64, + 0x5be0cd19137e2179_u64, + ]; + + let message = [ + 0x0000000000636261_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + ]; + let count = [3, 0]; + let f = true; + + b.iter(|| unsafe { + let fun = FN.load(Ordering::Relaxed); + mem::transmute::(fun)( + black_box(&mut state), + black_box(message), + black_box(count), + black_box(f), + black_box(**rounds as usize), + ); + }); + }); + } + + group.finish(); + } + + pub fn avx_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("avx2"); + + for rounds in [12, 50, 100].iter() { + group.throughput(Throughput::Elements(*rounds as u64)); + group.bench_with_input(BenchmarkId::new("rounds", rounds), &rounds, |b, rounds| { + let mut state = [ + 0x6a09e667f2bdc948_u64, + 0xbb67ae8584caa73b_u64, + 0x3c6ef372fe94f82b_u64, + 0xa54ff53a5f1d36f1_u64, + 0x510e527fade682d1_u64, + 0x9b05688c2b3e6c1f_u64, + 0x1f83d9abfb41bd6b_u64, + 0x5be0cd19137e2179_u64, + ]; + + let message = [ + 0x0000000000636261_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + ]; + let count = [3, 0]; + let f = true; + + b.iter(|| unsafe { + avx2::compress( + black_box(&mut state), + black_box(message), + black_box(count), + black_box(f), + black_box(**rounds as usize), + ); + }); + }); + } + + group.finish(); + } +} + +pub fn portable_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("portable_impl"); + + for rounds in [12, 50, 100].iter() { + group.throughput(Throughput::Elements(*rounds as u64)); + group.bench_with_input(BenchmarkId::new("rounds", rounds), &rounds, |b, rounds| { + let mut state = [ + 0x6a09e667f2bdc948_u64, + 0xbb67ae8584caa73b_u64, + 0x3c6ef372fe94f82b_u64, + 0xa54ff53a5f1d36f1_u64, + 0x510e527fade682d1_u64, + 0x9b05688c2b3e6c1f_u64, + 0x1f83d9abfb41bd6b_u64, + 0x5be0cd19137e2179_u64, + ]; + + let message = [ + 0x0000000000636261_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + ]; + let count = [3, 0]; + let f = true; + + b.iter(|| { + portable::compress( + black_box(&mut state), + black_box(message), + black_box(count), + black_box(f), + black_box(**rounds as usize), + ); + }); + }); + } + + group.finish(); +} + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +criterion_group!( + benches, + avx::avx_benchmark, + avx::avx_ifunc_benchmark, + portable_benchmark +); +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] +criterion_group!(benches, portable_benchmark); + +criterion_main!(benches); diff --git a/evm-tests/EIP-152/src/avx2.rs b/evm-tests/EIP-152/src/avx2.rs new file mode 100644 index 000000000..c2a3cb706 --- /dev/null +++ b/evm-tests/EIP-152/src/avx2.rs @@ -0,0 +1,467 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! AVX2 implementation of the blake2b compression function. +use crate::IV; + +use arrayref::{array_refs, mut_array_refs}; +#[cfg(target_arch = "x86")] +use core::arch::x86::*; +#[cfg(target_arch = "x86_64")] +use core::arch::x86_64::*; + +// Adapted from https://github.com/rust-lang-nursery/stdsimd/pull/479. +macro_rules! _MM_SHUFFLE { + ($z:expr, $y:expr, $x:expr, $w:expr) => { + ($z << 6) | ($y << 4) | ($x << 2) | $w + }; +} + +/// The Blake2b compression function F. See https://tools.ietf.org/html/rfc7693#section-3.2 +/// Takes as an argument the state vector `state`, message block vector `message`, offset counter, final +/// block indicator flag `f`, and number of rounds `rounds`. The state vector provided as the first +/// parameter is modified by the function. +/// +/// `g1` only operates on `x` from the original g function. +/// ``` +/// fn portable_g1(v: &mut [u64], a: usize, b: usize, c: usize, d: usize, x: u64) { +/// v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); +/// v[d] = (v[d] ^ v[a]).rotate_right(32); +/// v[c] = v[c].wrapping_add(v[d]); +/// v[b] = (v[b] ^ v[c]).rotate_right(24); +/// } +/// ``` +/// +/// `g2` only operates on `y` from the original g function. +/// ``` +/// fn portable_g2(v: &mut [u64], a: usize, b: usize, c: usize, d: usize, y: u64) { +/// v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); +/// v[d] = (v[d] ^ v[a]).rotate_right(16); +/// v[c] = v[c].wrapping_add(v[d]); +/// v[b] = (v[b] ^ v[c]).rotate_right(63); +/// } +/// ``` +/// +/// Message mixing is done based on sigma values, for a given round. +/// +/// # Example +/// +/// `SIGMA` for round 1 i.e `SIGMA[0]` = `[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]`; +/// ``` +/// let s = &SIGMA[0 % 10]; +/// // a, b, c, d, x +/// g(&mut v, 0, 4, 8 , 12, m[s[0]]); +/// g(&mut v, 1, 5, 9 , 13, m[s[2]]); +/// g(&mut v, 2, 6, 10, 14, m[s[4]]); +/// g(&mut v, 3, 7, 11, 15, m[s[6]]); +/// +/// let a = v[..4]; +/// let b = v[4..8]; +/// let c = v[8..12]; +/// let d = v[12..16]; +/// let mut b0 = [m[0], m[2], m[4], m[6]]; +/// +/// g1(&mut a, &mut b, &mut c, &mut d, &b0); +/// // ... then construct b0 for `g2` etc. +/// ``` +/// +#[target_feature(enable = "avx2")] +#[allow(clippy::missing_safety_doc)] +pub unsafe fn compress( + state: &mut [u64; 8], + message: [u64; 16], + count: [u64; 2], + f: bool, + rounds: usize, +) { + // get a mutable reference to state[0..4], state[4..] + let (state_low, state_high) = mut_array_refs!(state, 4, 4); + // get a reference to IV[0..4], IV[4..] + let (iv_low, iv_high) = array_refs!(&IV, 4, 4); + + // loads them into an __m256i + let mut a = loadu(state_low); + let mut b = loadu(state_high); + let mut c = loadu(iv_low); + + // !a = xor(a, xor(a, !a)) + let inverse = if f { iv_high[3] ^ !iv_high[3] } else { 0 }; + + let flags = set4(count[0], count[1], inverse, 0); + + let mut d = xor(loadu(iv_high), flags); + + // get a reference to message[(0..2)+,] + let msg_chunks = array_refs!(&message, 2, 2, 2, 2, 2, 2, 2, 2); + // load each message [u64; 2] into an __m128i, broadcast it into both lanes of an __m256i. + + // m0 = __m256i([message[0], message[1], message[0], message[1]]) + let m0 = _mm256_broadcastsi128_si256(loadu_128(msg_chunks.0)); + // m1 = __m256i([message[2], message[3], message[2], message[3]]) + let m1 = _mm256_broadcastsi128_si256(loadu_128(msg_chunks.1)); + // m2 = __m256i([message[4], message[5], message[4], message[5]]) + let m2 = _mm256_broadcastsi128_si256(loadu_128(msg_chunks.2)); + // m3 = __m256i([message[6], message[7], message[6], message[7]]) + let m3 = _mm256_broadcastsi128_si256(loadu_128(msg_chunks.3)); + // m4 = __m256i([message[8], message[9], message[8], message[9]]) + let m4 = _mm256_broadcastsi128_si256(loadu_128(msg_chunks.4)); + // m5 = __m256i([message[10], message[11], message[10], message[11]]) + let m5 = _mm256_broadcastsi128_si256(loadu_128(msg_chunks.5)); + // m6 = __m256i([message[12], message[13], message[12], message[13]]) + let m6 = _mm256_broadcastsi128_si256(loadu_128(msg_chunks.6)); + // m7 = __m256i([message[14], message[15], message[14], message[15]]) + let m7 = _mm256_broadcastsi128_si256(loadu_128(msg_chunks.7)); + + let iv0 = a; + let iv1 = b; + + let mut t0; + let mut t1; + let mut b0; + + for i in 0..rounds { + match i % 10 { + 0 => { + t0 = _mm256_unpacklo_epi64(m0, m1); // ([0, 1, 0, 1], [2, 3, 2, 3]) = [0, 2, 0, 2] + t1 = _mm256_unpacklo_epi64(m2, m3); // ([4, 5, 4, 5], [6, 7, 6, 7]) = [4, 6, 4, 6] + b0 = _mm256_blend_epi32(t0, t1, 0xF0); // ([0, 2, 0, 2], [4, 6, 4, 6]) = [0, 2, 4, 6] + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpackhi_epi64(m0, m1); // ([0, 1, 0, 1], [2, 3, 2, 3]) = [1, 3, 1, 3] + t1 = _mm256_unpackhi_epi64(m2, m3); // ([4, 5, 4, 5], [6, 7, 6, 7]) = [5, 7, 5, 7] + b0 = _mm256_blend_epi32(t0, t1, 0xF0); // ([1, 3, 1, 3], [5, 7, 5, 7]) = [1, 3, 5, 7] + g2(&mut a, &mut b, &mut c, &mut d, &b0); + diagonalize(&mut a, &mut b, &mut c, &mut d); + t0 = _mm256_unpacklo_epi64(m7, m4); // ([14, 15, 14, 15], [8, 9, 8, 9]) = [14, 8, 14, 8] + t1 = _mm256_unpacklo_epi64(m5, m6); // ([10, 11, 10, 11], [12, 13, 12, 13]) = [10, 12, 10, 12] + b0 = _mm256_blend_epi32(t0, t1, 0xF0); // ([14, 8, 14, 8], [10, 12, 10, 12]) = [14, 8, 10, 12] + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpackhi_epi64(m7, m4); // ([14, 15, 14, 15], [8, 9, 8, 9]) = [15, 9, 15, 9] + t1 = _mm256_unpackhi_epi64(m5, m6); // ([10, 11, 10, 11], [12, 13, 12, 13]) = [11, 13, 11, 13] + b0 = _mm256_blend_epi32(t0, t1, 0xF0); // ([15, 9, 15, 9], [11, 13, 11, 13]) = [15, 9, 11, 13] + g2(&mut a, &mut b, &mut c, &mut d, &b0); + undiagonalize(&mut a, &mut b, &mut c, &mut d); + } + 1 => { + t0 = _mm256_unpacklo_epi64(m7, m2); + t1 = _mm256_unpackhi_epi64(m4, m6); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpacklo_epi64(m5, m4); + t1 = _mm256_alignr_epi8(m3, m7, 8); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + diagonalize(&mut a, &mut b, &mut c, &mut d); + t0 = _mm256_unpackhi_epi64(m2, m0); + t1 = _mm256_blend_epi32(m5, m0, 0x33); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_alignr_epi8(m6, m1, 8); + t1 = _mm256_blend_epi32(m3, m1, 0x33); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + undiagonalize(&mut a, &mut b, &mut c, &mut d); + } + 2 => { + // round 3 + t0 = _mm256_alignr_epi8(m6, m5, 8); + t1 = _mm256_unpackhi_epi64(m2, m7); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpacklo_epi64(m4, m0); + t1 = _mm256_blend_epi32(m6, m1, 0x33); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + diagonalize(&mut a, &mut b, &mut c, &mut d); + t0 = _mm256_alignr_epi8(m5, m4, 8); + t1 = _mm256_unpackhi_epi64(m1, m3); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpacklo_epi64(m2, m7); + t1 = _mm256_blend_epi32(m0, m3, 0x33); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + undiagonalize(&mut a, &mut b, &mut c, &mut d); + } + 3 => { + // round 4 + t0 = _mm256_unpackhi_epi64(m3, m1); + t1 = _mm256_unpackhi_epi64(m6, m5); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpackhi_epi64(m4, m0); + t1 = _mm256_unpacklo_epi64(m6, m7); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + diagonalize(&mut a, &mut b, &mut c, &mut d); + t0 = _mm256_alignr_epi8(m1, m7, 8); + t1 = _mm256_shuffle_epi32(m2, _MM_SHUFFLE!(1, 0, 3, 2)); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpacklo_epi64(m4, m3); + t1 = _mm256_unpacklo_epi64(m5, m0); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + undiagonalize(&mut a, &mut b, &mut c, &mut d); + } + 4 => { + // round 5 + t0 = _mm256_unpackhi_epi64(m4, m2); + t1 = _mm256_unpacklo_epi64(m1, m5); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_blend_epi32(m3, m0, 0x33); + t1 = _mm256_blend_epi32(m7, m2, 0x33); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + diagonalize(&mut a, &mut b, &mut c, &mut d); + t0 = _mm256_alignr_epi8(m7, m1, 8); + t1 = _mm256_alignr_epi8(m3, m5, 8); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpackhi_epi64(m6, m0); + t1 = _mm256_unpacklo_epi64(m6, m4); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + undiagonalize(&mut a, &mut b, &mut c, &mut d); + } + 5 => { + // round 6 + t0 = _mm256_unpacklo_epi64(m1, m3); + t1 = _mm256_unpacklo_epi64(m0, m4); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpacklo_epi64(m6, m5); + t1 = _mm256_unpackhi_epi64(m5, m1); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + diagonalize(&mut a, &mut b, &mut c, &mut d); + t0 = _mm256_alignr_epi8(m2, m0, 8); + t1 = _mm256_unpackhi_epi64(m3, m7); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpackhi_epi64(m4, m6); + t1 = _mm256_alignr_epi8(m7, m2, 8); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + undiagonalize(&mut a, &mut b, &mut c, &mut d); + } + 6 => { + // round 7 + t0 = _mm256_blend_epi32(m0, m6, 0x33); + t1 = _mm256_unpacklo_epi64(m7, m2); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpackhi_epi64(m2, m7); + t1 = _mm256_alignr_epi8(m5, m6, 8); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + diagonalize(&mut a, &mut b, &mut c, &mut d); + t0 = _mm256_unpacklo_epi64(m4, m0); + t1 = _mm256_blend_epi32(m4, m3, 0x33); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpackhi_epi64(m5, m3); + t1 = _mm256_shuffle_epi32(m1, _MM_SHUFFLE!(1, 0, 3, 2)); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + undiagonalize(&mut a, &mut b, &mut c, &mut d); + } + 7 => { + // round 8 + t0 = _mm256_unpackhi_epi64(m6, m3); + t1 = _mm256_blend_epi32(m1, m6, 0x33); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_alignr_epi8(m7, m5, 8); + t1 = _mm256_unpackhi_epi64(m0, m4); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + diagonalize(&mut a, &mut b, &mut c, &mut d); + t0 = _mm256_blend_epi32(m2, m1, 0x33); + t1 = _mm256_alignr_epi8(m4, m7, 8); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpacklo_epi64(m5, m0); + t1 = _mm256_unpacklo_epi64(m2, m3); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + undiagonalize(&mut a, &mut b, &mut c, &mut d); + } + 8 => { + // round 9 + t0 = _mm256_unpacklo_epi64(m3, m7); + t1 = _mm256_alignr_epi8(m0, m5, 8); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpackhi_epi64(m7, m4); + t1 = _mm256_alignr_epi8(m4, m1, 8); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + diagonalize(&mut a, &mut b, &mut c, &mut d); + t0 = _mm256_unpacklo_epi64(m5, m6); + t1 = _mm256_unpackhi_epi64(m6, m0); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_alignr_epi8(m1, m2, 8); + t1 = _mm256_alignr_epi8(m2, m3, 8); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + undiagonalize(&mut a, &mut b, &mut c, &mut d); + } + _ => { + // round 10 + t0 = _mm256_unpacklo_epi64(m5, m4); + t1 = _mm256_unpackhi_epi64(m3, m0); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_unpacklo_epi64(m1, m2); + t1 = _mm256_blend_epi32(m2, m3, 0x33); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + diagonalize(&mut a, &mut b, &mut c, &mut d); + t0 = _mm256_unpackhi_epi64(m6, m7); + t1 = _mm256_unpackhi_epi64(m4, m1); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g1(&mut a, &mut b, &mut c, &mut d, &b0); + t0 = _mm256_blend_epi32(m5, m0, 0x33); + t1 = _mm256_unpacklo_epi64(m7, m6); + b0 = _mm256_blend_epi32(t0, t1, 0xF0); + g2(&mut a, &mut b, &mut c, &mut d, &b0); + undiagonalize(&mut a, &mut b, &mut c, &mut d); + } + } + } + + a = xor(a, c); + b = xor(b, d); + a = xor(a, iv0); + b = xor(b, iv1); + + storeu(a, state_low); + storeu(b, state_high); +} + +#[inline(always)] +unsafe fn loadu(src: *const [u64; 4]) -> __m256i { + // This is an unaligned load, so the pointer cast is allowed. + _mm256_loadu_si256(src as *const __m256i) +} + +#[inline(always)] +unsafe fn storeu(src: __m256i, dest: *mut [u64; 4]) { + // This is an unaligned store, so the pointer cast is allowed. + _mm256_storeu_si256(dest as *mut __m256i, src) +} + +#[inline(always)] +unsafe fn loadu_128(mem_addr: &[u64; 2]) -> __m128i { + _mm_loadu_si128(mem_addr.as_ptr() as *const __m128i) +} + +#[inline(always)] +unsafe fn add(a: __m256i, b: __m256i) -> __m256i { + _mm256_add_epi64(a, b) +} + +#[inline(always)] +unsafe fn xor(a: __m256i, b: __m256i) -> __m256i { + _mm256_xor_si256(a, b) +} + +#[inline(always)] +unsafe fn set4(a: u64, b: u64, c: u64, d: u64) -> __m256i { + _mm256_setr_epi64x(a as i64, b as i64, c as i64, d as i64) +} + +#[inline(always)] +unsafe fn rotate_right_32(x: __m256i) -> __m256i { + _mm256_shuffle_epi32(x, _MM_SHUFFLE!(2, 3, 0, 1)) +} + +#[inline(always)] +unsafe fn rotate_right_24(x: __m256i) -> __m256i { + let rotate24 = _mm256_setr_epi8( + 3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10, 3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, + 14, 15, 8, 9, 10, + ); + _mm256_shuffle_epi8(x, rotate24) +} + +#[inline(always)] +unsafe fn rotate_right_16(x: __m256i) -> __m256i { + let rotate16 = _mm256_setr_epi8( + 2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9, 2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, + 13, 14, 15, 8, 9, + ); + _mm256_shuffle_epi8(x, rotate16) +} + +#[inline(always)] +unsafe fn rotate_right_63(x: __m256i) -> __m256i { + _mm256_or_si256(_mm256_srli_epi64(x, 63), add(x, x)) +} + +#[inline(always)] +unsafe fn g1(a: &mut __m256i, b: &mut __m256i, c: &mut __m256i, d: &mut __m256i, m: &__m256i) { + *a = add(*a, *m); + *a = add(*a, *b); + *d = xor(*d, *a); + *d = rotate_right_32(*d); + *c = add(*c, *d); + *b = xor(*b, *c); + *b = rotate_right_24(*b); +} + +#[inline(always)] +unsafe fn g2(a: &mut __m256i, b: &mut __m256i, c: &mut __m256i, d: &mut __m256i, m: &__m256i) { + *a = add(*a, *m); + *a = add(*a, *b); + *d = xor(*d, *a); + *d = rotate_right_16(*d); + *c = add(*c, *d); + *b = xor(*b, *c); + *b = rotate_right_63(*b); +} + +// Note the optimization here of leaving b as the unrotated row, rather than a. +// All the message loads below are adjusted to compensate for this. See +// discussion at https://github.com/sneves/blake2-avx2/pull/4 +#[inline(always)] +unsafe fn diagonalize(a: &mut __m256i, _b: &mut __m256i, c: &mut __m256i, d: &mut __m256i) { + *a = _mm256_permute4x64_epi64(*a, _MM_SHUFFLE!(2, 1, 0, 3)); + *d = _mm256_permute4x64_epi64(*d, _MM_SHUFFLE!(1, 0, 3, 2)); + *c = _mm256_permute4x64_epi64(*c, _MM_SHUFFLE!(0, 3, 2, 1)); +} + +// Note the optimization here of leaving b as the unrotated row, rather than a. +// All the message loads below are adjusted to compensate for this. See +// discussion at https://github.com/sneves/blake2-avx2/pull/4 +#[inline(always)] +unsafe fn undiagonalize(a: &mut __m256i, _b: &mut __m256i, c: &mut __m256i, d: &mut __m256i) { + *a = _mm256_permute4x64_epi64(*a, _MM_SHUFFLE!(0, 3, 2, 1)); + *d = _mm256_permute4x64_epi64(*d, _MM_SHUFFLE!(1, 0, 3, 2)); + *c = _mm256_permute4x64_epi64(*c, _MM_SHUFFLE!(2, 1, 0, 3)); +} + +#[cfg(test)] +mod tests { + #[test] + fn test_mm_shuffle() { + assert_eq!(_MM_SHUFFLE!(0, 1, 1, 3), 0b00_01_01_11); + assert_eq!(_MM_SHUFFLE!(3, 1, 1, 0), 0b11_01_01_00); + assert_eq!(_MM_SHUFFLE!(1, 2, 2, 1), 0b01_10_10_01); + } +} diff --git a/evm-tests/EIP-152/src/lib.rs b/evm-tests/EIP-152/src/lib.rs new file mode 100644 index 000000000..d96c1b3e7 --- /dev/null +++ b/evm-tests/EIP-152/src/lib.rs @@ -0,0 +1,224 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub mod avx2; +pub mod portable; + +/// The precomputed values for BLAKE2b [from the spec](https://tools.ietf.org/html/rfc7693#section-2.7) +/// There are 10 16-byte arrays - one for each round +/// the entries are calculated from the sigma constants. +const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +/// IV is the initialization vector for BLAKE2b. See https://tools.ietf.org/html/rfc7693#section-2.6 +/// for details. +const IV: [u64; 8] = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, +]; + +/// blake2b compression function +pub fn compress(state: &mut [u64; 8], message: [u64; 16], count: [u64; 2], f: bool, rounds: usize) { + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + if is_x86_feature_detected!("avx2") { + unsafe { avx2::compress(state, message, count, f, rounds) } + } else { + portable::compress(state, message, count, f, rounds); + }; + } + + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + portable::compress(state, message, count, f, rounds); +} + +#[cfg(test)] +mod tests { + use crate::portable; + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + use crate::avx2; + use rustc_hex::FromHex; + + #[test] + fn test_blake2_f() { + // test from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#example-usage-in-solidity + let mut h_in = [ + 0x6a09e667f2bdc948_u64, + 0xbb67ae8584caa73b_u64, + 0x3c6ef372fe94f82b_u64, + 0xa54ff53a5f1d36f1_u64, + 0x510e527fade682d1_u64, + 0x9b05688c2b3e6c1f_u64, + 0x1f83d9abfb41bd6b_u64, + 0x5be0cd19137e2179_u64, + ]; + + let m = [ + 0x0000000000636261_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + 0x0000000000000000_u64, + ]; + let c = [3, 0]; + let f = true; + let rounds = 12; + let h_out: [u64; 8] = [ + 0x0D4D1C983FA580BA_u64, + 0xE9F6129FB697276A_u64, + 0xB7C45A68142F214C_u64, + 0xD1A2FFDB6FBB124B_u64, + 0x2D79AB2A39C5877D_u64, + 0x95CC3345DED552C2_u64, + 0x5A92F1DBA88AD318_u64, + 0x239900D4ED8623B9_u64, + ]; + + // portable + portable::compress(&mut h_in, m, c, f, rounds); + assert_eq!(h_in, h_out); + + // avx + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + let mut h_in = [ + 0x6a09e667f2bdc948_u64, + 0xbb67ae8584caa73b_u64, + 0x3c6ef372fe94f82b_u64, + 0xa54ff53a5f1d36f1_u64, + 0x510e527fade682d1_u64, + 0x9b05688c2b3e6c1f_u64, + 0x1f83d9abfb41bd6b_u64, + 0x5be0cd19137e2179_u64, + ]; + + if is_x86_feature_detected!("avx2") { + unsafe { + avx2::compress(&mut h_in, m, c, f, rounds); + assert_eq!(h_in, h_out); + } + } + } + } + + fn to_u64_slice(vec: &[u8], slice: &mut [u64]) { + vec.chunks(8).enumerate().for_each(|(index, val)| { + slice[index] = u64::from_le_bytes([ + val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7], + ]) + }) + } + + #[test] + fn test_vectors_from_eip() { + let vec = vec![ + ( + // Test vector 4 + "0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b", + ), + ( // test vector 5 + "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923", + ), + ( + // Test vector 6 + "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000", + "75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735", + ), + ( + // Test vector 7 + "0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + "b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421", + ), + // Test vector 8 – u32::MAX rounds – too slow to run +// ( +// "ffffffff48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", +// "fc59093aafa9ab43daae0e914c57635c5402d8e3d2130eb9b3cc181de7f0ecf9b22bf99a7815ce16419e200e01846e6b5df8cc7703041bbceb571de6631d2615", +// ), + ]; + for (hex, output) in vec { + let bytes: Vec = hex.from_hex().unwrap(); + + assert_eq!(bytes.len(), 213); + + let mut h = [0u64; 8]; + let mut m = [0u64; 16]; + let mut t = [0u64; 2]; + + let rounds = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); + let f = match bytes[212] { + 1 => true, + 0 => false, + _ => unreachable!(), + }; + + to_u64_slice(&bytes[4..68], &mut h); + to_u64_slice(&bytes[68..196], &mut m); + to_u64_slice(&bytes[196..212], &mut t); + let output: Vec = output.from_hex().unwrap(); + let mut out = [0u64; 8]; + to_u64_slice(&output[..], &mut out); + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + // avx + if is_x86_feature_detected!("avx2") { + unsafe { + avx2::compress(&mut h, m, t, f, rounds as usize); + assert_eq!(out, h); + } + } + } + + { + // portable + to_u64_slice(&bytes[4..68], &mut h); + portable::compress(&mut h, m, t, f, rounds as usize); + assert_eq!(out, h); + } + } + } +} diff --git a/evm-tests/EIP-152/src/portable.rs b/evm-tests/EIP-152/src/portable.rs new file mode 100644 index 000000000..2b4e89070 --- /dev/null +++ b/evm-tests/EIP-152/src/portable.rs @@ -0,0 +1,68 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Portable implementation of the blake2b compress function + +use crate::{IV, SIGMA}; + +/// The G mixing function. See https://tools.ietf.org/html/rfc7693#section-3.1 +#[inline(always)] +fn g(v: &mut [u64], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) { + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + v[d] = (v[d] ^ v[a]).rotate_right(32); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(24); + + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + v[d] = (v[d] ^ v[a]).rotate_right(16); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(63); +} + +/// The Blake2b compression function F. See https://tools.ietf.org/html/rfc7693#section-3.2 +/// Takes as an argument the state vector `h`, message block vector `m`, offset counter `t`, final +/// block indicator flag `f`, and number of rounds `rounds`. The state vector provided as the first +/// parameter is modified by the function. +pub fn compress(h: &mut [u64; 8], m: [u64; 16], t: [u64; 2], f: bool, rounds: usize) { + let mut v = [0u64; 16]; + v[..8].copy_from_slice(h); // First half from state. + v[8..].copy_from_slice(&IV); // Second half from IV. + + v[12] ^= t[0]; + v[13] ^= t[1]; + + if f { + v[14] = !v[14]; // Invert all bits if the last-block-flag is set. + } + + for i in 0..rounds { + // Message word selection permutation for this round. + let s = &SIGMA[i % 10]; + g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]); + g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]); + g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]); + g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]); + + g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]); + g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]); + } + + for i in 0..8 { + h[i] ^= v[i] ^ v[i + 8]; + } +} diff --git a/evm-tests/ethcore-builtin/Cargo.toml b/evm-tests/ethcore-builtin/Cargo.toml new file mode 100644 index 000000000..8fe980d0e --- /dev/null +++ b/evm-tests/ethcore-builtin/Cargo.toml @@ -0,0 +1,25 @@ +[package] +description = "ethereum vm builtin" +name = "ethcore-builtin" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +bn = { git = "https://github.com/paritytech/bn", rev = "b048fe1", default-features = false } +byteorder = "1.3.2" +eip-152 = { path = "../EIP-152" } +ethereum-types = "0.14" +ethjson = { path = "../ethjson" } +keccak-hash = "0.10" +log = "0.4" +num = "0.2" +parity-bytes = "0.1" +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" } + +[dev-dependencies] +hex-literal = "0.4" +maplit = "1.0.2" diff --git a/evm-tests/ethcore-builtin/src/lib.rs b/evm-tests/ethcore-builtin/src/lib.rs new file mode 100644 index 000000000..370f0d074 --- /dev/null +++ b/evm-tests/ethcore-builtin/src/lib.rs @@ -0,0 +1,2596 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Standard built-in contracts. + +#![warn(missing_docs)] + +use std::{ + cmp::{max, min}, + collections::BTreeMap, + convert::{TryFrom, TryInto}, + io::{self, Cursor, Read}, + str::FromStr, +}; + +use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; +use eip_152::compress; +use eth_pairings::public_interface::eip2537::{ + EIP2537Executor, SCALAR_BYTE_LENGTH, SERIALIZED_G1_POINT_BYTE_LENGTH, + SERIALIZED_G2_POINT_BYTE_LENGTH, +}; +use ethereum_types::{H256, U256}; +use keccak_hash::keccak; +use log::{trace, warn}; +use num::{BigUint, One, Zero}; +use parity_bytes::BytesRef; +use sha2::Digest; + +/// Native implementation of a built-in contract. +pub trait Implementation: Send + Sync { + /// execute this built-in on the given input, writing to the given output. + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str>; +} + +/// A gas pricing scheme for built-in contracts. +trait Pricer: Send + Sync { + /// The gas cost of running this built-in for the given input data at block number `at` + fn cost(&self, input: &[u8]) -> U256; +} + +/// Pricing for the Blake2 compression function (aka "F"). +/// Computes the price as a fixed cost per round where the number of rounds is part of the input +/// byte slice. +pub type Blake2FPricer = u64; + +impl Pricer for Blake2FPricer { + fn cost(&self, input: &[u8]) -> U256 { + const FOUR: usize = std::mem::size_of::(); + // Returning zero if the conversion fails is fine because `execute()` will check the length + // and bail with the appropriate error. + if input.len() < FOUR { + return U256::zero(); + } + let (rounds_bytes, _) = input.split_at(FOUR); + let rounds = u32::from_be_bytes(rounds_bytes.try_into().unwrap_or([0u8; FOUR])); + U256::from(*self as u128 * rounds as u128) + } +} + +/// Pricing model +#[derive(Debug)] +enum Pricing { + AltBn128Pairing(AltBn128PairingPricer), + AltBn128ConstOperations(AltBn128ConstOperations), + Blake2F(Blake2FPricer), + Linear(Linear), + Modexp(ModexpPricer), + Bls12Pairing(Bls12PairingPricer), + Bls12ConstOperations(Bls12ConstOperations), + Bls12MultiexpG1(Bls12MultiexpPricerG1), + Bls12MultiexpG2(Bls12MultiexpPricerG2), +} + +impl Pricer for Pricing { + fn cost(&self, input: &[u8]) -> U256 { + match self { + Self::AltBn128Pairing(inner) => inner.cost(input), + Self::AltBn128ConstOperations(inner) => inner.cost(input), + Self::Blake2F(inner) => inner.cost(input), + Self::Linear(inner) => inner.cost(input), + Self::Modexp(inner) => inner.cost(input), + Self::Bls12Pairing(inner) => inner.cost(input), + Self::Bls12ConstOperations(inner) => inner.cost(input), + Self::Bls12MultiexpG1(inner) => inner.cost(input), + Self::Bls12MultiexpG2(inner) => inner.cost(input), + } + } +} + +/// A linear pricing model. This computes a price using a base cost and a cost per-word. +#[derive(Debug)] +struct Linear { + base: u64, + word: u64, +} + +/// A special pricing model for modular exponentiation. +#[derive(Debug)] +struct ModexpPricer { + divisor: u64, + is_eip_2565: bool, +} + +impl Pricer for Linear { + fn cost(&self, input: &[u8]) -> U256 { + U256::from(self.base) + U256::from(self.word) * U256::from((input.len() + 31) / 32) + } +} + +/// alt_bn128 pairing price +#[derive(Debug, Copy, Clone)] +struct AltBn128PairingPrice { + base: u64, + pair: u64, +} + +/// alt_bn128_pairing pricing model. This computes a price using a base cost and a cost per pair. +#[derive(Debug)] +struct AltBn128PairingPricer { + price: AltBn128PairingPrice, +} + +/// Pricing for constant alt_bn128 operations (ECADD and ECMUL) +#[derive(Debug, Copy, Clone)] +pub struct AltBn128ConstOperations { + /// Fixed price. + pub price: u64, +} + +impl Pricer for AltBn128ConstOperations { + fn cost(&self, _input: &[u8]) -> U256 { + self.price.into() + } +} + +impl Pricer for AltBn128PairingPricer { + fn cost(&self, input: &[u8]) -> U256 { + U256::from(self.price.base) + U256::from(self.price.pair) * U256::from(input.len() / 192) + } +} + +impl Pricer for ModexpPricer { + fn cost(&self, input: &[u8]) -> U256 { + if self.is_eip_2565 { + let mut buf = [0; 32]; + let (base_len, exp_len, mod_len) = Self::read_lengths(input, &mut buf); + let exp = Self::read_exp(input, base_len, exp_len, &mut buf); + Self::eip_2565_cost(self.divisor.into(), base_len, mod_len, exp_len, exp) + } else { + Self::cost(self.divisor, input) + } + } +} + +impl ModexpPricer { + fn adjusted_exp_len(len: u64, exp_low: U256) -> u64 { + let bit_index = if exp_low.is_zero() { + 0 + } else { + (255 - exp_low.leading_zeros()) as u64 + }; + if len <= 32 { + bit_index + } else { + 8 * (len - 32) + bit_index + } + } + + const fn mult_complexity(x: u64) -> u64 { + match x { + x if x <= 64 => x * x, + x if x <= 1024 => (x * x) / 4 + 96 * x - 3072, + x => (x * x) / 16 + 480 * x - 199_680, + } + } + + fn read_lengths(input: &[u8], buf: &mut [u8; 32]) -> (U256, U256, U256) { + let mut reader = input.chain(io::repeat(0)); + let mut read_len = || { + reader + .read_exact(&mut buf[..]) + .expect("reading from zero-extended memory cannot fail; qed"); + U256::from_big_endian(&buf[..]) + }; + let base_len = read_len(); + let exp_len = read_len(); + let mod_len = read_len(); + (base_len, exp_len, mod_len) + } + + fn read_exp(input: &[u8], base_len: U256, exp_len: U256, buf: &mut [u8; 32]) -> U256 { + let base_len = if base_len > U256::from(u32::MAX) { + return U256::zero(); + } else { + base_len.low_u64() + }; + if base_len + 96 >= input.len() as u64 { + U256::zero() + } else { + buf.iter_mut().for_each(|b| *b = 0); + let mut reader = input[(96 + base_len as usize)..].chain(io::repeat(0)); + let len = if exp_len < U256::from(32) { + exp_len.low_u64() as usize + } else { + 32 + }; + reader + .read_exact(&mut buf[(32 - len)..]) + .expect("reading from zero-extended memory cannot fail; qed"); + U256::from_big_endian(&buf[..]) + } + } + + fn cost(divisor: u64, input: &[u8]) -> U256 { + let mut buf = [0; 32]; + + // read lengths as U256 here for accurate gas calculation. + let (base_len, exp_len, mod_len) = Self::read_lengths(input, &mut buf); + + if mod_len.is_zero() && base_len.is_zero() { + return U256::zero(); + } + + let max_len = U256::from(u32::max_value() / 2); + if base_len > max_len || mod_len > max_len || exp_len > max_len { + return U256::max_value(); + } + + // read fist 32-byte word of the exponent. + let exp_low = Self::read_exp(input, base_len, exp_len, &mut buf); + + let (base_len, exp_len, mod_len) = + (base_len.low_u64(), exp_len.low_u64(), mod_len.low_u64()); + + let m = max(mod_len, base_len); + + let adjusted_exp_len = Self::adjusted_exp_len(exp_len, exp_low); + + let (gas, overflow) = Self::mult_complexity(m).overflowing_mul(max(adjusted_exp_len, 1)); + if overflow { + return U256::max_value(); + } + (gas / divisor).into() + } + + fn eip_2565_mul_complexity(base_length: U256, modulus_length: U256) -> U256 { + let max_length = std::cmp::max(base_length, modulus_length); + let words = { + // div_ceil(max_length, 8); + let tmp = max_length / 8; + if (max_length % 8).is_zero() { + tmp + } else { + tmp + 1 + } + }; + words.saturating_mul(words) + } + + fn eip_2565_iter_count(exponent_length: U256, exponent: U256) -> U256 { + let thirty_two = U256::from(32); + let it = if exponent_length <= thirty_two && exponent.is_zero() { + U256::zero() + } else if exponent_length <= thirty_two { + U256::from(exponent.bits()) - U256::from(1) + } else { + // else > 32 + U256::from(8) + .saturating_mul(exponent_length - thirty_two) + .saturating_add(U256::from(exponent.bits()).saturating_sub(U256::from(1))) + }; + std::cmp::max(it, U256::one()) + } + + fn eip_2565_cost( + divisor: U256, + base_length: U256, + modulus_length: U256, + exponent_length: U256, + exponent: U256, + ) -> U256 { + let multiplication_complexity = Self::eip_2565_mul_complexity(base_length, modulus_length); + let iteration_count = Self::eip_2565_iter_count(exponent_length, exponent); + std::cmp::max( + U256::from(200), + multiplication_complexity.saturating_mul(iteration_count) / divisor, + ) + } +} + +/// Bls12 pairing price +#[derive(Debug, Copy, Clone)] +struct Bls12PairingPrice { + base: u64, + pair: u64, +} + +/// bls12_pairing pricing model. This computes a price using a base cost and a cost per pair. +#[derive(Debug)] +struct Bls12PairingPricer { + price: Bls12PairingPrice, +} + +/// Pricing for constant Bls12 operations (ADD and MUL in G1 and G2, as well as mappings) +#[derive(Debug, Copy, Clone)] +pub struct Bls12ConstOperations { + /// Fixed price. + pub price: u64, +} + +/// Discount table for multiexponentiation (Peppinger algorithm) +/// Later on is normalized using the divisor +pub const BLS12_MULTIEXP_DISCOUNTS_TABLE: [[u64; 2]; BLS12_MULTIEXP_PAIRS_FOR_MAX_DISCOUNT] = [ + [1, 1200], + [2, 888], + [3, 764], + [4, 641], + [5, 594], + [6, 547], + [7, 500], + [8, 453], + [9, 438], + [10, 423], + [11, 408], + [12, 394], + [13, 379], + [14, 364], + [15, 349], + [16, 334], + [17, 330], + [18, 326], + [19, 322], + [20, 318], + [21, 314], + [22, 310], + [23, 306], + [24, 302], + [25, 298], + [26, 294], + [27, 289], + [28, 285], + [29, 281], + [30, 277], + [31, 273], + [32, 269], + [33, 268], + [34, 266], + [35, 265], + [36, 263], + [37, 262], + [38, 260], + [39, 259], + [40, 257], + [41, 256], + [42, 254], + [43, 253], + [44, 251], + [45, 250], + [46, 248], + [47, 247], + [48, 245], + [49, 244], + [50, 242], + [51, 241], + [52, 239], + [53, 238], + [54, 236], + [55, 235], + [56, 233], + [57, 232], + [58, 231], + [59, 229], + [60, 228], + [61, 226], + [62, 225], + [63, 223], + [64, 222], + [65, 221], + [66, 220], + [67, 219], + [68, 219], + [69, 218], + [70, 217], + [71, 216], + [72, 216], + [73, 215], + [74, 214], + [75, 213], + [76, 213], + [77, 212], + [78, 211], + [79, 211], + [80, 210], + [81, 209], + [82, 208], + [83, 208], + [84, 207], + [85, 206], + [86, 205], + [87, 205], + [88, 204], + [89, 203], + [90, 202], + [91, 202], + [92, 201], + [93, 200], + [94, 199], + [95, 199], + [96, 198], + [97, 197], + [98, 196], + [99, 196], + [100, 195], + [101, 194], + [102, 193], + [103, 193], + [104, 192], + [105, 191], + [106, 191], + [107, 190], + [108, 189], + [109, 188], + [110, 188], + [111, 187], + [112, 186], + [113, 185], + [114, 185], + [115, 184], + [116, 183], + [117, 182], + [118, 182], + [119, 181], + [120, 180], + [121, 179], + [122, 179], + [123, 178], + [124, 177], + [125, 176], + [126, 176], + [127, 175], + [128, 174], +]; + +/// Max discount allowed +pub const BLS12_MULTIEXP_MAX_DISCOUNT: u64 = 174; +/// Max discount is reached at this number of pairs +pub const BLS12_MULTIEXP_PAIRS_FOR_MAX_DISCOUNT: usize = 128; +/// Divisor for discounts table +pub const BLS12_MULTIEXP_DISCOUNT_DIVISOR: u64 = 1000; +/// Length of single G1 + G2 points pair for pairing operation +pub const BLS12_G1_AND_G2_PAIR_LEN: usize = + SERIALIZED_G1_POINT_BYTE_LENGTH + SERIALIZED_G2_POINT_BYTE_LENGTH; + +/// Marter trait for length of input per one pair (point + scalar) +pub trait PointScalarLength: Copy + Clone + std::fmt::Debug + Send + Sync { + /// Length itself + const LENGTH: usize; +} +/// Marker trait that indicated that we perform operations in G1 +#[derive(Clone, Copy, Debug)] +pub struct G1Marker; +impl PointScalarLength for G1Marker { + const LENGTH: usize = SERIALIZED_G1_POINT_BYTE_LENGTH + SCALAR_BYTE_LENGTH; +} +/// Marker trait that indicated that we perform operations in G2 +#[derive(Clone, Copy, Debug)] +pub struct G2Marker; +impl PointScalarLength for G2Marker { + const LENGTH: usize = SERIALIZED_G2_POINT_BYTE_LENGTH + SCALAR_BYTE_LENGTH; +} + +/// Pricing for constant Bls12 operations (ADD and MUL in G1 and G2, as well as mappings) +#[derive(Debug, Copy, Clone)] +pub struct Bls12MultiexpPricer { + /// Base const of the operation (G1 or G2 multiplication) + pub base_price: Bls12ConstOperations, + + _marker: std::marker::PhantomData

, +} + +impl Pricer for Bls12ConstOperations { + fn cost(&self, _input: &[u8]) -> U256 { + self.price.into() + } +} + +impl Pricer for Bls12PairingPricer { + fn cost(&self, input: &[u8]) -> U256 { + U256::from(self.price.base) + + U256::from(self.price.pair) * U256::from(input.len() / BLS12_G1_AND_G2_PAIR_LEN) + } +} + +impl Pricer for Bls12MultiexpPricer

{ + fn cost(&self, input: &[u8]) -> U256 { + let num_pairs = input.len() / P::LENGTH; + if num_pairs == 0 { + return U256::zero(); + } + let discount = if num_pairs > BLS12_MULTIEXP_PAIRS_FOR_MAX_DISCOUNT { + BLS12_MULTIEXP_MAX_DISCOUNT + } else { + let table_entry = BLS12_MULTIEXP_DISCOUNTS_TABLE[num_pairs - 1]; + table_entry[1] + }; + U256::from(self.base_price.price) * U256::from(num_pairs) * U256::from(discount) + / U256::from(BLS12_MULTIEXP_DISCOUNT_DIVISOR) + } +} + +/// Multiexp pricer in G1 +pub type Bls12MultiexpPricerG1 = Bls12MultiexpPricer; + +/// Multiexp pricer in G2 +pub type Bls12MultiexpPricerG2 = Bls12MultiexpPricer; + +/// Pricing scheme, execution definition, and activation block for a built-in contract. +/// +/// Call `cost` to compute cost for the given input, `execute` to execute the contract +/// on the given input, and `is_active` to determine whether the contract is active. +pub struct Builtin { + pricer: BTreeMap, + native: EthereumBuiltin, +} + +impl Builtin { + /// Simple forwarder for cost. + /// + /// Return the cost of the most recently activated pricer at the current block number. + /// + /// If no pricer is actived `zero` is returned + /// + /// If multiple `activation_at` has the same block number the last one is used + /// (follows `BTreeMap` semantics). + #[inline] + pub fn cost(&self, input: &[u8], at: u64) -> U256 { + if let Some((_, pricer)) = self.pricer.range(0..=at).last() { + pricer.cost(input) + } else { + U256::zero() + } + } + + /// Simple forwarder for execute. + #[inline] + pub fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + self.native.execute(input, output) + } + + /// Whether the builtin is activated at the given block number. + #[inline] + pub fn is_active(&self, at: u64) -> bool { + self.pricer.range(0..=at).last().is_some() + } +} + +impl TryFrom for Builtin { + type Error = (); + + fn try_from(b: ethjson::spec::builtin::Builtin) -> Result { + let native = EthereumBuiltin::from_str(&b.name)?; + let mut pricer = BTreeMap::new(); + + for (activate_at, p) in b.pricing { + pricer.insert(activate_at, p.price.into()); + } + + Ok(Self { pricer, native }) + } +} + +impl From for Pricing { + fn from(pricing: ethjson::spec::builtin::Pricing) -> Self { + match pricing { + ethjson::spec::builtin::Pricing::Blake2F { gas_per_round } => { + Self::Blake2F(gas_per_round) + } + ethjson::spec::builtin::Pricing::Linear(linear) => Self::Linear(Linear { + base: linear.base, + word: linear.word, + }), + ethjson::spec::builtin::Pricing::Modexp(exp) => Self::Modexp(ModexpPricer { + divisor: if exp.divisor == 0 { + warn!(target: "builtin", "Zero modexp divisor specified. Falling back to default: 10."); + 10 + } else { + exp.divisor + }, + is_eip_2565: exp.is_eip_2565, + }), + ethjson::spec::builtin::Pricing::AltBn128Pairing(pricer) => { + Self::AltBn128Pairing(AltBn128PairingPricer { + price: AltBn128PairingPrice { + base: pricer.base, + pair: pricer.pair, + }, + }) + } + ethjson::spec::builtin::Pricing::AltBn128ConstOperations(pricer) => { + Self::AltBn128ConstOperations(AltBn128ConstOperations { + price: pricer.price, + }) + } + ethjson::spec::builtin::Pricing::Bls12ConstOperations(pricer) => { + Self::Bls12ConstOperations(Bls12ConstOperations { + price: pricer.price, + }) + } + ethjson::spec::builtin::Pricing::Bls12Pairing(pricer) => { + Self::Bls12Pairing(Bls12PairingPricer { + price: Bls12PairingPrice { + base: pricer.base, + pair: pricer.pair, + }, + }) + } + ethjson::spec::builtin::Pricing::Bls12G1Multiexp(pricer) => { + Self::Bls12MultiexpG1(Bls12MultiexpPricerG1 { + base_price: Bls12ConstOperations { price: pricer.base }, + _marker: std::marker::PhantomData, + }) + } + ethjson::spec::builtin::Pricing::Bls12G2Multiexp(pricer) => { + Self::Bls12MultiexpG2(Bls12MultiexpPricerG2 { + base_price: Bls12ConstOperations { price: pricer.base }, + _marker: std::marker::PhantomData, + }) + } + } + } +} + +/// Ethereum builtins: +enum EthereumBuiltin { + /// The identity function + Identity(Identity), + /// ec recovery + EcRecover(EcRecover), + /// sha256 + Sha256(Sha256), + /// ripemd160 + Ripemd160(Ripemd160), + /// modexp (EIP 198) + Modexp(Modexp), + /// alt_bn128_add + Bn128Add(Bn128Add), + /// alt_bn128_mul + Bn128Mul(Bn128Mul), + /// alt_bn128_pairing + Bn128Pairing(Bn128Pairing), + /// blake2_f (The Blake2 compression function F, EIP-152) + Blake2F(Blake2F), + /// bls12_381 addition in g1 + Bls12G1Add(Bls12G1Add), + /// bls12_381 multiplication in g1 + Bls12G1Mul(Bls12G1Mul), + /// bls12_381 multiexponentiation in g1 + Bls12G1MultiExp(Bls12G1MultiExp), + /// bls12_381 addition in g2 + Bls12G2Add(Bls12G2Add), + /// bls12_381 multiplication in g2 + Bls12G2Mul(Bls12G2Mul), + /// bls12_381 multiexponentiation in g2 + Bls12G2MultiExp(Bls12G2MultiExp), + /// bls12_381 pairing + Bls12Pairing(Bls12Pairing), + /// bls12_381 fp to g1 mapping + Bls12MapFpToG1(Bls12MapFpToG1), + /// bls12_381 fp2 to g2 mapping + Bls12MapFp2ToG2(Bls12MapFp2ToG2), +} + +impl FromStr for EthereumBuiltin { + type Err = (); + + fn from_str(name: &str) -> Result { + match name { + "identity" => Ok(Self::Identity(Identity)), + "ecrecover" => Ok(Self::EcRecover(EcRecover)), + "sha256" => Ok(Self::Sha256(Sha256)), + "ripemd160" => Ok(Self::Ripemd160(Ripemd160)), + "modexp" => Ok(Self::Modexp(Modexp)), + "alt_bn128_add" => Ok(Self::Bn128Add(Bn128Add)), + "alt_bn128_mul" => Ok(Self::Bn128Mul(Bn128Mul)), + "alt_bn128_pairing" => Ok(Self::Bn128Pairing(Bn128Pairing)), + "blake2_f" => Ok(Self::Blake2F(Blake2F)), + "bls12_381_g1_add" => Ok(Self::Bls12G1Add(Bls12G1Add)), + "bls12_381_g1_mul" => Ok(Self::Bls12G1Mul(Bls12G1Mul)), + "bls12_381_g1_multiexp" => Ok(Self::Bls12G1MultiExp(Bls12G1MultiExp)), + "bls12_381_g2_add" => Ok(Self::Bls12G2Add(Bls12G2Add)), + "bls12_381_g2_mul" => Ok(Self::Bls12G2Mul(Bls12G2Mul)), + "bls12_381_g2_multiexp" => Ok(Self::Bls12G2MultiExp(Bls12G2MultiExp)), + "bls12_381_pairing" => Ok(Self::Bls12Pairing(Bls12Pairing)), + "bls12_381_fp_to_g1" => Ok(Self::Bls12MapFpToG1(Bls12MapFpToG1)), + "bls12_381_fp2_to_g2" => Ok(Self::Bls12MapFp2ToG2(Bls12MapFp2ToG2)), + _ => Err(()), + } + } +} + +impl Implementation for EthereumBuiltin { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + match self { + Self::Identity(inner) => inner.execute(input, output), + Self::EcRecover(inner) => inner.execute(input, output), + Self::Sha256(inner) => inner.execute(input, output), + Self::Ripemd160(inner) => inner.execute(input, output), + Self::Modexp(inner) => inner.execute(input, output), + Self::Bn128Add(inner) => inner.execute(input, output), + Self::Bn128Mul(inner) => inner.execute(input, output), + Self::Bn128Pairing(inner) => inner.execute(input, output), + Self::Blake2F(inner) => inner.execute(input, output), + Self::Bls12G1Add(inner) => inner.execute(input, output), + Self::Bls12G1Mul(inner) => inner.execute(input, output), + Self::Bls12G1MultiExp(inner) => inner.execute(input, output), + Self::Bls12G2Add(inner) => inner.execute(input, output), + Self::Bls12G2Mul(inner) => inner.execute(input, output), + Self::Bls12G2MultiExp(inner) => inner.execute(input, output), + Self::Bls12Pairing(inner) => inner.execute(input, output), + Self::Bls12MapFpToG1(inner) => inner.execute(input, output), + Self::Bls12MapFp2ToG2(inner) => inner.execute(input, output), + } + } +} + +#[derive(Debug)] +/// The identity builtin +pub struct Identity; + +#[derive(Debug)] +/// The EC Recover builtin +pub struct EcRecover; + +#[derive(Debug)] +/// The Sha256 builtin +pub struct Sha256; + +#[derive(Debug)] +/// The Ripemd160 builtin +pub struct Ripemd160; + +#[derive(Debug)] +/// The Modexp builtin +pub struct Modexp; + +#[derive(Debug)] +/// The Bn128Add builtin +pub struct Bn128Add; + +#[derive(Debug)] +/// The Bn128Mul builtin +pub struct Bn128Mul; + +#[derive(Debug)] +/// The Bn128Pairing builtin +pub struct Bn128Pairing; + +#[derive(Debug)] +/// The Blake2F builtin +pub struct Blake2F; + +#[derive(Debug)] +/// The Bls12G1Add builtin. +pub struct Bls12G1Add; + +#[derive(Debug)] +/// The Bls12G1Mul builtin. +pub struct Bls12G1Mul; + +#[derive(Debug)] +/// The Bls12G1MultiExp builtin. +pub struct Bls12G1MultiExp; + +#[derive(Debug)] +/// The Bls12G2Add builtin. +pub struct Bls12G2Add; + +#[derive(Debug)] +/// The Bls12G2Mul builtin. +pub struct Bls12G2Mul; + +#[derive(Debug)] +/// The Bls12G2MultiExp builtin. +pub struct Bls12G2MultiExp; + +#[derive(Debug)] +/// The Bls12Pairing builtin. +pub struct Bls12Pairing; + +#[derive(Debug)] +/// The Bls12MapFpToG1 builtin. +pub struct Bls12MapFpToG1; + +#[derive(Debug)] +/// The Bls12MapFp2ToG2 builtin. +pub struct Bls12MapFp2ToG2; + +impl Implementation for Identity { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + output.write(0, input); + Ok(()) + } +} + +impl Implementation for EcRecover { + fn execute(&self, i: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + let len = min(i.len(), 128); + + let mut input = [0; 128]; + input[..len].copy_from_slice(&i[..len]); + + let mut hash = [0; 32]; + hash.copy_from_slice(&input[0..32]); + + let v = H256::from_slice(&input[32..64]); + let bit = match v[31] { + 27 | 28 if v.0[..31] == [0; 31] => v[31] - 27, + _ => { + return Ok(()); + } + }; + let mut signature = [0; 64]; + signature[..64].copy_from_slice(&input[64..128]); + let signature = libsecp256k1::Signature::parse_standard(&signature); + if bit <= 1 { + if let Ok(signature) = signature { + // The builtin allows/requires all-zero messages to be valid to + // recover the public key. Use of such messages is disallowed in + // `rust-secp256k1` and this is a workaround for that. It is not an + // openethereum-level error to fail here; instead we return all + // zeroes and let the caller interpret that outcome. + let message = libsecp256k1::Message::parse(&hash); + let recovery_id = libsecp256k1::RecoveryId::parse(bit); + if let Ok(recovery_id) = recovery_id { + if let Ok(p) = libsecp256k1::recover(&message, &signature, &recovery_id) { + let r = keccak(&p.serialize()[1..65]); + output.write(0, &[0; 12]); + output.write(12, &r.as_bytes()[12..]); + } + } + } + } + + Ok(()) + } +} + +impl Implementation for Sha256 { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + let mut hasher = sha2::Sha256::new(); + hasher.update(input); + output.write(0, &hasher.finalize()); + Ok(()) + } +} + +impl Implementation for Blake2F { + /// Format of `input`: + /// [4 bytes for rounds][64 bytes for h][128 bytes for m][8 bytes for t_0][8 bytes for t_1][1 byte for f] + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + const BLAKE2_F_ARG_LEN: usize = 213; + const PROOF: &str = "Checked the length of the input above; qed"; + + if input.len() != BLAKE2_F_ARG_LEN { + trace!(target: "builtin", "input length for Blake2 F precompile should be exactly 213 bytes, was {}", input.len()); + return Err("input length for Blake2 F precompile should be exactly 213 bytes"); + } + + let mut cursor = Cursor::new(input); + let rounds = cursor.read_u32::().expect(PROOF); + + // state vector, h + let mut h = [0u64; 8]; + for state_word in &mut h { + *state_word = cursor.read_u64::().expect(PROOF); + } + + // message block vector, m + let mut m = [0u64; 16]; + for msg_word in &mut m { + *msg_word = cursor.read_u64::().expect(PROOF); + } + + // 2w-bit offset counter, t + let t = [ + cursor.read_u64::().expect(PROOF), + cursor.read_u64::().expect(PROOF), + ]; + + // final block indicator flag, "f" + let f = match input.last() { + Some(1) => true, + Some(0) => false, + _ => { + trace!(target: "builtin", "incorrect final block indicator flag, was: {:?}", input.last()); + return Err("incorrect final block indicator flag"); + } + }; + + compress(&mut h, m, t, f, rounds as usize); + + let mut output_buf = [0u8; u64::BITS as usize]; + for (i, state_word) in h.iter().enumerate() { + output_buf[i * 8..(i + 1) * 8].copy_from_slice(&state_word.to_le_bytes()); + } + output.write(0, &output_buf[..]); + Ok(()) + } +} + +impl Implementation for Ripemd160 { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + output.write(0, &[0; 12][..]); + output.write(12, &ripemd::Ripemd160::digest(input)); + Ok(()) + } +} + +impl Implementation for Modexp { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + let mut reader = input.chain(io::repeat(0)); + let mut buf = [0; 32]; + + // read lengths as usize. + // ignoring the first 24 bytes might technically lead us to fall out of consensus, + // but so would running out of addressable memory! + let mut read_len = |reader: &mut io::Chain<&[u8], io::Repeat>| { + reader + .read_exact(&mut buf[..]) + .expect("reading from zero-extended memory cannot fail; qed"); + let mut len_bytes = [0u8; 8]; + len_bytes.copy_from_slice(&buf[24..]); + u64::from_be_bytes(len_bytes) as usize + }; + + let base_len = read_len(&mut reader); + let exp_len = read_len(&mut reader); + let mod_len = read_len(&mut reader); + + // Gas formula allows arbitrary large exp_len when base and modulus are empty, so we need to handle empty base first. + let r = if base_len == 0 && mod_len == 0 { + BigUint::zero() + } else { + // read the numbers themselves. + let mut buf = vec![0; max(mod_len, max(base_len, exp_len))]; + let mut read_num = |reader: &mut io::Chain<&[u8], io::Repeat>, len: usize| { + reader + .read_exact(&mut buf[..len]) + .expect("reading from zero-extended memory cannot fail; qed"); + BigUint::from_bytes_be(&buf[..len]) + }; + + let base = read_num(&mut reader, base_len); + let exponent = read_num(&mut reader, exp_len); + let modulus = read_num(&mut reader, mod_len); + + if modulus.is_zero() || modulus.is_one() { + BigUint::zero() + } else { + base.modpow(&exponent, &modulus) + } + }; + + // write output to given memory, left padded and same length as the modulus. + let bytes = r.to_bytes_be(); + + // always true except in the case of zero-length modulus, which leads to + // output of length and value 1. + if bytes.len() <= mod_len { + let res_start = mod_len - bytes.len(); + output.write(res_start, &bytes); + } + + Ok(()) + } +} + +fn read_fr(reader: &mut io::Chain<&[u8], io::Repeat>) -> Result { + let mut buf = [0u8; 32]; + + reader + .read_exact(&mut buf[..]) + .expect("reading from zero-extended memory cannot fail; qed"); + bn::Fr::from_slice(&buf[0..32]).map_err(|_| "Invalid field element") +} + +fn read_point(reader: &mut io::Chain<&[u8], io::Repeat>) -> Result { + use bn::{AffineG1, Fq, Group, G1}; + + let mut buf = [0u8; 32]; + + reader + .read_exact(&mut buf[..]) + .expect("reading from zero-extended memory cannot fail; qed"); + let px = Fq::from_slice(&buf[0..32]).map_err(|_| "Invalid point x coordinate")?; + + reader + .read_exact(&mut buf[..]) + .expect("reading from zero-extended memory cannot fail; qed"); + let py = Fq::from_slice(&buf[0..32]).map_err(|_| "Invalid point y coordinate")?; + Ok(if px == Fq::zero() && py == Fq::zero() { + G1::zero() + } else { + AffineG1::new(px, py) + .map_err(|_| "Invalid curve point")? + .into() + }) +} + +impl Implementation for Bn128Add { + // Can fail if any of the 2 points does not belong the bn128 curve + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + use bn::AffineG1; + + let mut padded_input = input.chain(io::repeat(0)); + let p1 = read_point(&mut padded_input)?; + let p2 = read_point(&mut padded_input)?; + + let mut write_buf = [0u8; 64]; + if let Some(sum) = AffineG1::from_jacobian(p1 + p2) { + // point not at infinity + sum.x() + .to_big_endian(&mut write_buf[0..32]) + .expect("Cannot fail since 0..32 is 32-byte length"); + sum.y() + .to_big_endian(&mut write_buf[32..64]) + .expect("Cannot fail since 32..64 is 32-byte length"); + } + output.write(0, &write_buf); + + Ok(()) + } +} + +impl Implementation for Bn128Mul { + // Can fail if first paramter (bn128 curve point) does not actually belong to the curve + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + use bn::AffineG1; + + let mut padded_input = input.chain(io::repeat(0)); + let p = read_point(&mut padded_input)?; + let fr = read_fr(&mut padded_input)?; + + let mut write_buf = [0u8; 64]; + if let Some(sum) = AffineG1::from_jacobian(p * fr) { + // point not at infinity + sum.x() + .to_big_endian(&mut write_buf[0..32]) + .expect("Cannot fail since 0..32 is 32-byte length"); + sum.y() + .to_big_endian(&mut write_buf[32..64]) + .expect("Cannot fail since 32..64 is 32-byte length"); + } + output.write(0, &write_buf); + Ok(()) + } +} + +impl Implementation for Bn128Pairing { + /// Can fail if: + /// - input length is not a multiple of 192 + /// - any of odd points does not belong to bn128 curve + /// - any of even points does not belong to the twisted bn128 curve over the field F_p^2 = F_p[i] / (i^2 + 1) + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + if input.len() % 192 != 0 { + return Err("Invalid input length, must be multiple of 192 (3 * (32*2))"); + } + + if let Err(err) = self.execute_with_error(input, output) { + trace!(target: "builtin", "Pairing error: {:?}", err); + return Err(err); + } + Ok(()) + } +} + +impl Bn128Pairing { + fn execute_with_error(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + use bn::{pairing_batch, AffineG1, AffineG2, Fq, Fq2, Group, Gt, G1, G2}; + + let ret_val = if input.is_empty() { + U256::one() + } else { + // (a, b_a, b_b - each 64-byte affine coordinates) + let elements = input.len() / 192; + let mut vals = Vec::new(); + for idx in 0..elements { + let a_x = Fq::from_slice(&input[idx * 192..idx * 192 + 32]) + .map_err(|_| "Invalid a argument x coordinate")?; + + let a_y = Fq::from_slice(&input[idx * 192 + 32..idx * 192 + 64]) + .map_err(|_| "Invalid a argument y coordinate")?; + + let b_a_y = Fq::from_slice(&input[idx * 192 + 64..idx * 192 + 96]) + .map_err(|_| "Invalid b argument imaginary coeff x coordinate")?; + + let b_a_x = Fq::from_slice(&input[idx * 192 + 96..idx * 192 + 128]) + .map_err(|_| "Invalid b argument imaginary coeff y coordinate")?; + + let b_b_y = Fq::from_slice(&input[idx * 192 + 128..idx * 192 + 160]) + .map_err(|_| "Invalid b argument real coeff x coordinate")?; + + let b_b_x = Fq::from_slice(&input[idx * 192 + 160..idx * 192 + 192]) + .map_err(|_| "Invalid b argument real coeff y coordinate")?; + + let b_a = Fq2::new(b_a_x, b_a_y); + let b_b = Fq2::new(b_b_x, b_b_y); + let b = if b_a.is_zero() && b_b.is_zero() { + G2::zero() + } else { + G2::from( + AffineG2::new(b_a, b_b).map_err(|_| "Invalid b argument - not on curve")?, + ) + }; + let a = if a_x.is_zero() && a_y.is_zero() { + G1::zero() + } else { + G1::from( + AffineG1::new(a_x, a_y).map_err(|_| "Invalid a argument - not on curve")?, + ) + }; + vals.push((a, b)); + } + + let mul = pairing_batch(&vals); + + if mul == Gt::one() { + U256::one() + } else { + U256::zero() + } + }; + + let mut buf = [0u8; 32]; + ret_val.to_big_endian(&mut buf); + output.write(0, &buf); + + Ok(()) + } +} + +impl Implementation for Bls12G1Add { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + let result = EIP2537Executor::g1_add(input); + + match result { + Ok(result_bytes) => { + output.write(0, &result_bytes[..]); + + Ok(()) + } + Err(e) => { + trace!(target: "builtin", "Bls12G1Add error: {:?}", e); + + Err("Bls12G1Add error") + } + } + } +} + +impl Implementation for Bls12G1Mul { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + let result = EIP2537Executor::g1_mul(input); + + match result { + Ok(result_bytes) => { + output.write(0, &result_bytes[..]); + + Ok(()) + } + Err(e) => { + trace!(target: "builtin", "Bls12G1Mul error: {:?}", e); + + Err("Bls12G1Mul error") + } + } + } +} + +impl Implementation for Bls12G1MultiExp { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + let result = EIP2537Executor::g1_multiexp(input); + + match result { + Ok(result_bytes) => { + output.write(0, &result_bytes[..]); + + Ok(()) + } + Err(e) => { + trace!(target: "builtin", "Bls12G1MultiExp error: {:?}", e); + + Err("Bls12G1MultiExp error") + } + } + } +} + +impl Implementation for Bls12G2Add { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + let result = EIP2537Executor::g2_add(input); + + match result { + Ok(result_bytes) => { + output.write(0, &result_bytes[..]); + + Ok(()) + } + Err(e) => { + trace!(target: "builtin", "Bls12G2Add error: {:?}", e); + + Err("Bls12G2Add error") + } + } + } +} + +impl Implementation for Bls12G2Mul { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + let result = EIP2537Executor::g2_mul(input); + + match result { + Ok(result_bytes) => { + output.write(0, &result_bytes[..]); + + Ok(()) + } + Err(e) => { + trace!(target: "builtin", "Bls12G2Mul error: {:?}", e); + + Err("Bls12G2Mul error") + } + } + } +} + +impl Implementation for Bls12G2MultiExp { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + let result = EIP2537Executor::g2_multiexp(input); + + match result { + Ok(result_bytes) => { + output.write(0, &result_bytes[..]); + + Ok(()) + } + Err(e) => { + trace!(target: "builtin", "Bls12G2MultiExp error: {:?}", e); + + Err("Bls12G2MultiExp error") + } + } + } +} + +impl Implementation for Bls12Pairing { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + let result = EIP2537Executor::pair(input); + + match result { + Ok(result_bytes) => { + output.write(0, &result_bytes[..]); + + Ok(()) + } + Err(e) => { + trace!(target: "builtin", "Bls12Pairing error: {:?}", e); + + Err("Bls12Pairing error") + } + } + } +} + +impl Implementation for Bls12MapFpToG1 { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + let result = EIP2537Executor::map_fp_to_g1(input); + + match result { + Ok(result_bytes) => { + output.write(0, &result_bytes[..]); + + Ok(()) + } + Err(e) => { + trace!(target: "builtin", "Bls12MapFpToG1 error: {:?}", e); + + Err("Bls12MapFpToG1 error") + } + } + } +} + +impl Implementation for Bls12MapFp2ToG2 { + fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { + let result = EIP2537Executor::map_fp2_to_g2(input); + + match result { + Ok(result_bytes) => { + output.write(0, &result_bytes[..]); + + Ok(()) + } + Err(e) => { + trace!(target: "builtin", "Bls12MapFp2ToG2 error: {:?}", e); + + Err("Bls12MapFp2ToG2 error") + } + } + } +} + +#[cfg(test)] +mod tests { + use super::{ + Bls12ConstOperations, Bls12PairingPrice, Bls12PairingPricer, Builtin, EthereumBuiltin, + FromStr, Implementation, Linear, ModexpPricer, Pricing, + }; + use ethereum_types::U256; + use ethjson::spec::builtin::{ + AltBn128Pairing as JsonAltBn128PairingPricing, Builtin as JsonBuiltin, + Linear as JsonLinearPricing, Pricing as JsonPricing, PricingAt, + }; + use hex_literal::hex; + use maplit::btreemap; + use parity_bytes::BytesRef; + use std::convert::TryFrom; + + #[test] + fn blake2f_cost() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Blake2F(123)], + native: EthereumBuiltin::from_str("blake2_f").unwrap(), + }; + // 5 rounds + let input = hex!("0000000548c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let mut output = [0u8; 64]; + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .unwrap(); + + assert_eq!(f.cost(&input[..], 0), U256::from(123 * 5)); + } + + #[test] + fn blake2f_cost_on_invalid_length() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Blake2F(123)], + native: EthereumBuiltin::from_str("blake2_f").expect("known builtin"), + }; + // invalid input (too short) + let input = hex!("00"); + + assert_eq!(f.cost(&input[..], 0), U256::from(0)); + } + + #[test] + fn blake2_f_is_err_on_invalid_length() { + let blake2 = EthereumBuiltin::from_str("blake2_f").unwrap(); + // Test vector 1 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-1 + let input = hex!("00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let mut out = [0u8; 64]; + + let result = blake2.execute(&input[..], &mut BytesRef::Fixed(&mut out[..])); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + "input length for Blake2 F precompile should be exactly 213 bytes" + ); + } + + #[test] + fn blake2_f_is_err_on_invalid_length_2() { + let blake2 = EthereumBuiltin::from_str("blake2_f").unwrap(); + // Test vector 2 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-2 + let input = hex!("000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let mut out = [0u8; 64]; + + let result = blake2.execute(&input[..], &mut BytesRef::Fixed(&mut out[..])); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + "input length for Blake2 F precompile should be exactly 213 bytes" + ); + } + + #[test] + fn blake2_f_is_err_on_bad_finalization_flag() { + let blake2 = EthereumBuiltin::from_str("blake2_f").unwrap(); + // Test vector 3 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-3 + let input = hex!("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002"); + let mut out = [0u8; 64]; + + let result = blake2.execute(&input[..], &mut BytesRef::Fixed(&mut out[..])); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), "incorrect final block indicator flag"); + } + + #[test] + fn blake2_f_zero_rounds_is_ok_test_vector_4() { + let blake2 = EthereumBuiltin::from_str("blake2_f").unwrap(); + // Test vector 4 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-4 + let input = hex!("0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let expected = hex!("08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b"); + let mut output = [0u8; 64]; + blake2 + .execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .unwrap(); + assert_eq!(&output[..], &expected[..]); + } + + #[test] + fn blake2_f_test_vector_5() { + let blake2 = EthereumBuiltin::from_str("blake2_f").unwrap(); + // Test vector 5 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-5 + let input = hex!("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let expected = hex!("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"); + let mut out = [0u8; 64]; + blake2 + .execute(&input[..], &mut BytesRef::Fixed(&mut out[..])) + .unwrap(); + assert_eq!(&out[..], &expected[..]); + } + + #[test] + fn blake2_f_test_vector_6() { + let blake2 = EthereumBuiltin::from_str("blake2_f").unwrap(); + // Test vector 6 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-6 + let input = hex!("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000"); + let expected = hex!("75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735"); + let mut out = [0u8; 64]; + blake2 + .execute(&input[..], &mut BytesRef::Fixed(&mut out[..])) + .unwrap(); + assert_eq!(&out[..], &expected[..]); + } + + #[test] + fn blake2_f_test_vector_7() { + let blake2 = EthereumBuiltin::from_str("blake2_f").unwrap(); + // Test vector 7 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-7 + let input = hex!("0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let expected = hex!("b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421"); + let mut out = [0u8; 64]; + blake2 + .execute(&input[..], &mut BytesRef::Fixed(&mut out[..])) + .unwrap(); + assert_eq!(&out[..], &expected[..]); + } + + #[ignore] + #[test] + fn blake2_f_test_vector_8() { + let blake2 = EthereumBuiltin::from_str("blake2_f").unwrap(); + // Test vector 8 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-8 + // Note this test is slow, 4294967295/0xffffffff rounds take a while. + let input = hex!("ffffffff48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let expected = hex!("fc59093aafa9ab43daae0e914c57635c5402d8e3d2130eb9b3cc181de7f0ecf9b22bf99a7815ce16419e200e01846e6b5df8cc7703041bbceb571de6631d2615"); + let mut out = [0u8; 64]; + blake2 + .execute(&input[..], &mut BytesRef::Fixed(&mut out[..])) + .unwrap(); + assert_eq!(&out[..], &expected[..]); + } + + #[test] + fn identity() { + let f = EthereumBuiltin::from_str("identity").unwrap(); + let i = [0u8, 1, 2, 3]; + + let mut o2 = [255u8; 2]; + f.execute(&i[..], &mut BytesRef::Fixed(&mut o2[..])) + .expect("Builtin should not fail"); + assert_eq!(i[0..2], o2); + + let mut o4 = [255u8; 4]; + f.execute(&i[..], &mut BytesRef::Fixed(&mut o4[..])) + .expect("Builtin should not fail"); + assert_eq!(i, o4); + + let mut o8 = [255u8; 8]; + f.execute(&i[..], &mut BytesRef::Fixed(&mut o8[..])) + .expect("Builtin should not fail"); + assert_eq!(i, o8[..4]); + assert_eq!([255u8; 4], o8[4..]); + } + + #[test] + fn sha256() { + let f = EthereumBuiltin::from_str("sha256").unwrap(); + let i = [0u8; 0]; + + let mut o = [255u8; 32]; + f.execute(&i[..], &mut BytesRef::Fixed(&mut o[..])) + .expect("Builtin should not fail"); + assert_eq!( + &o[..], + hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + ); + + let mut o8 = [255u8; 8]; + f.execute(&i[..], &mut BytesRef::Fixed(&mut o8[..])) + .expect("Builtin should not fail"); + assert_eq!(&o8[..], hex!("e3b0c44298fc1c14")); + + let mut o34 = [255u8; 34]; + f.execute(&i[..], &mut BytesRef::Fixed(&mut o34[..])) + .expect("Builtin should not fail"); + assert_eq!( + &o34[..], + &hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855ffff")[..] + ); + + let mut ov = vec![]; + f.execute(&i[..], &mut BytesRef::Flexible(&mut ov)) + .expect("Builtin should not fail"); + assert_eq!( + &ov[..], + &hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")[..] + ); + } + + #[test] + fn ripemd160() { + let f = EthereumBuiltin::from_str("ripemd160").unwrap(); + let i = [0u8; 0]; + + let mut o = [255u8; 32]; + f.execute(&i[..], &mut BytesRef::Fixed(&mut o[..])) + .expect("Builtin should not fail"); + assert_eq!( + &o[..], + &hex!("0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31")[..] + ); + + let mut o8 = [255u8; 8]; + f.execute(&i[..], &mut BytesRef::Fixed(&mut o8[..])) + .expect("Builtin should not fail"); + assert_eq!(&o8[..], &hex!("0000000000000000")[..]); + + let mut o34 = [255u8; 34]; + f.execute(&i[..], &mut BytesRef::Fixed(&mut o34[..])) + .expect("Builtin should not fail"); + assert_eq!( + &o34[..], + &hex!("0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31ffff")[..] + ); + } + + #[test] + fn ecrecover() { + let f = EthereumBuiltin::from_str("ecrecover").unwrap(); + + let i = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e03"); + + let mut o = [255u8; 32]; + f.execute(&i[..], &mut BytesRef::Fixed(&mut o[..])) + .expect("Builtin should not fail"); + assert_eq!( + &o[..], + &hex!("000000000000000000000000c08b5542d177ac6686946920409741463a15dddb")[..] + ); + + let mut o8 = [255u8; 8]; + f.execute(&i[..], &mut BytesRef::Fixed(&mut o8[..])) + .expect("Builtin should not fail"); + assert_eq!(&o8[..], &hex!("0000000000000000")[..]); + + let mut o34 = [255u8; 34]; + f.execute(&i[..], &mut BytesRef::Fixed(&mut o34[..])) + .expect("Builtin should not fail"); + assert_eq!( + &o34[..], + &hex!("000000000000000000000000c08b5542d177ac6686946920409741463a15dddbffff")[..] + ); + + let i_bad = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001a650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e03"); + let mut o = [255u8; 32]; + f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..])) + .expect("Builtin should not fail"); + assert_eq!( + &o[..], + &hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..] + ); + + let i_bad = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b0000000000000000000000000000000000000000000000000000000000000000"); + let mut o = [255u8; 32]; + f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..])) + .expect("Builtin should not fail"); + assert_eq!( + &o[..], + &hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..] + ); + + let i_bad = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b"); + let mut o = [255u8; 32]; + f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..])) + .expect("Builtin should not fail"); + assert_eq!( + &o[..], + &hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..] + ); + + let i_bad = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000001b"); + let mut o = [255u8; 32]; + f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..])) + .expect("Builtin should not fail"); + assert_eq!( + &o[..], + &hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..] + ); + + let i_bad = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + let mut o = [255u8; 32]; + f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..])) + .expect("Builtin should not fail"); + assert_eq!( + &o[..], + &hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..] + ); + + // TODO: Should this (corrupted version of the above) fail rather than returning some address? + /* let i_bad = FromHex::from_hex("48173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e03").unwrap(); + let mut o = [255u8; 32]; + f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..])); + assert_eq!(&o[..], &(FromHex::from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap())[..]);*/ + } + + #[test] + fn modexp() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Modexp(ModexpPricer { divisor: 20, is_eip_2565: false })], + native: EthereumBuiltin::from_str("modexp").unwrap(), + }; + + // test for potential gas cost multiplication overflow + { + let input = hex!("0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000003b27bafd00000000000000000000000000000000000000000000000000000000503c8ac3"); + let expected_cost = U256::max_value(); + assert_eq!(f.cost(&input[..], 0), expected_cost); + } + + // test for potential exp len overflow + { + let input = hex!( + " + 00000000000000000000000000000000000000000000000000000000000000ff + 2a1e530000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000" + ); + + let mut output = vec![0u8; 32]; + let expected = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + let expected_cost = U256::max_value(); + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should fail"); + assert_eq!(output, expected); + assert_eq!(f.cost(&input[..], 0), expected_cost); + } + + // fermat's little theorem example. + { + let input = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + 03 + fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e + fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" + ); + + let mut output = vec![0u8; 32]; + let expected = hex!("0000000000000000000000000000000000000000000000000000000000000001"); + let expected_cost = 13056; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(output, expected); + assert_eq!(f.cost(&input[..], 0), expected_cost.into()); + } + + // second example from EIP: zero base. + { + let input = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e + fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" + ); + + let mut output = vec![0u8; 32]; + let expected = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + let expected_cost = 13056; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(output, expected); + assert_eq!(f.cost(&input[..], 0), expected_cost.into()); + } + + // another example from EIP: zero-padding + { + let input = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000000000000000000000000000000000000000000020 + 03 + ffff + 80" + ); + + let mut output = vec![0u8; 32]; + let expected = hex!("3b01b01ac41f2d6e917c6d6a221ce793802469026d9ab7578fa2e79e4da6aaab"); + let expected_cost = 768; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(output, expected); + assert_eq!(f.cost(&input[..], 0), expected_cost.into()); + } + + // zero-length modulus. + { + let input = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000000000000000000000000000000000000000000000 + 03 + ffff" + ); + + let mut output = vec![]; + let expected_cost = 0; + + f.execute(&input[..], &mut BytesRef::Flexible(&mut output)) + .expect("Builtin should not fail"); + assert_eq!(output.len(), 0); // shouldn't have written any output. + assert_eq!(f.cost(&input[..], 0), expected_cost.into()); + } + } + + #[test] + fn bn128_add() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Linear(Linear { base: 0, word: 0 })], + native: EthereumBuiltin::from_str("alt_bn128_add").unwrap(), + }; + + // zero-points additions + { + let input = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000" + ); + + let mut output = vec![0u8; 64]; + let expected = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000" + ); + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(output, &expected[..]); + } + + // no input, should not fail + { + let mut empty = [0u8; 0]; + let input = BytesRef::Fixed(&mut empty); + + let mut output = vec![0u8; 64]; + let expected = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000" + ); + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(output, &expected[..]); + } + + // should fail - point not on curve + { + let input = hex!( + " + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111" + ); + + let mut output = [0u8; 64]; + + let res = f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])); + assert!(res.is_err(), "There should be built-in error here"); + } + } + + #[test] + fn bn128_mul() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Linear(Linear { base: 0, word: 0 })], + native: EthereumBuiltin::from_str("alt_bn128_mul").unwrap(), + }; + + // zero-point multiplication + { + let input = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0200000000000000000000000000000000000000000000000000000000000000" + ); + + let mut output = vec![0u8; 64]; + let expected = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000" + ); + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(output, &expected[..]); + } + + // should fail - point not on curve + { + let input = hex!( + " + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 0f00000000000000000000000000000000000000000000000000000000000000" + ); + + let mut output = [0u8; 64]; + + let res = f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])); + assert!(res.is_err(), "There should be built-in error here"); + } + } + + fn builtin_pairing() -> Builtin { + Builtin { + pricer: btreemap![0 => Pricing::Linear(Linear { base: 0, word: 0 })], + native: EthereumBuiltin::from_str("alt_bn128_pairing").unwrap(), + } + } + + fn empty_test(f: Builtin, expected: Vec) { + let mut empty = [0u8; 0]; + let input = BytesRef::Fixed(&mut empty); + + let mut output = vec![0u8; expected.len()]; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(output, expected); + } + + fn error_test(f: Builtin, input: &[u8], msg_contains: Option<&str>) { + let mut output = [0u8; 64]; + let res = f.execute(input, &mut BytesRef::Fixed(&mut output[..])); + msg_contains.map_or_else( + || { + assert!(res.is_err(), "There should be built-in error here"); + }, + |msg| { + if let Err(e) = res { + if !e.contains(msg) { + panic!( + "There should be error containing '{}' here, but got: '{}'", + msg, e + ); + } + } + }, + ) + } + + #[test] + fn bn128_pairing_empty() { + // should not fail, because empty input is a valid input of 0 elements + empty_test( + builtin_pairing(), + hex!("0000000000000000000000000000000000000000000000000000000000000001").to_vec(), + ); + } + + #[test] + fn bn128_pairing_notcurve() { + // should fail - point not on curve + error_test( + builtin_pairing(), + &hex!( + " + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111" + ), + Some("not on curve"), + ); + } + + #[test] + fn bn128_pairing_fragmented() { + // should fail - input length is invalid + error_test( + builtin_pairing(), + &hex!( + " + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 111111111111111111111111111111" + ), + Some("Invalid input length"), + ); + } + + #[test] + #[should_panic] + fn from_unknown_linear() { + let _ = EthereumBuiltin::from_str("foo").unwrap(); + } + + #[test] + fn is_active() { + let pricer = Pricing::Linear(Linear { base: 10, word: 20 }); + let b = Builtin { + pricer: btreemap![100_000 => pricer], + native: EthereumBuiltin::from_str("identity").unwrap(), + }; + + assert!(!b.is_active(99_999)); + assert!(b.is_active(100_000)); + assert!(b.is_active(100_001)); + } + + #[test] + fn from_named_linear() { + let pricer = Pricing::Linear(Linear { base: 10, word: 20 }); + let b = Builtin { + pricer: btreemap![0 => pricer], + native: EthereumBuiltin::from_str("identity").unwrap(), + }; + + assert_eq!(b.cost(&[0; 0], 0), U256::from(10)); + assert_eq!(b.cost(&[0; 1], 0), U256::from(30)); + assert_eq!(b.cost(&[0; 32], 0), U256::from(30)); + assert_eq!(b.cost(&[0; 33], 0), U256::from(50)); + + let i = [0u8, 1, 2, 3]; + let mut o = [255u8; 4]; + b.execute(&i[..], &mut BytesRef::Fixed(&mut o[..])) + .expect("Builtin should not fail"); + assert_eq!(i, o); + } + + #[test] + fn from_json() { + let b = Builtin::try_from(ethjson::spec::Builtin { + name: "identity".to_owned(), + pricing: btreemap![ + 0 => PricingAt { + info: None, + price: JsonPricing::Linear(JsonLinearPricing { base: 10, word: 20 }) + } + ], + }) + .expect("known builtin"); + + assert_eq!(b.cost(&[0; 0], 0), U256::from(10)); + assert_eq!(b.cost(&[0; 1], 0), U256::from(30)); + assert_eq!(b.cost(&[0; 32], 0), U256::from(30)); + assert_eq!(b.cost(&[0; 33], 0), U256::from(50)); + + let i = [0u8, 1, 2, 3]; + let mut o = [255u8; 4]; + b.execute(&i[..], &mut BytesRef::Fixed(&mut o[..])) + .expect("Builtin should not fail"); + assert_eq!(i, o); + } + + #[test] + fn bn128_pairing_eip1108_transition() { + let b = Builtin::try_from(JsonBuiltin { + name: "alt_bn128_pairing".to_owned(), + pricing: btreemap![ + 10 => PricingAt { + info: None, + price: JsonPricing::AltBn128Pairing(JsonAltBn128PairingPricing { + base: 100_000, + pair: 80_000, + }), + }, + 20 => PricingAt { + info: None, + price: JsonPricing::AltBn128Pairing(JsonAltBn128PairingPricing { + base: 45_000, + pair: 34_000, + }), + } + ], + }) + .unwrap(); + + assert_eq!( + b.cost(&[0; 192 * 3], 10), + U256::from(340_000), + "80 000 * 3 + 100 000 == 340 000" + ); + assert_eq!( + b.cost(&[0; 192 * 7], 20), + U256::from(283_000), + "34 000 * 7 + 45 000 == 283 000" + ); + } + + #[test] + fn bn128_add_eip1108_transition() { + let b = Builtin::try_from(JsonBuiltin { + name: "alt_bn128_add".to_owned(), + pricing: btreemap![ + 10 => PricingAt { + info: None, + price: JsonPricing::Linear(JsonLinearPricing { + base: 500, + word: 0, + }), + }, + 20 => PricingAt { + info: None, + price: JsonPricing::Linear(JsonLinearPricing { + base: 150, + word: 0, + }), + } + ], + }) + .unwrap(); + + assert_eq!(b.cost(&[0; 192], 10), U256::from(500)); + assert_eq!( + b.cost(&[0; 10], 20), + U256::from(150), + "after istanbul hardfork gas cost for add should be 150" + ); + } + + #[test] + fn bn128_mul_eip1108_transition() { + let b = Builtin::try_from(JsonBuiltin { + name: "alt_bn128_mul".to_owned(), + pricing: btreemap![ + 10 => PricingAt { + info: None, + price: JsonPricing::Linear(JsonLinearPricing { + base: 40_000, + word: 0, + }), + }, + 20 => PricingAt { + info: None, + price: JsonPricing::Linear(JsonLinearPricing { + base: 6_000, + word: 0, + }), + } + ], + }) + .unwrap(); + + assert_eq!(b.cost(&[0; 192], 10), U256::from(40_000)); + assert_eq!( + b.cost(&[0; 10], 20), + U256::from(6_000), + "after istanbul hardfork gas cost for mul should be 6 000" + ); + } + + #[test] + fn multimap_use_most_recent_on_activate() { + let b = Builtin::try_from(JsonBuiltin { + name: "alt_bn128_mul".to_owned(), + pricing: btreemap![ + 10 => PricingAt { + info: None, + price: JsonPricing::Linear(JsonLinearPricing { + base: 40_000, + word: 0, + }), + }, + 20 => PricingAt { + info: None, + price: JsonPricing::Linear(JsonLinearPricing { + base: 6_000, + word: 0, + }) + }, + 100 => PricingAt { + info: None, + price: JsonPricing::Linear(JsonLinearPricing { + base: 1_337, + word: 0, + }) + } + ], + }) + .unwrap(); + + assert_eq!( + b.cost(&[0; 2], 0), + U256::zero(), + "not activated yet; should be zero" + ); + assert_eq!(b.cost(&[0; 3], 10), U256::from(40_000), "use price #1"); + assert_eq!(b.cost(&[0; 4], 20), U256::from(6_000), "use price #2"); + assert_eq!(b.cost(&[0; 1], 99), U256::from(6_000), "use price #2"); + assert_eq!(b.cost(&[0; 1], 100), U256::from(1_337), "use price #3"); + assert_eq!( + b.cost(&[0; 1], u64::max_value()), + U256::from(1_337), + "use price #3 indefinitely" + ); + } + + #[test] + fn multimap_use_last_with_same_activate_at() { + let b = Builtin::try_from(JsonBuiltin { + name: "alt_bn128_mul".to_owned(), + pricing: btreemap![ + 1 => PricingAt { + info: None, + price: JsonPricing::Linear(JsonLinearPricing { + base: 40_000, + word: 0, + }), + }, + 1 => PricingAt { + info: None, + price: JsonPricing::Linear(JsonLinearPricing { + base: 6_000, + word: 0, + }), + }, + 1 => PricingAt { + info: None, + price: JsonPricing::Linear(JsonLinearPricing { + base: 1_337, + word: 0, + }), + } + ], + }) + .unwrap(); + + assert_eq!(b.cost(&[0; 1], 0), U256::from(0), "not activated yet"); + assert_eq!(b.cost(&[0; 1], 1), U256::from(1_337), "use price #3"); + } + + #[test] + fn bls12_381_g1_add() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Bls12ConstOperations(Bls12ConstOperations{price: 1})], + native: EthereumBuiltin::from_str("bls12_381_g1_add").unwrap(), + }; + + let input = hex!(" + 00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb + 0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed2 + 000000000000000000000000000000000441e7f7f96198e4c23bd5eb16f1a7f045dbc8c53219ab2bcea91d3a027e2dfe659feac64905f8b9add7e4bfc91bec2b + 0000000000000000000000000000000005fc51bb1b40c87cd4292d4b66f8ca5ce4ef9abd2b69d4464b4879064203bda7c9fc3f896a3844ebc713f7bb20951d95 + "); + let expected = hex!(" + 0000000000000000000000000000000016b8ab56b45a9294466809b8e858c1ad15ad0d52cfcb62f8f5753dc94cee1de6efaaebce10701e3ec2ecaa9551024ea + 600000000000000000000000000000000124571eec37c0b1361023188d66ec17c1ec230d31b515e0e81e599ec19e40c8a7c8cdea9735bc3d8b4e37ca7e5dd71f6 + "); + + let mut output = [0u8; 128]; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(&output[..], &expected[..]); + } + + #[test] + fn bls12_381_g1_mul() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Bls12ConstOperations(Bls12ConstOperations{price: 1})], + native: EthereumBuiltin::from_str("bls12_381_g1_mul").unwrap(), + }; + + let input = hex!(" + 000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e + 000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa3 + ac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659 + "); + let expected = hex!(" + 000000000000000000000000000000001227b7021e9d3dc8bcbf5b346fc503f7f8576965769c5e22bb70056eef03c84b8c80290ae9ce20345770290c55549bce + 00000000000000000000000000000000188ddbbfb4ad2d34a8d3dc0ec92b70b63caa73ad7dea0cc9740bac2309b4bb11107912bd086379746e9a9bcd26d4db58 + "); + + let mut output = [0u8; 128]; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(&output[..], &expected[..]); + } + + #[test] + fn bls12_381_g1_multiexp() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Bls12ConstOperations(Bls12ConstOperations{price: 1})], + native: EthereumBuiltin::from_str("bls12_381_g1_multiexp").unwrap(), + }; + let input = hex!(" + 0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f56 + 0000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee + b3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e + 00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb + 0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed2 + 4d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d + 0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee11 + 0000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f + 973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1 + 0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb33 + 0000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4 + 4c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a + 0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca9 + 00000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d3 + 8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89 + 000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce9 + 0000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90 + 787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944 + 000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719 + 000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b + aaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1 + 0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb + 00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a + dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c + 0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246 + 000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795 + bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108 + 0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f48 + 00000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190 + fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672 + 0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d + 0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943 + b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea + 000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d + 000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e + 3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76 + 0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba4 + 0000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b + dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c + 00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d9 + 0000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a5787681 + 7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a + 0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a + 000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f768 + 94c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054 + 00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42 + 000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d + b3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746 + "); + let expected = hex!(" + 000000000000000000000000000000000b370fc4ca67fb0c3c270b1b4c4816ef953cd9f7cf6ad20e88099c40aace9c4bb3f4cd215e5796f65080c69c9f4d2a0f + 0000000000000000000000000000000007203220935ddc0190e2d7a99ec3f9231da550768373f9a5933dffd366f48146f8ea5fe5dee6539d925288083bb5a8f1 + "); + + let mut output = [0u8; 128]; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(&output[..], &expected[..]); + } + + #[test] + fn bls12_381_g2_add() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Bls12ConstOperations(Bls12ConstOperations{price: 1})], + native: EthereumBuiltin::from_str("bls12_381_g2_add").unwrap(), + }; + let input = hex!(" + 00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b + 00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06 + 000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d + 0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2 + 00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f729558 + 0000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf2 + 0000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d + 0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb + "); + let expected = hex!(" + 000000000000000000000000000000000383ab7a17cc57e239e874af3f1aaabba0e64625b848676712f05f56132dbbd1cadfabeb3fe1f461daba3f1720057ddd + 00000000000000000000000000000000096967e9b3747f1b8e344535eaa0c51e70bc77412bfaa2a7ce76f11f570c9febb8f4227316866a416a50436d098e6f9a + 000000000000000000000000000000001079452b7519a7b090d668d54c266335b1cdd1080ed867dd17a2476b11c2617da829bf740e51cb7dfd60d73ed02c0c67 + 00000000000000000000000000000000015fc3a972e05cbd9014882cfe6f2f16d0291c403bf28b05056ac625e4f71dfb1295c85d73145ef554614e6eb2d5bf02 + "); + + let mut output = [0u8; 256]; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(&output[..], &expected[..]); + } + + #[test] + fn bls12_381_g2_mul() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Bls12ConstOperations(Bls12ConstOperations{price: 1})], + native: EthereumBuiltin::from_str("bls12_381_g2_mul").unwrap(), + }; + + let input = hex!(" + 00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829 + 000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d9 + 00000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c0 + 00000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196 + b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea + "); + let expected = hex!(" + 000000000000000000000000000000000b24adeb2ca184c9646cb39f45e0cf8711e10bf308ddae06519562b0af3b43be44c2fcb90622726f7446ed690551d30e + 00000000000000000000000000000000069467c3edc19416067f572c51740ba8e0e7380121ade98e38ce26d907a2bf3a4e82af2bd195b6c3b7c9b29218880531 + 000000000000000000000000000000000eb8c90d0727511be53ffcb6f3b144c07983ed4b76d31ab003e45b37c7bc1066910f5e29f5adad5757af979dd0d8351d + 0000000000000000000000000000000004760f8d814189dcd893949797a3c4f56f2b60964bba3a4fc741e7ead05eb886787b2502fc64b20363eeba44e65d0ca0 + "); + + let mut output = [0u8; 256]; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(&output[..], &expected[..]); + } + + #[test] + fn bls12_381_g2_multiexp() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Bls12ConstOperations(Bls12ConstOperations{price: 1})], + native: EthereumBuiltin::from_str("bls12_381_g2_multiexp").unwrap(), + }; + + let input = hex!(" + 00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e + 0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d3004 + 0000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576 + 000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df + b3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e + 0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6 + 000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d2090324 + 0000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd9 + 00000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d9388 + 4d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d + 0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d + 000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407 + 000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80 + 000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc + 973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1 + 000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb59 + 00000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c + 00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d + 0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf49 + 4c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a + 000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8 + 000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291 + 000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519 + 000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e + 8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89 + 0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e + 000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72 + 000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31 + 000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2 + 787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944 + 00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50 + 000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d + 0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f3 + 00000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46e + aaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1 + 000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff + 0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e67 + 00000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d1 + 0000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12 + dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c + 0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af3 + 0000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f + 0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f + 00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5b + bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108 + 000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e + 00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b6 + 00000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb40 + 00000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596 + fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672 + 00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829 + 000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d9 + 00000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c0 + 00000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196 + b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea + 000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3 + 000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a + 000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b66 + 0000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a9 + 3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76 + 000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c + 000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc668 + 0000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd + 0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836 + dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c + 000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e4 + 00000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c + 0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b + 000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b + 7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a + 0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623 + 000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346 + 000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d93 + 00000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd3 + 94c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054 + 00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf + 0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675 + 000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d + 00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bd + b3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746 + "); + let expected = hex!(" + 00000000000000000000000000000000083ad744b34f6393bc983222b004657494232c5d9fbc978d76e2377a28a34c4528da5d91cbc0977dc953397a6d21eca2 + 0000000000000000000000000000000015aec6526e151cf5b8403353517dfb9a162087a698b71f32b266d3c5c936a83975d5567c25b3a5994042ec1379c8e526 + 000000000000000000000000000000000e3647185d1a20efad19f975729908840dc33909a583600f7915025f906aef9c022fd34e618170b11178aaa824ae36b3 + 00000000000000000000000000000000159576d1d53f6cd12c39d651697e11798321f17cd287118d7ebeabf68281bc03109ee103ee8ef2ef93c71dd1dcbaf1e0 + "); + + let mut output = [0u8; 256]; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(&output[..], &expected[..]); + } + + #[test] + fn bls12_381_pairing() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Bls12Pairing(Bls12PairingPricer{price: Bls12PairingPrice{base: 1, pair: 1}})], + native: EthereumBuiltin::from_str("bls12_381_pairing").unwrap(), + }; + + let input = hex!(" + 000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80 + 000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d5 + 00000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e + 000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834 + 000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e + 0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e + 000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80 + 00000000000000000000000000000000118cd94e36ab177de95f52f180fdbdc584b8d30436eb882980306fa0625f07a1f7ad3b4c38a921c53d14aa9a6ba5b8d6 + 00000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e + 000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834 + 000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e + 0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e + "); + let expected = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000001 + " + ); + + let mut output = [0u8; 32]; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(&output[..], &expected[..]); + } + + #[test] + fn bls12_381_fp_to_g1() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Bls12Pairing(Bls12PairingPricer{price: Bls12PairingPrice{base: 1, pair: 1}})], + native: EthereumBuiltin::from_str("bls12_381_fp_to_g1").unwrap(), + }; + + let input = hex!(" + 0000000000000000000000000000000017f66b472b36717ee0902d685c808bb5f190bbcb2c51d067f1cbec64669f10199a5868d7181dcec0498fcc71f5acaf79 + "); + let expected = hex!(" + 00000000000000000000000000000000188dc9e5ddf48977f33aeb6e505518269bf67fb624fa86b79741d842e75a6fa1be0911c2caa9e55571b6e55a3c0c0b9e + 00000000000000000000000000000000193e8b7c7e78daf104a59d7b39401a65355fa874bd34e91688580941e99a863367efc68fe871e38e07423090e93919c9 + "); + + let mut output = [0u8; 128]; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(&output[..], &expected[..]); + } + + #[test] + fn bls12_381_fp2_to_g2() { + let f = Builtin { + pricer: btreemap![0 => Pricing::Bls12Pairing(Bls12PairingPricer{price: Bls12PairingPrice{base: 1, pair: 1}})], + native: EthereumBuiltin::from_str("bls12_381_fp2_to_g2").unwrap(), + }; + + let input = hex!(" + 000000000000000000000000000000000f470603a402bc134db1b389fd187460f9eb2dd001a2e99f730af386508c62f0e911d831a2562da84bce11d39f2ff13f + 000000000000000000000000000000000d8c45f4ab20642d0cba9764126e0818b7d731a6ba29ed234d9d6309a5e8ddfbd85193f1fa8b7cfeed3d31b23b904ee9 + "); + let expected = hex!(" + 0000000000000000000000000000000012e74d5a0c005a86ca148e9eff8e34a00bfa8b6e6aadf633d65cd09bb29917e0ceb0d5c9d9650c162d7fe4aa27452685 + 0000000000000000000000000000000005f09101a2088712619f9c096403b66855a12f9016c55aef6047372fba933f02d9d59db1a86df7be57978021e2457821 + 00000000000000000000000000000000136975b37fe400d1d217a2b496c1552b39be4e9e71dd7ad482f5f0836d271d02959fdb698dda3d0530587fb86e0db1dd + 0000000000000000000000000000000000bad0aabd9309e92e2dd752f4dd73be07c0de2c5ddd57916b9ffa065d7440d03d44e7c042075cda694414a9fb639bb7 + "); + + let mut output = [0u8; 256]; + + f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])) + .expect("Builtin should not fail"); + assert_eq!(&output[..], &expected[..]); + } + + #[test] + fn bls12_381_g1_multiexp_init_from_spec() { + use ethjson::spec::builtin::{Bls12G1Multiexp, Pricing}; + + let b = Builtin::try_from(JsonBuiltin { + name: "bls12_381_g1_multiexp".to_owned(), + pricing: btreemap![ + 10000000 => PricingAt { + info: None, + price: Pricing::Bls12G1Multiexp(Bls12G1Multiexp{ + base: 12000, + }), + } + ], + }) + .unwrap(); + + match b.native { + EthereumBuiltin::Bls12G1MultiExp(..) => {} + _ => { + panic!("invalid precompile type"); + } + } + } + + #[test] + fn bls12_381_g2_multiexp_init_from_spec() { + use ethjson::spec::builtin::{Bls12G2Multiexp, Pricing}; + + let b = Builtin::try_from(JsonBuiltin { + name: "bls12_381_g2_multiexp".to_owned(), + pricing: btreemap![ + 10000000 => PricingAt { + info: None, + price: Pricing::Bls12G2Multiexp(Bls12G2Multiexp{ + base: 55000, + }), + } + ], + }) + .unwrap(); + + match b.native { + EthereumBuiltin::Bls12G2MultiExp(..) => {} + _ => { + panic!("invalid precompile type"); + } + } + } +} diff --git a/evm-tests/ethjson/Cargo.toml b/evm-tests/ethjson/Cargo.toml new file mode 100644 index 000000000..50f0ab90e --- /dev/null +++ b/evm-tests/ethjson/Cargo.toml @@ -0,0 +1,18 @@ +[package] +description = "OpenEthereum JSON Deserialization" +name = "ethjson" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +ethereum-types = "0.14" +rustc-hex = "2.1.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +[dev-dependencies] +maplit = "1.0.2" + +[features] +test-helpers = [] diff --git a/evm-tests/ethjson/src/bytes.rs b/evm-tests/ethjson/src/bytes.rs new file mode 100644 index 000000000..8f761b616 --- /dev/null +++ b/evm-tests/ethjson/src/bytes.rs @@ -0,0 +1,134 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Lenient bytes json deserialization for test json files. + +use rustc_hex::FromHex; +use serde::de::{Error, Visitor}; +use serde::{Deserialize, Deserializer}; +use std::fmt; +use std::ops::Deref; +use std::str::FromStr; + +/// Lenient bytes json deserialization for test json files. +#[derive(Default, Debug, PartialEq, Eq, Clone, PartialOrd, Ord)] +pub struct Bytes(Vec); + +impl Bytes { + /// Creates bytes struct. + pub fn new(v: Vec) -> Self { + Self(v) + } +} + +impl From for Vec { + fn from(bytes: Bytes) -> Self { + bytes.0 + } +} + +impl From> for Bytes { + fn from(bytes: Vec) -> Self { + Self(bytes) + } +} + +impl Deref for Bytes { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + &self.0 + } +} + +impl FromStr for Bytes { + type Err = String; + + fn from_str(value: &str) -> Result { + let v = match value.len() { + 0 => vec![], + 2 if value.starts_with("0x") => vec![], + _ if value.starts_with("0x") && value.len() % 2 == 1 => { + let v = "0".to_owned() + &value[2..]; + FromHex::from_hex(v.as_str()).unwrap_or_default() + } + _ if value.starts_with("0x") => FromHex::from_hex(&value[2..]).unwrap_or_default(), + _ => FromHex::from_hex(value).unwrap_or_default(), + }; + + Ok(Self(v)) + } +} + +impl<'a> Deserialize<'a> for Bytes { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + deserializer.deserialize_any(BytesVisitor) + } +} + +struct BytesVisitor; + +impl<'a> Visitor<'a> for BytesVisitor { + type Value = Bytes; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a hex encoded string of bytes") + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + Bytes::from_str(value).map_err(Error::custom) + } + + fn visit_string(self, value: String) -> Result + where + E: Error, + { + self.visit_str(value.as_ref()) + } +} + +#[cfg(test)] +mod test { + use super::Bytes; + + #[test] + fn bytes_deserialization() { + let s = r#"["", "0x", "0x12", "1234", "0x001"]"#; + let deserialized: Vec = serde_json::from_str(s).unwrap(); + assert_eq!( + deserialized, + vec![ + Bytes(vec![]), + Bytes(vec![]), + Bytes(vec![0x12]), + Bytes(vec![0x12, 0x34]), + Bytes(vec![0, 1]) + ] + ); + } + + #[test] + fn bytes_into() { + let v: Vec = Bytes(vec![0xff, 0x11]).into(); + assert_eq!(vec![0xff, 0x11], v); + } +} diff --git a/evm-tests/ethjson/src/hash.rs b/evm-tests/ethjson/src/hash.rs new file mode 100644 index 000000000..fac3a27eb --- /dev/null +++ b/evm-tests/ethjson/src/hash.rs @@ -0,0 +1,142 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Lenient hash json deserialization for test json files. + +use ethereum_types::{ + Bloom as Hash2048, H160 as Hash160, H256 as Hash256, H520 as Hash520, H64 as Hash64, +}; +use serde::de::{Error, Visitor}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; +use std::str::FromStr; + +macro_rules! impl_hash { + ($name: ident, $inner: ident) => { + /// Lenient hash json deserialization for test json files. + #[derive(Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)] + pub struct $name(pub $inner); + + impl From<$name> for $inner { + fn from(other: $name) -> $inner { + other.0 + } + } + + impl From<$inner> for $name { + fn from(i: $inner) -> Self { + $name(i) + } + } + + impl<'a> Deserialize<'a> for $name { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + struct HashVisitor; + + impl<'b> Visitor<'b> for HashVisitor { + type Value = $name; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a 0x-prefixed hex-encoded hash") + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + let value = match value.len() { + 0 => $inner::from_low_u64_be(0), + 2 if value == "0x" => $inner::from_low_u64_be(0), + _ if value.starts_with("0x") => { + $inner::from_str(&value[2..]).map_err(|e| { + Error::custom( + format!("Invalid hex value {}: {}", value, e).as_str(), + ) + })? + } + _ => $inner::from_str(value).map_err(|e| { + Error::custom( + format!("Invalid hex value {}: {}", value, e).as_str(), + ) + })?, + }; + + Ok($name(value)) + } + + fn visit_string(self, value: String) -> Result + where + E: Error, + { + self.visit_str(value.as_ref()) + } + } + + deserializer.deserialize_any(HashVisitor) + } + } + + impl Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("{:#x}", self.0)) + } + } + }; +} + +impl_hash!(H64, Hash64); +impl_hash!(Address, Hash160); +impl_hash!(H256, Hash256); +impl_hash!(H520, Hash520); +impl_hash!(Bloom, Hash2048); + +#[cfg(test)] +mod test { + use super::H256; + use std::str::FromStr; + + #[test] + fn hash_deserialization() { + let s = r#"["", "5a39ed1020c04d4d84539975b893a4e7c53eab6c2965db8bc3468093a31bc5ae"]"#; + let deserialized: Vec = serde_json::from_str(s).unwrap(); + assert_eq!( + deserialized, + vec![ + H256(ethereum_types::H256::zero()), + H256( + ethereum_types::H256::from_str( + "5a39ed1020c04d4d84539975b893a4e7c53eab6c2965db8bc3468093a31bc5ae" + ) + .unwrap() + ) + ] + ); + } + + #[test] + fn hash_into() { + assert_eq!( + ethereum_types::H256::zero(), + H256(ethereum_types::H256::zero()).into() + ); + } +} diff --git a/evm-tests/ethjson/src/lib.rs b/evm-tests/ethjson/src/lib.rs new file mode 100644 index 000000000..17a9581cd --- /dev/null +++ b/evm-tests/ethjson/src/lib.rs @@ -0,0 +1,31 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! JSON deserialization library + +#![warn(missing_docs)] + +pub mod bytes; +pub mod hash; +pub mod maybe; +pub mod spec; +pub mod state; +pub mod transaction; +pub mod uint; +pub mod vm; + +#[cfg(any(test, feature = "test-helpers"))] +pub mod test_helpers; diff --git a/evm-tests/ethjson/src/maybe.rs b/evm-tests/ethjson/src/maybe.rs new file mode 100644 index 000000000..c800702d3 --- /dev/null +++ b/evm-tests/ethjson/src/maybe.rs @@ -0,0 +1,153 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Deserializer of empty string values into optionals. + +use std::fmt; +use std::marker::PhantomData; + +use ethereum_types::U256; +use serde::de::{Error, IntoDeserializer, Visitor}; +use serde::{Deserialize, Deserializer}; + +use crate::uint::Uint; + +/// Deserializer of empty string values into optionals. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum MaybeEmpty { + /// Some. + Some(T), + /// None. + None, +} + +impl<'a, T> Deserialize<'a> for MaybeEmpty +where + T: Deserialize<'a>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + deserializer.deserialize_any(MaybeEmptyVisitor::new()) + } +} + +struct MaybeEmptyVisitor { + _phantom: PhantomData, +} + +impl MaybeEmptyVisitor { + const fn new() -> Self { + Self { + _phantom: PhantomData, + } + } +} + +impl<'a, T> Visitor<'a> for MaybeEmptyVisitor +where + T: Deserialize<'a>, +{ + type Value = MaybeEmpty; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "an empty string or string-encoded type") + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + self.visit_string(value.to_owned()) + } + + fn visit_string(self, value: String) -> Result + where + E: Error, + { + if value.is_empty() { + Ok(MaybeEmpty::None) + } else { + T::deserialize(value.into_deserializer()).map(MaybeEmpty::Some) + } + } +} + +impl From> for Option { + fn from(val: MaybeEmpty) -> Self { + match val { + MaybeEmpty::Some(s) => Some(s), + MaybeEmpty::None => None, + } + } +} + +#[cfg(test)] +impl From for MaybeEmpty { + fn from(uint: Uint) -> Self { + Self::Some(uint) + } +} + +impl From> for U256 { + fn from(maybe: MaybeEmpty) -> Self { + match maybe { + MaybeEmpty::Some(v) => v.0, + MaybeEmpty::None => Self::zero(), + } + } +} + +impl From> for u64 { + fn from(maybe: MaybeEmpty) -> Self { + match maybe { + MaybeEmpty::Some(v) => v.0.low_u64(), + MaybeEmpty::None => 0u64, + } + } +} + +impl Default for MaybeEmpty { + fn default() -> Self { + Self::Some(Uint::default()) + } +} + +#[cfg(test)] +mod tests { + use super::MaybeEmpty; + use crate::hash::H256; + use std::str::FromStr; + + #[test] + fn maybe_deserialization() { + let s = r#"["", "5a39ed1020c04d4d84539975b893a4e7c53eab6c2965db8bc3468093a31bc5ae"]"#; + let deserialized: Vec> = serde_json::from_str(s).unwrap(); + assert_eq!( + deserialized, + vec![ + MaybeEmpty::None, + MaybeEmpty::Some(H256( + ethereum_types::H256::from_str( + "5a39ed1020c04d4d84539975b893a4e7c53eab6c2965db8bc3468093a31bc5ae" + ) + .unwrap() + )) + ] + ); + } +} diff --git a/evm-tests/ethjson/src/spec/account.rs b/evm-tests/ethjson/src/spec/account.rs new file mode 100644 index 000000000..b45b6f038 --- /dev/null +++ b/evm-tests/ethjson/src/spec/account.rs @@ -0,0 +1,166 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Spec account deserialization. + +use std::collections::BTreeMap; + +use crate::{bytes::Bytes, spec::builtin::BuiltinCompat, uint::Uint}; +use serde::Deserialize; + +/// Spec account. +#[cfg_attr(any(test, feature = "test-helpers"), derive(Clone))] +#[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct Account { + /// Builtin contract. + pub builtin: Option, + /// Balance. + pub balance: Option, + /// Nonce. + pub nonce: Option, + /// Code. + pub code: Option, + /// Version. + pub version: Option, + /// Storage. + pub storage: Option>, + /// Constructor. + pub constructor: Option, +} + +impl Account { + /// Returns true if account does not have nonce, balance, code and storage. + pub const fn is_empty(&self) -> bool { + self.balance.is_none() + && self.nonce.is_none() + && self.code.is_none() + && self.storage.is_none() + } +} + +#[cfg(test)] +mod tests { + use super::{Account, BTreeMap, Bytes, Uint}; + use ethereum_types::U256; + + #[test] + fn account_balance_missing_not_empty() { + let s = r#"{ + "nonce": "0", + "code": "1234", + "storage": { "0x7fffffffffffffff7fffffffffffffff": "0x1" } + }"#; + let deserialized: Account = serde_json::from_str(s).unwrap(); + assert!(!deserialized.is_empty()); + } + + #[test] + fn account_nonce_missing_not_empty() { + let s = r#"{ + "balance": "1", + "code": "1234", + "storage": { "0x7fffffffffffffff7fffffffffffffff": "0x1" } + }"#; + let deserialized: Account = serde_json::from_str(s).unwrap(); + assert!(!deserialized.is_empty()); + } + + #[test] + fn account_code_missing_not_empty() { + let s = r#"{ + "balance": "1", + "nonce": "0", + "storage": { "0x7fffffffffffffff7fffffffffffffff": "0x1" } + }"#; + let deserialized: Account = serde_json::from_str(s).unwrap(); + assert!(!deserialized.is_empty()); + } + + #[test] + fn account_storage_missing_not_empty() { + let s = r#"{ + "balance": "1", + "nonce": "0", + "code": "1234" + }"#; + let deserialized: Account = serde_json::from_str(s).unwrap(); + assert!(!deserialized.is_empty()); + } + + #[test] + fn account_empty() { + let s = r#"{ + "builtin": { + "name": "ecrecover", + "pricing": { + "linear": { + "base": 3000, + "word": 0 + } + } + } + }"#; + let deserialized: Account = serde_json::from_str(s).unwrap(); + assert!(deserialized.is_empty()); + } + + #[test] + fn account_deserialization() { + let s = r#"{ + "balance": "1", + "nonce": "0", + "code": "1234", + "builtin": { + "name": "ecrecover", + "pricing": { + "linear": { + "base": 3000, + "word": 0 + } + } + } + }"#; + let deserialized: Account = serde_json::from_str(s).unwrap(); + assert!(!deserialized.is_empty()); + assert_eq!(deserialized.balance.unwrap(), Uint(U256::from(1))); + assert_eq!(deserialized.nonce.unwrap(), Uint(U256::from(0))); + assert_eq!(deserialized.code.unwrap(), Bytes::new(vec![0x12, 0x34])); + assert!(deserialized.builtin.is_some()); // Further tested in builtin.rs + } + + #[test] + fn account_storage_deserialization() { + let s = r#"{ + "balance": "1", + "nonce": "0", + "code": "1234", + "storage": { "0x7fffffffffffffff7fffffffffffffff": "0x1" } + }"#; + let deserialized: Account = serde_json::from_str(s).unwrap(); + assert!(!deserialized.is_empty()); + assert_eq!(deserialized.balance.unwrap(), Uint(U256::from(1))); + assert_eq!(deserialized.nonce.unwrap(), Uint(U256::from(0))); + assert_eq!(deserialized.code.unwrap(), Bytes::new(vec![0x12, 0x34])); + let mut storage = BTreeMap::new(); + storage.insert( + Uint(U256::from("7fffffffffffffff7fffffffffffffff")), + Uint(U256::from(1)), + ); + assert_eq!(deserialized.storage.unwrap(), storage); + } +} diff --git a/evm-tests/ethjson/src/spec/authority_round.rs b/evm-tests/ethjson/src/spec/authority_round.rs new file mode 100644 index 000000000..f136aed1c --- /dev/null +++ b/evm-tests/ethjson/src/spec/authority_round.rs @@ -0,0 +1,201 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Authority Round parameter deserialization. +//! +//! Here is an example of input parameters where the step duration is constant at 5 seconds, the set +//! of validators is decided by the contract at address `0x10..01` starting from block 0, and where +//! the address of the contract that computes block rewards is set to `0x20..02` for blocks 0 +//! through 41 and to `0x30.03` for all blocks starting from block 42. +//! +//! ```ignore +//! "params": { +//! "stepDuration": "5", +//! "validators": { +//! "multi": { +//! "0": { +//! "contract": "0x1000000000000000000000000000000000000001" +//! } +//! } +//! }, +//! "blockRewardContractTransitions": { +//! "0": "0x2000000000000000000000000000000000000002", +//! "42": "0x3000000000000000000000000000000000000003" +//! } +//! } +//! ``` + +use super::{StepDuration, ValidatorSet}; +use crate::{bytes::Bytes, hash::Address, uint::Uint}; +use serde::Deserialize; +use std::collections::BTreeMap; + +/// Authority params deserialization. +#[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct AuthorityRoundParams { + /// Block duration, in seconds. + pub step_duration: StepDuration, + /// Valid authorities + pub validators: ValidatorSet, + /// Starting step. Determined automatically if not specified. + /// To be used for testing only. + pub start_step: Option, + /// Block at which score validation should start. + pub validate_score_transition: Option, + /// Block from which monotonic steps start. + pub validate_step_transition: Option, + /// Whether transitions should be immediate. + pub immediate_transitions: Option, + /// Reward per block in wei. + pub block_reward: Option, + /// Block at which the block reward contract should start being used. This option allows one to + /// add a single block reward contract transition and is compatible with the multiple address + /// option `block_reward_contract_transitions` below. + pub block_reward_contract_transition: Option, + /// Block reward contract address which overrides the `block_reward` setting. This option allows + /// one to add a single block reward contract address and is compatible with the multiple + /// address option `block_reward_contract_transitions` below. + pub block_reward_contract_address: Option

, + /// Block reward contract addresses with their associated starting block numbers. + /// + /// Setting the block reward contract overrides `block_reward`. If the single block reward + /// contract address is also present then it is added into the map at the block number stored in + /// `block_reward_contract_transition` or 0 if that block number is not provided. Therefore both + /// a single block reward contract transition and a map of reward contract transitions can be + /// used simulataneously in the same configuration. In such a case the code requires that the + /// block number of the single transition is strictly less than any of the block numbers in the + /// map. + pub block_reward_contract_transitions: Option>, + /// Block reward code. This overrides the block reward contract address. + pub block_reward_contract_code: Option, + /// Block at which maximum uncle count should be considered. + pub maximum_uncle_count_transition: Option, + /// Maximum number of accepted uncles. + pub maximum_uncle_count: Option, + /// Block at which empty step messages should start. + pub empty_steps_transition: Option, + /// Maximum number of accepted empty steps. + pub maximum_empty_steps: Option, + /// Strict validation of empty steps transition block. + pub strict_empty_steps_transition: Option, + /// First block for which a 2/3 quorum (instead of 1/2) is required. + pub two_thirds_majority_transition: Option, + /// The random number contract's address, or a map of contract transitions. + pub randomness_contract_address: Option>, + /// The addresses of contracts that determine the block gas limit starting from the block number + /// associated with each of those contracts. + pub block_gas_limit_contract_transitions: Option>, + /// The block number at which the consensus engine switches from AuRa to AuRa with POSDAO + /// modifications. + pub posdao_transition: Option, +} + +/// Authority engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct AuthorityRound { + /// Authority Round parameters. + pub params: AuthorityRoundParams, +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use ethereum_types::{H160, U256}; + use serde_json; + + use super::{Address, StepDuration, Uint}; + use crate::spec::{authority_round::AuthorityRound, validator_set::ValidatorSet}; + + #[test] + fn authority_round_deserialization() { + let s = r#"{ + "params": { + "stepDuration": "0x02", + "validators": { + "list" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + }, + "startStep" : 24, + "validateStepTransition": 150, + "blockReward": 5000000, + "maximumUncleCountTransition": 10000000, + "maximumUncleCount": 5, + "randomnessContractAddress": { + "10": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "20": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + "blockGasLimitContractTransitions": { + "10": "0x1000000000000000000000000000000000000001", + "20": "0x2000000000000000000000000000000000000002" + } + } + }"#; + + let deserialized: AuthorityRound = serde_json::from_str(s).unwrap(); + assert_eq!( + deserialized.params.step_duration, + StepDuration::Single(Uint(U256::from(2))) + ); + assert_eq!( + deserialized.params.validators, + ValidatorSet::List(vec![Address( + H160::from_str("c6d9d2cd449a754c494264e1809c50e34d64562b").unwrap() + )]), + ); + assert_eq!(deserialized.params.start_step, Some(Uint(U256::from(24)))); + assert_eq!(deserialized.params.immediate_transitions, None); + assert_eq!( + deserialized.params.maximum_uncle_count_transition, + Some(Uint(10_000_000.into())) + ); + assert_eq!( + deserialized.params.maximum_uncle_count, + Some(Uint(5.into())) + ); + assert_eq!( + deserialized.params.randomness_contract_address.unwrap(), + vec![ + ( + Uint(10.into()), + Address(H160::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap()) + ), + ( + Uint(20.into()), + Address(H160::from_str("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").unwrap()) + ), + ] + .into_iter() + .collect() + ); + let expected_bglc = [ + ( + Uint(10.into()), + Address(H160::from_str("1000000000000000000000000000000000000001").unwrap()), + ), + ( + Uint(20.into()), + Address(H160::from_str("2000000000000000000000000000000000000002").unwrap()), + ), + ]; + assert_eq!( + deserialized.params.block_gas_limit_contract_transitions, + Some(expected_bglc.iter().copied().collect()) + ); + } +} diff --git a/evm-tests/ethjson/src/spec/basic_authority.rs b/evm-tests/ethjson/src/spec/basic_authority.rs new file mode 100644 index 000000000..752b368cf --- /dev/null +++ b/evm-tests/ethjson/src/spec/basic_authority.rs @@ -0,0 +1,68 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Authority params deserialization. + +use super::ValidatorSet; +use crate::uint::Uint; +use serde::Deserialize; + +/// Authority params deserialization. +#[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct BasicAuthorityParams { + /// Block duration. + pub duration_limit: Uint, + /// Valid authorities + pub validators: ValidatorSet, +} + +/// Authority engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct BasicAuthority { + /// Ethash params. + pub params: BasicAuthorityParams, +} + +#[cfg(test)] +mod tests { + use super::{BasicAuthority, Uint}; + use crate::{hash::Address, spec::validator_set::ValidatorSet}; + use ethereum_types::{H160, U256}; + use std::str::FromStr; + + #[test] + fn basic_authority_deserialization() { + let s = r#"{ + "params": { + "durationLimit": "0x0d", + "validators" : { + "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } + } + }"#; + + let deserialized: BasicAuthority = serde_json::from_str(s).unwrap(); + + assert_eq!(deserialized.params.duration_limit, Uint(U256::from(0x0d))); + let vs = ValidatorSet::List(vec![Address( + H160::from_str("c6d9d2cd449a754c494264e1809c50e34d64562b").unwrap(), + )]); + assert_eq!(deserialized.params.validators, vs); + } +} diff --git a/evm-tests/ethjson/src/spec/builtin.rs b/evm-tests/ethjson/src/spec/builtin.rs new file mode 100644 index 000000000..9de3c8a9b --- /dev/null +++ b/evm-tests/ethjson/src/spec/builtin.rs @@ -0,0 +1,366 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Spec builtin deserialization. + +use std::collections::BTreeMap; + +use crate::uint::Uint; +use serde::Deserialize; + +/// Linear pricing. +#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct Linear { + /// Base price. + pub base: u64, + /// Price for word. + pub word: u64, +} + +/// Pricing for modular exponentiation. +#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct Modexp { + /// Price divisor. + pub divisor: u64, + /// Use price scheme from EIP-2565 + pub is_eip_2565: bool, +} + +/// Pricing for constant alt_bn128 operations (ECADD and ECMUL) +#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct AltBn128ConstOperations { + /// price + pub price: u64, +} + +/// Pricing for alt_bn128_pairing. +#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct AltBn128Pairing { + /// Base price. + pub base: u64, + /// Price per point pair. + pub pair: u64, +} + +/// Bls12 pairing price +#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct Bls12Pairing { + /// Price per final exp + pub base: u64, + /// Price per pair (Miller loop) + pub pair: u64, +} + +/// Pricing for constant Bls12 operations (ADD and MUL in G1 and G2, as well as mappings) +#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct Bls12ConstOperations { + /// Fixed price. + pub price: u64, +} + +/// Pricing for constant Bls12 operations (ADD and MUL in G1, as well as mappings) +#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct Bls12G1Multiexp { + /// Base const of the operation (G1 or G2 multiplication) + pub base: u64, +} + +/// Pricing for constant Bls12 operations (ADD and MUL in G2, as well as mappings) +#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct Bls12G2Multiexp { + /// Base const of the operation (G1 or G2 multiplication) + pub base: u64, +} + +/// Pricing variants. +#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "snake_case")] +pub enum Pricing { + /// Pricing for Blake2 compression function: each call costs the same amount per round. + Blake2F { + /// Price per round of Blake2 compression function. + gas_per_round: u64, + }, + /// Linear pricing. + Linear(Linear), + /// Pricing for modular exponentiation. + Modexp(Modexp), + /// Pricing for alt_bn128_pairing exponentiation. + AltBn128Pairing(AltBn128Pairing), + /// Pricing for constant alt_bn128 operations + AltBn128ConstOperations(AltBn128ConstOperations), + /// Pricing of constant price bls12_381 operations + Bls12ConstOperations(Bls12ConstOperations), + /// Pricing of pairing bls12_381 operation + Bls12Pairing(Bls12Pairing), + /// Pricing of bls12_381 multiexp operations in G1 + Bls12G1Multiexp(Bls12G1Multiexp), + /// Pricing of bls12_381 multiexp operations in G2 + Bls12G2Multiexp(Bls12G2Multiexp), +} + +/// Builtin compability layer +#[derive(Debug, PartialEq, Deserialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct BuiltinCompat { + /// Builtin name. + name: String, + /// Builtin pricing. + pricing: PricingCompat, + /// Activation block. + activate_at: Option, +} + +/// Spec builtin. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Builtin { + /// Builtin name. + pub name: String, + /// Builtin pricing. + pub pricing: BTreeMap, +} + +impl From for Builtin { + fn from(legacy: BuiltinCompat) -> Self { + let pricing = match legacy.pricing { + PricingCompat::Single(pricing) => { + let mut map = BTreeMap::new(); + let activate_at: u64 = legacy.activate_at.map_or(0, Into::into); + map.insert( + activate_at, + PricingAt { + info: None, + price: pricing, + }, + ); + map + } + PricingCompat::Multi(pricings) => { + pricings.into_iter().map(|(a, p)| (a.into(), p)).collect() + } + }; + Self { + name: legacy.name, + pricing, + } + } +} + +/// Compability layer for different pricings +#[derive(Debug, PartialEq, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +enum PricingCompat { + /// Single builtin + Single(Pricing), + /// Multiple builtins + Multi(BTreeMap), +} + +/// Price for a builtin, with the block number to activate it on +#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct PricingAt { + /// Description of the activation, e.g. "PunyPony HF, March 12, 2025". + pub info: Option, + /// Builtin pricing. + pub price: Pricing, +} + +#[cfg(test)] +mod tests { + use super::{ + AltBn128ConstOperations, Bls12G1Multiexp, Bls12G2Multiexp, Builtin, BuiltinCompat, Linear, + Modexp, Pricing, PricingAt, + }; + use maplit::btreemap; + + #[test] + fn builtin_deserialization() { + let s = r#"{ + "name": "ecrecover", + "pricing": { "linear": { "base": 3000, "word": 0 } } + }"#; + let builtin: Builtin = serde_json::from_str::(s).unwrap().into(); + assert_eq!(builtin.name, "ecrecover"); + assert_eq!( + builtin.pricing, + btreemap![ + 0 => PricingAt { + info: None, + price: Pricing::Linear(Linear { base: 3000, word: 0 }) + } + ] + ); + } + + #[test] + fn deserialize_multiple_pricings() { + let s = r#"{ + "name": "ecrecover", + "pricing": { + "0": { + "price": {"linear": { "base": 3000, "word": 0 }} + }, + "500": { + "info": "enable fake EIP at block 500", + "price": {"linear": { "base": 10, "word": 0 }} + } + } + }"#; + let builtin: Builtin = serde_json::from_str::(s).unwrap().into(); + assert_eq!(builtin.name, "ecrecover"); + assert_eq!( + builtin.pricing, + btreemap![ + 0 => PricingAt { + info: None, + price: Pricing::Linear(Linear { base: 3000, word: 0 }) + }, + 500 => PricingAt { + info: Some(String::from("enable fake EIP at block 500")), + price: Pricing::Linear(Linear { base: 10, word: 0 }) + } + ] + ); + } + + #[test] + fn deserialization_blake2_f_builtin() { + let s = r#"{ + "name": "blake2_f", + "activate_at": "0xffffff", + "pricing": { "blake2_f": { "gas_per_round": 123 } } + }"#; + let builtin: Builtin = serde_json::from_str::(s).unwrap().into(); + assert_eq!(builtin.name, "blake2_f"); + assert_eq!( + builtin.pricing, + btreemap![ + 0xffffff => PricingAt { + info: None, + price: Pricing::Blake2F { gas_per_round: 123 } + } + ] + ); + } + + #[test] + fn deserialization_alt_bn128_const_operations() { + let s = r#"{ + "name": "alt_bn128_mul", + "pricing": { + "100500": { + "price": { "alt_bn128_const_operations": { "price": 123 }} + } + } + }"#; + let builtin: Builtin = serde_json::from_str::(s).unwrap().into(); + assert_eq!(builtin.name, "alt_bn128_mul"); + assert_eq!( + builtin.pricing, + btreemap![ + 100500 => PricingAt { + info: None, + price: Pricing::AltBn128ConstOperations(AltBn128ConstOperations { + price: 123, + }), + } + ] + ); + } + + #[test] + fn activate_at() { + let s = r#"{ + "name": "late_start", + "activate_at": 100000, + "pricing": { "modexp": { "divisor": 5, "is_eip_2565": false } } + }"#; + + let builtin: Builtin = serde_json::from_str::(s).unwrap().into(); + assert_eq!(builtin.name, "late_start"); + assert_eq!( + builtin.pricing, + btreemap![ + 100_000 => PricingAt { + info: None, + price: Pricing::Modexp(Modexp { divisor: 5, is_eip_2565: false }) + } + ] + ); + } + + #[test] + fn deserialization_bls12_381_multiexp_operation() { + let s = r#"{ + "name": "bls12_381_g1_multiexp", + "pricing": { + "10000000": { + "price": { "bls12_g1_multiexp": { "base": 12000}} + } + } + }"#; + let builtin: Builtin = serde_json::from_str::(s).unwrap().into(); + assert_eq!(builtin.name, "bls12_381_g1_multiexp"); + assert_eq!( + builtin.pricing, + btreemap![ + 10000000 => PricingAt { + info: None, + price: Pricing::Bls12G1Multiexp(Bls12G1Multiexp{ + base: 12000 + }), + } + ] + ); + } + + #[test] + fn deserialization_bls12_381_multiexp_operation_in_g2() { + let s = r#"{ + "name": "bls12_381_g2_multiexp", + "pricing": { + "10000000": { + "price": { "bls12_g2_multiexp": { "base": 55000}} + } + } + }"#; + let builtin: Builtin = serde_json::from_str::(s).unwrap().into(); + assert_eq!(builtin.name, "bls12_381_g2_multiexp"); + assert_eq!( + builtin.pricing, + btreemap![ + 10000000 => PricingAt { + info: None, + price: Pricing::Bls12G2Multiexp(Bls12G2Multiexp{ + base: 55000 + }), + } + ] + ); + } +} diff --git a/evm-tests/ethjson/src/spec/clique.rs b/evm-tests/ethjson/src/spec/clique.rs new file mode 100644 index 000000000..f1c7feef8 --- /dev/null +++ b/evm-tests/ethjson/src/spec/clique.rs @@ -0,0 +1,55 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Clique params deserialization. + +use serde::Deserialize; +use std::num::NonZeroU64; + +/// Clique params deserialization. +#[derive(Debug, PartialEq, Eq, Deserialize)] +pub struct CliqueParams { + /// period as defined in EIP 225 + pub period: Option, + /// epoch length as defined in EIP 225 + pub epoch: Option, +} + +/// Clique engine deserialization. +#[derive(Debug, PartialEq, Eq, Deserialize)] +pub struct Clique { + /// CliqueEngine params + pub params: CliqueParams, +} + +#[cfg(test)] +mod tests { + use super::{Clique, NonZeroU64}; + + #[test] + fn clique_deserialization() { + let s = r#"{ + "params": { + "period": 5, + "epoch": 30000 + } + }"#; + + let deserialized: Clique = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized.params.period, Some(5u64)); + assert_eq!(deserialized.params.epoch, NonZeroU64::new(30000)); + } +} diff --git a/evm-tests/ethjson/src/spec/engine.rs b/evm-tests/ethjson/src/spec/engine.rs new file mode 100644 index 000000000..c1a499ec9 --- /dev/null +++ b/evm-tests/ethjson/src/spec/engine.rs @@ -0,0 +1,150 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Engine deserialization. + +use super::{AuthorityRound, BasicAuthority, Clique, Ethash, InstantSeal, NullEngine}; +use serde::Deserialize; + +/// Engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub enum Engine { + /// Null engine. + Null(NullEngine), + /// Instantly sealing engine. + InstantSeal(Option), + /// Ethash engine. + #[serde(rename = "Ethash")] + Ethash(Ethash), + /// BasicAuthority engine. + BasicAuthority(BasicAuthority), + /// AuthorityRound engine. + AuthorityRound(AuthorityRound), + /// Clique engine. + Clique(Clique), +} + +#[cfg(test)] +mod tests { + use super::Engine; + + #[test] + fn engine_deserialization() { + let s = r#"{ + "null": { + "params": { + "blockReward": "0x0d" + } + } + }"#; + + let deserialized: Engine = serde_json::from_str(s).unwrap(); + match deserialized { + Engine::Null(_) => {} // unit test in its own file. + _ => panic!(), + } + + let s = r#"{ + "instantSeal": {"params": {}} + }"#; + + let deserialized: Engine = serde_json::from_str(s).unwrap(); + match deserialized { + Engine::InstantSeal(_) => {} // instant seal is unit tested in its own file. + _ => panic!(), + }; + + let s = r#"{ + "instantSeal": null + }"#; + + let deserialized: Engine = serde_json::from_str(s).unwrap(); + match deserialized { + Engine::InstantSeal(_) => {} // instant seal is unit tested in its own file. + _ => panic!(), + }; + + let s = r#"{ + "Ethash": { + "params": { + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "homesteadTransition" : "0x", + "daoHardforkTransition": "0xffffffffffffffff", + "daoHardforkBeneficiary": "0x0000000000000000000000000000000000000000", + "daoHardforkAccounts": [] + } + } + }"#; + + let deserialized: Engine = serde_json::from_str(s).unwrap(); + match deserialized { + Engine::Ethash(_) => {} // ethash is unit tested in its own file. + _ => panic!(), + }; + + let s = r#"{ + "basicAuthority": { + "params": { + "durationLimit": "0x0d", + "validators" : { + "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } + } + } + }"#; + let deserialized: Engine = serde_json::from_str(s).unwrap(); + match deserialized { + Engine::BasicAuthority(_) => {} // basicAuthority is unit tested in its own file. + _ => panic!(), + }; + + let s = r#"{ + "authorityRound": { + "params": { + "stepDuration": "0x02", + "validators": { + "list" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + }, + "startStep" : 24, + "validateStepTransition": 150 + } + } + }"#; + let deserialized: Engine = serde_json::from_str(s).unwrap(); + match deserialized { + Engine::AuthorityRound(_) => {} // AuthorityRound is unit tested in its own file. + _ => panic!(), + }; + + let s = r#"{ + "clique": { + "params": { + "period": 15, + "epoch": 30000 + } + } + }"#; + let deserialized: Engine = serde_json::from_str(s).unwrap(); + match deserialized { + Engine::Clique(_) => {} // Clique is unit tested in its own file. + _ => panic!(), + }; + } +} diff --git a/evm-tests/ethjson/src/spec/ethash.rs b/evm-tests/ethjson/src/spec/ethash.rs new file mode 100644 index 000000000..9014b8e95 --- /dev/null +++ b/evm-tests/ethjson/src/spec/ethash.rs @@ -0,0 +1,306 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Ethash params deserialization. + +use crate::{ + bytes::Bytes, + hash::Address, + uint::{self, Uint}, +}; +use serde::Deserialize; +use std::collections::BTreeMap; + +/// Deserializable doppelganger of block rewards for EthashParams +#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum BlockReward { + /// Single block reward + Single(Uint), + /// Several block rewards + Multi(BTreeMap), +} + +/// Deserializable doppelganger of EthashParams. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct EthashParams { + /// See main EthashParams docs. + #[serde(deserialize_with = "uint::validate_non_zero")] + pub minimum_difficulty: Uint, + /// See main EthashParams docs. + #[serde(deserialize_with = "uint::validate_non_zero")] + pub difficulty_bound_divisor: Uint, + /// See main EthashParams docs. + #[serde(default, deserialize_with = "uint::validate_optional_non_zero")] + pub difficulty_increment_divisor: Option, + /// See main EthashParams docs. + #[serde(default, deserialize_with = "uint::validate_optional_non_zero")] + pub metropolis_difficulty_increment_divisor: Option, + /// See main EthashParams docs. + pub duration_limit: Option, + + /// See main EthashParams docs. + pub homestead_transition: Option, + /// Reward per block in wei. + pub block_reward: Option, + /// Block at which the block reward contract should start being used. + pub block_reward_contract_transition: Option, + /// Block reward contract address (setting the block reward contract + /// overrides all other block reward parameters). + pub block_reward_contract_address: Option
, + /// Block reward code. This overrides the block reward contract address. + pub block_reward_contract_code: Option, + + /// See main EthashParams docs. + pub dao_hardfork_transition: Option, + /// See main EthashParams docs. + pub dao_hardfork_beneficiary: Option
, + /// See main EthashParams docs. + pub dao_hardfork_accounts: Option>, + + /// See main EthashParams docs. + pub difficulty_hardfork_transition: Option, + /// See main EthashParams docs. + #[serde(default, deserialize_with = "uint::validate_optional_non_zero")] + pub difficulty_hardfork_bound_divisor: Option, + /// See main EthashParams docs. + pub bomb_defuse_transition: Option, + + /// See main EthashParams docs. + pub eip100b_transition: Option, + + /// See main EthashParams docs. + #[serde(default, deserialize_with = "uint::validate_optional_non_zero")] + pub ecip1017_era_rounds: Option, + + /// Delays of difficulty bombs. + pub difficulty_bomb_delays: Option>, + + /// EXPIP-2 block height + pub expip2_transition: Option, + /// EXPIP-2 duration limit + pub expip2_duration_limit: Option, + /// Block to transition to progpow + pub progpow_transition: Option, +} + +/// Ethash engine deserialization. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Ethash { + /// Ethash params. + pub params: EthashParams, +} + +#[cfg(test)] +mod tests { + use super::{Address, BlockReward, Ethash, EthashParams, Uint}; + use ethereum_types::{H160, U256}; + use std::str::FromStr; + + #[test] + fn ethash_deserialization() { + let s = r#"{ + "params": { + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "homesteadTransition": "0x42", + "blockReward": "0x100", + "daoHardforkTransition": "0x08", + "daoHardforkBeneficiary": "0xabcabcabcabcabcabcabcabcabcabcabcabcabca", + "daoHardforkAccounts": [ + "0x304a554a310c7e546dfe434669c62820b7d83490", + "0x914d1b8b43e92723e64fd0a06f5bdb8dd9b10c79", + "0xfe24cdd8648121a43a7c86d289be4dd2951ed49f", + "0x17802f43a0137c506ba92291391a8a8f207f487d", + "0xb136707642a4ea12fb4bae820f03d2562ebff487", + "0xdbe9b615a3ae8709af8b93336ce9b477e4ac0940", + "0xf14c14075d6c4ed84b86798af0956deef67365b5", + "0xca544e5c4687d109611d0f8f928b53a25af72448", + "0xaeeb8ff27288bdabc0fa5ebb731b6f409507516c", + "0xcbb9d3703e651b0d496cdefb8b92c25aeb2171f7", + "0xaccc230e8a6e5be9160b8cdf2864dd2a001c28b6", + "0x2b3455ec7fedf16e646268bf88846bd7a2319bb2", + "0x4613f3bca5c44ea06337a9e439fbc6d42e501d0a", + "0xd343b217de44030afaa275f54d31a9317c7f441e", + "0x84ef4b2357079cd7a7c69fd7a37cd0609a679106", + "0xda2fef9e4a3230988ff17df2165440f37e8b1708", + "0xf4c64518ea10f995918a454158c6b61407ea345c", + "0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97", + "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", + "0x807640a13483f8ac783c557fcdf27be11ea4ac7a" + ], + "difficultyHardforkTransition": "0x59d9", + "difficultyHardforkBoundDivisor": "0x0200", + "bombDefuseTransition": "0x41", + "eip100bTransition": "0x42" + } + }"#; + + let deserialized: Ethash = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + Ethash { + params: EthashParams { + minimum_difficulty: Uint(U256::from(0x020000)), + difficulty_bound_divisor: Uint(U256::from(0x0800)), + difficulty_increment_divisor: None, + metropolis_difficulty_increment_divisor: None, + duration_limit: Some(Uint(U256::from(0x0d))), + homestead_transition: Some(Uint(U256::from(0x42))), + block_reward: Some(BlockReward::Single(Uint(U256::from(0x100)))), + block_reward_contract_address: None, + block_reward_contract_code: None, + block_reward_contract_transition: None, + dao_hardfork_transition: Some(Uint(U256::from(0x08))), + dao_hardfork_beneficiary: Some(Address( + H160::from_str("abcabcabcabcabcabcabcabcabcabcabcabcabca").unwrap() + )), + dao_hardfork_accounts: Some(vec![ + Address( + H160::from_str("304a554a310c7e546dfe434669c62820b7d83490").unwrap() + ), + Address( + H160::from_str("914d1b8b43e92723e64fd0a06f5bdb8dd9b10c79").unwrap() + ), + Address( + H160::from_str("fe24cdd8648121a43a7c86d289be4dd2951ed49f").unwrap() + ), + Address( + H160::from_str("17802f43a0137c506ba92291391a8a8f207f487d").unwrap() + ), + Address( + H160::from_str("b136707642a4ea12fb4bae820f03d2562ebff487").unwrap() + ), + Address( + H160::from_str("dbe9b615a3ae8709af8b93336ce9b477e4ac0940").unwrap() + ), + Address( + H160::from_str("f14c14075d6c4ed84b86798af0956deef67365b5").unwrap() + ), + Address( + H160::from_str("ca544e5c4687d109611d0f8f928b53a25af72448").unwrap() + ), + Address( + H160::from_str("aeeb8ff27288bdabc0fa5ebb731b6f409507516c").unwrap() + ), + Address( + H160::from_str("cbb9d3703e651b0d496cdefb8b92c25aeb2171f7").unwrap() + ), + Address( + H160::from_str("accc230e8a6e5be9160b8cdf2864dd2a001c28b6").unwrap() + ), + Address( + H160::from_str("2b3455ec7fedf16e646268bf88846bd7a2319bb2").unwrap() + ), + Address( + H160::from_str("4613f3bca5c44ea06337a9e439fbc6d42e501d0a").unwrap() + ), + Address( + H160::from_str("d343b217de44030afaa275f54d31a9317c7f441e").unwrap() + ), + Address( + H160::from_str("84ef4b2357079cd7a7c69fd7a37cd0609a679106").unwrap() + ), + Address( + H160::from_str("da2fef9e4a3230988ff17df2165440f37e8b1708").unwrap() + ), + Address( + H160::from_str("f4c64518ea10f995918a454158c6b61407ea345c").unwrap() + ), + Address( + H160::from_str("7602b46df5390e432ef1c307d4f2c9ff6d65cc97").unwrap() + ), + Address( + H160::from_str("bb9bc244d798123fde783fcc1c72d3bb8c189413").unwrap() + ), + Address( + H160::from_str("807640a13483f8ac783c557fcdf27be11ea4ac7a").unwrap() + ), + ]), + difficulty_hardfork_transition: Some(Uint(U256::from(0x59d9))), + difficulty_hardfork_bound_divisor: Some(Uint(U256::from(0x0200))), + bomb_defuse_transition: Some(Uint(U256::from(0x41))), + eip100b_transition: Some(Uint(U256::from(0x42))), + ecip1017_era_rounds: None, + expip2_transition: None, + expip2_duration_limit: None, + progpow_transition: None, + difficulty_bomb_delays: None, + } + } + ); + } + + #[test] + fn ethash_deserialization_missing_optionals() { + let s = r#"{ + "params": { + "difficultyBoundDivisor": "0x0800", + "minimumDifficulty": "0x020000" + } + }"#; + + let deserialized: Ethash = serde_json::from_str(s).unwrap(); + assert_eq!( + deserialized, + Ethash { + params: EthashParams { + minimum_difficulty: Uint(U256::from(0x020000)), + difficulty_bound_divisor: Uint(U256::from(0x0800)), + difficulty_increment_divisor: None, + metropolis_difficulty_increment_divisor: None, + duration_limit: None, + homestead_transition: None, + block_reward: None, + block_reward_contract_address: None, + block_reward_contract_code: None, + block_reward_contract_transition: None, + dao_hardfork_transition: None, + dao_hardfork_beneficiary: None, + dao_hardfork_accounts: None, + difficulty_hardfork_transition: None, + difficulty_hardfork_bound_divisor: None, + bomb_defuse_transition: None, + eip100b_transition: None, + ecip1017_era_rounds: None, + expip2_transition: None, + expip2_duration_limit: None, + progpow_transition: None, + difficulty_bomb_delays: None, + } + } + ); + } + + #[test] + #[should_panic(expected = "a non-zero value")] + fn test_zero_value_divisor() { + let s = r#"{ + "params": { + "difficultyBoundDivisor": "0x0", + "minimumDifficulty": "0x020000" + } + }"#; + + let _deserialized: Ethash = serde_json::from_str(s).unwrap(); + } +} diff --git a/evm-tests/ethjson/src/spec/genesis.rs b/evm-tests/ethjson/src/spec/genesis.rs new file mode 100644 index 000000000..018f69525 --- /dev/null +++ b/evm-tests/ethjson/src/spec/genesis.rs @@ -0,0 +1,127 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Spec genesis deserialization. + +use crate::{ + bytes::Bytes, + hash::{Address, H256}, + spec::Seal, + uint::{self, Uint}, +}; +use serde::Deserialize; + +/// Spec genesis. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct Genesis { + /// Seal. + pub seal: Seal, + /// Difficulty. + pub difficulty: Uint, + /// Block author, defaults to 0. + pub author: Option
, + /// Block timestamp, defaults to 0. + pub timestamp: Option, + /// Parent hash, defaults to 0. + pub parent_hash: Option, + /// Gas limit. + #[serde(deserialize_with = "uint::validate_non_zero")] + pub gas_limit: Uint, + /// Transactions root. + pub transactions_root: Option, + /// Receipts root. + pub receipts_root: Option, + /// State root. + pub state_root: Option, + /// Gas used. + pub gas_used: Option, + /// Extra data. + pub extra_data: Option, +} + +#[cfg(test)] +mod tests { + use super::{Address, Bytes, Genesis, Uint, H256}; + use crate::{ + hash::H64, + spec::{Ethereum, Seal}, + }; + use ethereum_types::{H160, H256 as Eth256, H64 as Eth64, U256}; + use std::str::FromStr; + + #[test] + fn genesis_deserialization() { + let s = r#"{ + "difficulty": "0x400000000", + "seal": { + "ethereum": { + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x00006d6f7264656e" + } + }, + "author": "0x1000000000000000000000000000000000000001", + "timestamp": "0x07", + "parentHash": "0x9000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "gasLimit": "0x1388", + "stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544" + }"#; + let deserialized: Genesis = serde_json::from_str(s).unwrap(); + assert_eq!( + deserialized, + Genesis { + seal: Seal::Ethereum(Ethereum { + nonce: H64(Eth64::from_str("00006d6f7264656e").unwrap()), + mix_hash: H256( + Eth256::from_str( + "0000000000000000000000000000000000000000000000000000000000000000" + ) + .unwrap() + ) + }), + difficulty: Uint(U256::from(0x400000000u64)), + author: Some(Address( + H160::from_str("1000000000000000000000000000000000000001").unwrap() + )), + timestamp: Some(Uint(U256::from(0x07))), + parent_hash: Some(H256( + Eth256::from_str( + "9000000000000000000000000000000000000000000000000000000000000000" + ) + .unwrap() + )), + gas_limit: Uint(U256::from(0x1388)), + transactions_root: None, + receipts_root: None, + state_root: Some(H256( + Eth256::from_str( + "d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544" + ) + .unwrap() + )), + gas_used: None, + extra_data: Some( + Bytes::from_str( + "11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa" + ) + .unwrap() + ), + } + ); + } +} diff --git a/evm-tests/ethjson/src/spec/hardcoded_sync.rs b/evm-tests/ethjson/src/spec/hardcoded_sync.rs new file mode 100644 index 000000000..d2703b996 --- /dev/null +++ b/evm-tests/ethjson/src/spec/hardcoded_sync.rs @@ -0,0 +1,62 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Spec hardcoded synchronization deserialization for the light client. + +use crate::{bytes::Bytes, hash::H256, uint::Uint}; +use serde::Deserialize; + +/// Spec hardcoded sync. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct HardcodedSync { + /// Hexadecimal of the RLP encoding of the header of the block to start synchronization from. + pub header: Bytes, + /// Total difficulty including the block of `header`. + pub total_difficulty: Uint, + /// Ordered trie roots of blocks before and including `header`. + #[serde(rename = "CHTs")] + pub chts: Vec, +} + +#[cfg(test)] +mod tests { + use super::{HardcodedSync, Uint, H256}; + use ethereum_types::{H256 as Eth256, U256}; + use std::str::FromStr; + + #[test] + fn hardcoded_sync_deserialization() { + let s = r#"{ + "header": "f901f9a0d405da4e66f1445d455195229624e133f5baafe72b5cf7b3c36c12c8146e98b7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a05fb2b4bfdef7b314451cb138a534d225c922fc0e5fbe25e451142732c3e25c25a088d2ec6b9860aae1a2c3b299f72b6a5d70d7f7ba4722c78f2c49ba96273c2158a007c6fdfa8eea7e86b81f5b0fc0f78f90cc19f4aa60d323151e0cac660199e9a1b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302008003832fefba82524d84568e932a80a0a0349d8c3df71f1a48a9df7d03fd5f14aeee7d91332c009ecaff0a71ead405bd88ab4e252a7e8c2a23", + "totalDifficulty": "0x400000000", + "CHTs": [ + "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544" + ] + }"#; + let deserialized: HardcodedSync = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized, HardcodedSync { + header: "f901f9a0d405da4e66f1445d455195229624e133f5baafe72b5cf7b3c36c12c8146e98b7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a05fb2b4bfdef7b314451cb138a534d225c922fc0e5fbe25e451142732c3e25c25a088d2ec6b9860aae1a2c3b299f72b6a5d70d7f7ba4722c78f2c49ba96273c2158a007c6fdfa8eea7e86b81f5b0fc0f78f90cc19f4aa60d323151e0cac660199e9a1b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302008003832fefba82524d84568e932a80a0a0349d8c3df71f1a48a9df7d03fd5f14aeee7d91332c009ecaff0a71ead405bd88ab4e252a7e8c2a23".parse().unwrap(), + total_difficulty: Uint(U256::from(0x400000000u64)), + chts: vec![ + H256(Eth256::from_str("11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa").unwrap()), + H256(Eth256::from_str("d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544").unwrap()), + ] + }); + } +} diff --git a/evm-tests/ethjson/src/spec/instant_seal.rs b/evm-tests/ethjson/src/spec/instant_seal.rs new file mode 100644 index 000000000..f54efe76c --- /dev/null +++ b/evm-tests/ethjson/src/spec/instant_seal.rs @@ -0,0 +1,37 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Instant seal engine params deserialization. + +use serde::Deserialize; + +/// Instant seal engine params deserialization. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct InstantSealParams { + /// Whether to enable millisecond timestamp. + #[serde(default)] + pub millisecond_timestamp: bool, +} + +/// Instant seal engine descriptor. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct InstantSeal { + /// Instant seal parameters. + pub params: InstantSealParams, +} diff --git a/evm-tests/ethjson/src/spec/mod.rs b/evm-tests/ethjson/src/spec/mod.rs new file mode 100644 index 000000000..17ff23dc7 --- /dev/null +++ b/evm-tests/ethjson/src/spec/mod.rs @@ -0,0 +1,54 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Spec deserialization. + +pub mod account; +pub mod authority_round; +pub mod basic_authority; +pub mod builtin; +pub mod clique; +pub mod engine; +pub mod ethash; +pub mod genesis; +pub mod hardcoded_sync; +pub mod instant_seal; +pub mod null_engine; +pub mod params; +pub mod seal; +#[allow(clippy::module_inception)] +pub mod spec; +pub mod state; +pub mod step_duration; +pub mod validator_set; + +pub use self::account::Account; +pub use self::authority_round::{AuthorityRound, AuthorityRoundParams}; +pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams}; +pub use self::builtin::{Builtin, Linear, Pricing}; +pub use self::clique::{Clique, CliqueParams}; +pub use self::engine::Engine; +pub use self::ethash::{BlockReward, Ethash, EthashParams}; +pub use self::genesis::Genesis; +pub use self::hardcoded_sync::HardcodedSync; +pub use self::instant_seal::{InstantSeal, InstantSealParams}; +pub use self::null_engine::{NullEngine, NullEngineParams}; +pub use self::params::Params; +pub use self::seal::{AuthorityRoundSeal, Ethereum, Seal, TendermintSeal}; +pub use self::spec::{ForkSpec, Spec}; +pub use self::state::{HashOrMap, State}; +pub use self::step_duration::StepDuration; +pub use self::validator_set::ValidatorSet; diff --git a/evm-tests/ethjson/src/spec/null_engine.rs b/evm-tests/ethjson/src/spec/null_engine.rs new file mode 100644 index 000000000..71bb790a1 --- /dev/null +++ b/evm-tests/ethjson/src/spec/null_engine.rs @@ -0,0 +1,60 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Null engine params deserialization. + +use crate::uint::Uint; +use serde::Deserialize; + +/// Authority params deserialization. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct NullEngineParams { + /// Block reward. + pub block_reward: Option, + /// Immediate finalization. + pub immediate_finalization: Option, +} + +/// Null engine descriptor +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct NullEngine { + /// Ethash params. + pub params: NullEngineParams, +} + +#[cfg(test)] +mod tests { + use super::{NullEngine, Uint}; + use ethereum_types::U256; + + #[test] + fn null_engine_deserialization() { + let s = r#"{ + "params": { + "blockReward": "0x0d" + } + }"#; + + let deserialized: NullEngine = serde_json::from_str(s).unwrap(); + assert_eq!( + deserialized.params.block_reward, + Some(Uint(U256::from(0x0d))) + ); + } +} diff --git a/evm-tests/ethjson/src/spec/params.rs b/evm-tests/ethjson/src/spec/params.rs new file mode 100644 index 000000000..38df7e1fe --- /dev/null +++ b/evm-tests/ethjson/src/spec/params.rs @@ -0,0 +1,203 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Spec params deserialization. + +use crate::{ + bytes::Bytes, + hash::{Address, H256}, + uint::{self, Uint}, +}; +use serde::Deserialize; + +/// Spec params. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct Params { + /// Account start nonce, defaults to 0. + pub account_start_nonce: Option, + /// Maximum size of extra data. + pub maximum_extra_data_size: Uint, + /// Minimum gas limit. + pub min_gas_limit: Uint, + + /// Network id. + #[serde(rename = "networkID")] + pub network_id: Uint, + /// Chain id. + #[serde(rename = "chainID")] + pub chain_id: Option, + + /// Name of the main ("eth") subprotocol. + pub subprotocol_name: Option, + + /// Option fork block number to check. + pub fork_block: Option, + /// Expected fork block hash. + #[serde(rename = "forkCanonHash")] + pub fork_hash: Option, + + /// See main EthashParams docs. + pub eip150_transition: Option, + + /// See main EthashParams docs. + pub eip160_transition: Option, + + /// See main EthashParams docs. + pub eip161abc_transition: Option, + /// See main EthashParams docs. + pub eip161d_transition: Option, + + /// See `CommonParams` docs. + pub eip98_transition: Option, + /// See `CommonParams` docs. + pub eip155_transition: Option, + /// See `CommonParams` docs. + pub validate_chain_id_transition: Option, + /// See `CommonParams` docs. + pub validate_receipts_transition: Option, + /// See `CommonParams` docs. + pub eip140_transition: Option, + /// See `CommonParams` docs. + pub eip210_transition: Option, + /// See `CommonParams` docs. + pub eip210_contract_address: Option
, + /// See `CommonParams` docs. + pub eip210_contract_code: Option, + /// See `CommonParams` docs. + pub eip210_contract_gas: Option, + /// See `CommonParams` docs. + pub eip211_transition: Option, + /// See `CommonParams` docs. + pub eip145_transition: Option, + /// See `CommonParams` docs. + pub eip214_transition: Option, + /// See `CommonParams` docs. + pub eip658_transition: Option, + /// See `CommonParams` docs. + pub eip1052_transition: Option, + /// See `CommonParams` docs. + pub eip1283_transition: Option, + /// See `CommonParams` docs. + pub eip1283_disable_transition: Option, + /// See `CommonParams` docs. + pub eip1283_reenable_transition: Option, + /// See `CommonParams` docs. + pub eip1014_transition: Option, + /// See `CommonParams` docs. + pub eip1706_transition: Option, + /// See `CommonParams` docs. + pub eip1344_transition: Option, + /// See `CommonParams` docs. + pub eip1884_transition: Option, + /// See `CommonParams` docs. + pub eip2028_transition: Option, + /// See `CommonParams` docs. + pub eip2046_transition: Option, + /// See `CommonParams` docs. + pub eip2200_advance_transition: Option, + /// See `CommonParams` docs. + pub eip2315_transition: Option, + /// See `CommonParams` docs. + pub dust_protection_transition: Option, + /// See `CommonParams` docs. + pub nonce_cap_increment: Option, + /// See `CommonParams` docs. + pub remove_dust_contracts: Option, + /// See `CommonParams` docs. + #[serde(deserialize_with = "uint::validate_non_zero")] + pub gas_limit_bound_divisor: Uint, + /// See `CommonParams` docs. + pub registrar: Option
, + /// Apply reward flag + pub apply_reward: Option, + /// Node permission contract address. + pub node_permission_contract: Option
, + /// See main EthashParams docs. + pub max_code_size: Option, + /// Maximum size of transaction RLP payload. + pub max_transaction_size: Option, + /// See main EthashParams docs. + pub max_code_size_transition: Option, + /// Transaction permission contract address. + pub transaction_permission_contract: Option
, + /// Block at which the transaction permission contract should start being used. + pub transaction_permission_contract_transition: Option, + /// Wasm activation block height, if not activated from start. + pub wasm_activation_transition: Option, + /// Define a separate wasm version instead of using the prefix. + pub wasm_version: Option, + /// KIP4 activiation block height. + pub kip4_transition: Option, + /// KIP6 activiation block height. + pub kip6_transition: Option, +} + +#[cfg(test)] +mod tests { + use super::{Params, Uint}; + use ethereum_types::U256; + + #[test] + fn params_deserialization() { + let s = r#"{ + "maximumExtraDataSize": "0x20", + "networkID": "0x1", + "chainID": "0x15", + "subprotocolName": "exp", + "minGasLimit": "0x1388", + "accountStartNonce": "0x01", + "gasLimitBoundDivisor": "0x20", + "maxCodeSize": "0x1000", + "wasmActivationTransition": "0x1010" + }"#; + + let deserialized: Params = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized.maximum_extra_data_size, Uint(U256::from(0x20))); + assert_eq!(deserialized.network_id, Uint(U256::from(0x1))); + assert_eq!(deserialized.chain_id, Some(Uint(U256::from(0x15)))); + assert_eq!(deserialized.subprotocol_name, Some("exp".to_owned())); + assert_eq!(deserialized.min_gas_limit, Uint(U256::from(0x1388))); + assert_eq!( + deserialized.account_start_nonce, + Some(Uint(U256::from(0x01))) + ); + assert_eq!(deserialized.gas_limit_bound_divisor, Uint(U256::from(0x20))); + assert_eq!(deserialized.max_code_size, Some(Uint(U256::from(0x1000)))); + assert_eq!( + deserialized.wasm_activation_transition, + Some(Uint(U256::from(0x1010))) + ); + } + + #[test] + #[should_panic(expected = "a non-zero value")] + fn test_zero_value_divisor() { + let s = r#"{ + "maximumExtraDataSize": "0x20", + "networkID" : "0x1", + "chainID" : "0x15", + "subprotocolName" : "exp", + "minGasLimit": "0x1388", + "accountStartNonce": "0x01", + "gasLimitBoundDivisor": "0x0", + "maxCodeSize": "0x1000" + }"#; + + let _deserialized: Params = serde_json::from_str(s).unwrap(); + } +} diff --git a/evm-tests/ethjson/src/spec/seal.rs b/evm-tests/ethjson/src/spec/seal.rs new file mode 100644 index 000000000..8148d9c3f --- /dev/null +++ b/evm-tests/ethjson/src/spec/seal.rs @@ -0,0 +1,144 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Spec seal deserialization. + +use crate::{ + bytes::Bytes, + hash::{H256, H520, H64}, + uint::Uint, +}; +use serde::Deserialize; + +/// Ethereum seal. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct Ethereum { + /// Seal nonce. + pub nonce: H64, + /// Seal mix hash. + pub mix_hash: H256, +} + +/// AuthorityRound seal. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct AuthorityRoundSeal { + /// Seal step. + pub step: Uint, + /// Seal signature. + pub signature: H520, +} + +/// Tendermint seal. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TendermintSeal { + /// Seal round. + pub round: Uint, + /// Proposal seal signature. + pub proposal: H520, + /// Proposal seal signature. + pub precommits: Vec, +} + +/// Seal variants. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub enum Seal { + /// Ethereum seal. + Ethereum(Ethereum), + /// AuthorityRound seal. + AuthorityRound(AuthorityRoundSeal), + /// Tendermint seal. + Tendermint(TendermintSeal), + /// Generic seal. + Generic(Bytes), +} + +#[cfg(test)] +mod tests { + use super::{AuthorityRoundSeal, Bytes, Ethereum, Seal, TendermintSeal, Uint, H256, H520, H64}; + use ethereum_types::{H256 as Eth256, H520 as Eth520, H64 as Eth64, U256}; + use std::str::FromStr; + + #[test] + fn seal_deserialization() { + let s = r#"[{ + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x1000000000000000000000000000000000000000000000000000000000000001" + } + },{ + "generic": "0xe011bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa" + },{ + "authorityRound": { + "step": "0x0", + "signature": "0x2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002" + } + },{ + "tendermint": { + "round": "0x3", + "proposal": "0x3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003", + "precommits": [ + "0x4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004" + ] + } + }]"#; + + let deserialized: Vec = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized.len(), 4); + + // [0] + assert_eq!( + deserialized[0], + Seal::Ethereum(Ethereum { + nonce: H64(Eth64::from_str("0000000000000042").unwrap()), + mix_hash: H256( + Eth256::from_str( + "1000000000000000000000000000000000000000000000000000000000000001" + ) + .unwrap() + ) + }) + ); + + // [1] + assert_eq!( + deserialized[1], + Seal::Generic(Bytes::new(vec![ + 0xe0, 0x11, 0xbb, 0xe8, 0xdb, 0x4e, 0x34, 0x7b, 0x4e, 0x8c, 0x93, 0x7c, 0x1c, 0x83, + 0x70, 0xe4, 0xb5, 0xed, 0x33, 0xad, 0xb3, 0xdb, 0x69, 0xcb, 0xdb, 0x7a, 0x38, 0xe1, + 0xe5, 0x0b, 0x1b, 0x82, 0xfa + ])) + ); + + // [2] + assert_eq!(deserialized[2], Seal::AuthorityRound(AuthorityRoundSeal { + step: Uint(U256::from(0x0)), + signature: H520(Eth520::from_str("2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002").unwrap()) + })); + + // [3] + assert_eq!(deserialized[3], Seal::Tendermint(TendermintSeal { + round: Uint(U256::from(0x3)), + proposal: H520(Eth520::from_str("3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003").unwrap()), + precommits: vec![H520(Eth520::from_str("4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004").unwrap())] + })); + } +} diff --git a/evm-tests/ethjson/src/spec/spec.rs b/evm-tests/ethjson/src/spec/spec.rs new file mode 100644 index 000000000..8493d81bc --- /dev/null +++ b/evm-tests/ethjson/src/spec/spec.rs @@ -0,0 +1,289 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Spec deserialization. + +use crate::spec::{Engine, Genesis, HardcodedSync, Params, State}; +use serde::Deserialize; +use serde_json::Error; +use std::io::Read; + +/// Fork spec definition +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)] +pub enum ForkSpec { + /// EIP 150 Tangerine Whistle: Gas cost changes for IO-heavy operations (#2,463,000, 2016-10-18) + EIP150, + /// EIP 158/EIP 161 Spurious Dragon: State trie clearing (#2,675,000, 2016-11-22) + EIP158, + /// Frontier (#1, 2015-07-30) + Frontier, + /// Homestead (#1,150,000, 2016-03-14) + Homestead, + /// Byzantium Metropolis phase 1 (#4,370,000, 2017-10-16) + Byzantium, + /// Constantinople Metropolis phase 2 (#7,280,000, 2019-02-28) + Constantinople, + /// Constantinople transition test-net + ConstantinopleFix, + /// Istanbul (#9,069,000, 2019-12-08) + Istanbul, + /// Berlin (#12,244,000, 2021-04-15) + Berlin, + /// London (#12,965,000, 2021-08-05) + London, + /// Paris - The Merge (#15,537,394, 2022-09-15) + Merge, + /// Shanghai (#17,034,870, 2023-04-12) + Shanghai, + /// Cancun + Cancun, + + /// Byzantium transition test-net + EIP158ToByzantiumAt5, + /// Homestead transition test-net + FrontierToHomesteadAt5, + /// Homestead transition test-net + HomesteadToDaoAt5, + /// EIP158/EIP161 transition test-net + HomesteadToEIP150At5, + /// ConstantinopleFix transition test-net + ByzantiumToConstantinopleFixAt5, + /// Istanbul transition test-net + ConstantinopleFixToIstanbulAt5, +} + +impl ForkSpec { + /// Returns true if the fork is at or after the merge. + pub const fn is_eth2(&self) -> bool { + // NOTE: Include new forks in this match arm. + matches!(*self, Self::London | Self::Merge | Self::Shanghai) + } +} + +/// Spec deserialization. +#[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct Spec { + /// Spec name. + pub name: String, + /// Special fork name. + pub data_dir: Option, + /// Engine. + pub engine: Engine, + /// Spec params. + pub params: Params, + /// Genesis header. + pub genesis: Genesis, + /// Genesis state. + pub accounts: State, + /// Boot nodes. + pub nodes: Option>, + /// Hardcoded synchronization for the light client. + pub hardcoded_sync: Option, +} + +impl Spec { + /// Loads test from json. + pub fn load(reader: R) -> Result + where + R: Read, + { + serde_json::from_reader(reader) + } +} + +#[cfg(test)] +mod tests { + use super::Spec; + + #[test] + fn should_error_on_unknown_fields() { + let s = r#"{ + "name": "Null Morden", + "dataDir": "morden", + "engine": { + "Ethash": { + "params": { + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "homesteadTransition" : "0x", + "daoHardforkTransition": "0xffffffffffffffff", + "daoHardforkBeneficiary": "0x0000000000000000000000000000000000000000", + "daoHardforkAccounts": [] + } + } + }, + "params": { + "accountStartNonce": "0x0100000", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x2", + "forkBlock": "0xffffffffffffffff", + "forkCanonHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimitBoundDivisor": "0x20", + "unknownField": "0x0" + }, + "genesis": { + "seal": { + "ethereum": { + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x00006d6f7264656e" + } + }, + "difficulty": "0x20000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x2fefd8" + }, + "nodes": [ + "enode://b1217cbaa440e35ed471157123fe468e19e8b5ad5bedb4b1fdbcbdab6fb2f5ed3e95dd9c24a22a79fdb2352204cea207df27d92bfd21bfd41545e8b16f637499@104.44.138.37:30303" + ], + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "nonce": "1048576", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" } + }, + "hardcodedSync": { + "header": "f901f9a0d405da4e66f1445d455195229624e133f5baafe72b5cf7b3c36c12c8146e98b7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a05fb2b4bfdef7b314451cb138a534d225c922fc0e5fbe25e451142732c3e25c25a088d2ec6b9860aae1a2c3b299f72b6a5d70d7f7ba4722c78f2c49ba96273c2158a007c6fdfa8eea7e86b81f5b0fc0f78f90cc19f4aa60d323151e0cac660199e9a1b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302008003832fefba82524d84568e932a80a0a0349d8c3df71f1a48a9df7d03fd5f14aeee7d91332c009ecaff0a71ead405bd88ab4e252a7e8c2a23", + "totalDifficulty": "0x400000000", + "CHTs": [ + "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544" + ] + } + }"#; + let result: Result = serde_json::from_str(s); + assert!(result.is_err()); + } + + #[test] + fn spec_deserialization() { + let s = r#"{ + "name": "Null Morden", + "dataDir": "morden", + "engine": { + "Ethash": { + "params": { + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "homesteadTransition" : "0x", + "daoHardforkTransition": "0xffffffffffffffff", + "daoHardforkBeneficiary": "0x0000000000000000000000000000000000000000", + "daoHardforkAccounts": [] + } + } + }, + "params": { + "accountStartNonce": "0x0100000", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x2", + "forkBlock": "0xffffffffffffffff", + "forkCanonHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimitBoundDivisor": "0x20" + }, + "genesis": { + "seal": { + "ethereum": { + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x00006d6f7264656e" + } + }, + "difficulty": "0x20000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x2fefd8" + }, + "nodes": [ + "enode://b1217cbaa440e35ed471157123fe468e19e8b5ad5bedb4b1fdbcbdab6fb2f5ed3e95dd9c24a22a79fdb2352204cea207df27d92bfd21bfd41545e8b16f637499@104.44.138.37:30303" + ], + "accounts": { + "0000000000000000000000000000000000000001": { + "balance": "1", + "nonce": "1048576", + "builtin": { + "name": "ecrecover", + "pricing": { + "linear": { + "base": 3000, + "word": 0 + } + } + } + }, + "0000000000000000000000000000000000000002": { + "balance": "1", + "nonce": "1048576", + "builtin": { + "name": "sha256", + "pricing": { + "linear": { + "base": 60, + "word": 12 + } + } + } + }, + "0000000000000000000000000000000000000003": { + "balance": "1", + "nonce": "1048576", + "builtin": { + "name": "ripemd160", + "pricing": { + "linear": { + "base": 600, + "word": 120 + } + } + } + }, + "0000000000000000000000000000000000000004": { + "balance": "1", + "nonce": "1048576", + "builtin": { + "name": "identity", + "pricing": { + "linear": { + "base": 15, + "word": 3 + } + } + } + }, + "102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" } + }, + "hardcodedSync": { + "header": "f901f9a0d405da4e66f1445d455195229624e133f5baafe72b5cf7b3c36c12c8146e98b7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a05fb2b4bfdef7b314451cb138a534d225c922fc0e5fbe25e451142732c3e25c25a088d2ec6b9860aae1a2c3b299f72b6a5d70d7f7ba4722c78f2c49ba96273c2158a007c6fdfa8eea7e86b81f5b0fc0f78f90cc19f4aa60d323151e0cac660199e9a1b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302008003832fefba82524d84568e932a80a0a0349d8c3df71f1a48a9df7d03fd5f14aeee7d91332c009ecaff0a71ead405bd88ab4e252a7e8c2a23", + "totalDifficulty": "0x400000000", + "CHTs": [ + "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544" + ] + } + }"#; + let _deserialized: Spec = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/evm-tests/ethjson/src/spec/state.rs b/evm-tests/ethjson/src/spec/state.rs new file mode 100644 index 000000000..c6ed45002 --- /dev/null +++ b/evm-tests/ethjson/src/spec/state.rs @@ -0,0 +1,81 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Blockchain state deserializer. + +use crate::{ + bytes::Bytes, + hash::{Address, H256}, + spec::{Account, Builtin}, +}; +use serde::Deserialize; +use std::collections::BTreeMap; + +/// Recent JSON tests can be either a map or a hash (represented by a string). +/// See https://github.com/ethereum/tests/issues/637 +#[cfg_attr(any(test, feature = "test-helpers"), derive(Clone))] +#[derive(Debug, PartialEq, Deserialize)] +#[serde(untagged)] +pub enum HashOrMap { + /// When the `postState` is large, tests sometimes just include the state root of the last + /// successful block here. + Hash(H256), + /// The expected `postState` of a test + Map(BTreeMap), +} + +/// Blockchain state deserializer. +#[cfg_attr(any(test, feature = "test-helpers"), derive(Clone))] +#[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct State(pub HashOrMap); + +impl State { + /// Returns all builtins. + pub fn builtins(&self) -> BTreeMap { + match &self.0 { + HashOrMap::Hash(_) => BTreeMap::default(), + HashOrMap::Map(map) => map + .iter() + .filter_map(|(add, acc)| acc.builtin.clone().map(|b| (*add, b.into()))) + .collect(), + } + } + + /// Returns all constructors. + pub fn constructors(&self) -> BTreeMap { + match &self.0 { + HashOrMap::Hash(_) => BTreeMap::default(), + HashOrMap::Map(map) => map + .iter() + .filter_map(|(add, acc)| acc.constructor.clone().map(|b| (*add, b))) + .collect(), + } + } +} + +impl IntoIterator for State { + type Item = as IntoIterator>::Item; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + if let HashOrMap::Map(m) = self.0 { + m.into_iter() + } else { + BTreeMap::default().into_iter() + } + } +} diff --git a/evm-tests/ethjson/src/spec/step_duration.rs b/evm-tests/ethjson/src/spec/step_duration.rs new file mode 100644 index 000000000..0c082d36a --- /dev/null +++ b/evm-tests/ethjson/src/spec/step_duration.rs @@ -0,0 +1,36 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Step duration configuration parameter + +use std::collections::BTreeMap; + +use serde::Deserialize; + +use crate::uint::Uint; + +/// Step duration can be specified either as a `Uint` (in seconds), in which case it will be +/// constant, or as a list of pairs consisting of a timestamp of type `Uint` and a duration, in +/// which case the duration of a step will be determined by a mapping arising from that list. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum StepDuration { + /// Duration of all steps. + Single(Uint), + /// Step duration transitions: a mapping of timestamp to step durations. + Transitions(BTreeMap), +} diff --git a/evm-tests/ethjson/src/spec/validator_set.rs b/evm-tests/ethjson/src/spec/validator_set.rs new file mode 100644 index 000000000..22676dcb0 --- /dev/null +++ b/evm-tests/ethjson/src/spec/validator_set.rs @@ -0,0 +1,91 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Validator set deserialization. + +use crate::{hash::Address, uint::Uint}; +use serde::Deserialize; +use std::collections::BTreeMap; + +/// Different ways of specifying validators. +#[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub enum ValidatorSet { + /// A simple list of authorities. + List(Vec
), + /// Address of a contract that indicates the list of authorities. + SafeContract(Address), + /// Address of a contract that indicates the list of authorities and enables reporting of theor misbehaviour using transactions. + Contract(Address), + /// A map of starting blocks for each validator set. + Multi(BTreeMap), +} + +#[cfg(test)] +mod tests { + use super::{Address, Uint, ValidatorSet}; + use ethereum_types::{H160, U256}; + use std::str::FromStr; + + #[test] + fn validator_set_deserialization() { + let s = r#"[{ + "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + }, { + "safeContract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + }, { + "contract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + }, { + "multi": { + "0": { "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] }, + "10": { "list": ["0xd6d9d2cd449a754c494264e1809c50e34d64562b"] }, + "20": { "contract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b" } + } + }]"#; + + let deserialized: Vec = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized.len(), 4); + + assert_eq!( + deserialized[0], + ValidatorSet::List(vec![Address( + H160::from_str("c6d9d2cd449a754c494264e1809c50e34d64562b").unwrap() + )]) + ); + assert_eq!( + deserialized[1], + ValidatorSet::SafeContract(Address( + H160::from_str("c6d9d2cd449a754c494264e1809c50e34d64562b").unwrap() + )) + ); + assert_eq!( + deserialized[2], + ValidatorSet::Contract(Address( + H160::from_str("c6d9d2cd449a754c494264e1809c50e34d64562b").unwrap() + )) + ); + match deserialized[3] { + ValidatorSet::Multi(ref map) => { + assert_eq!(map.len(), 3); + assert!(map.contains_key(&Uint(U256::from(0)))); + assert!(map.contains_key(&Uint(U256::from(10)))); + assert!(map.contains_key(&Uint(U256::from(20)))); + } + _ => panic!(), + } + } +} diff --git a/evm-tests/ethjson/src/state.rs b/evm-tests/ethjson/src/state.rs new file mode 100644 index 000000000..7d894bd7e --- /dev/null +++ b/evm-tests/ethjson/src/state.rs @@ -0,0 +1,55 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! State deserialization types + +use crate::{ + bytes::Bytes, + hash::{Address, Bloom, H256}, +}; +use serde::Deserialize; + +/// State log deserialization. +#[derive(Debug, PartialEq, Eq, Deserialize)] +pub struct Log { + /// Address. + pub address: Address, + /// Topics. + pub topics: Vec, + /// Data. + pub data: Bytes, + /// Bloom. + pub bloom: Bloom, +} + +#[cfg(test)] +mod tests { + use super::Log; + + #[test] + fn log_deserialization() { + let s = r#"{ + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "bloom" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008800000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "data" : "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "topics" : [ + "0000000000000000000000000000000000000000000000000000000000000000" + ] + }"#; + let _deserialized: Log = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/evm-tests/ethjson/src/test_helpers/blockchain/block.rs b/evm-tests/ethjson/src/test_helpers/blockchain/block.rs new file mode 100644 index 000000000..c9cbf2700 --- /dev/null +++ b/evm-tests/ethjson/src/test_helpers/blockchain/block.rs @@ -0,0 +1,74 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Blockchain test block deserializer. + +use super::header::Header; +use crate::{bytes::Bytes, transaction::Transaction}; +use serde::Deserialize; + +/// Blockchain test block deserializer. +#[derive(Debug, PartialEq, Eq, Deserialize)] +pub struct Block { + #[serde(rename = "blockHeader")] + header: Option
, + rlp: Bytes, + transactions: Option>, + #[serde(rename = "uncleHeaders")] + uncles: Option>, +} + +impl Block { + /// Returns block rlp. + pub fn rlp(&self) -> Vec { + self.rlp.clone().into() + } +} + +#[cfg(test)] +mod tests { + use super::Block; + + #[test] + fn block_deserialization() { + let s = r#"{ + "blockHeader" : { + "bloom" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "coinbase" : "8888f1f195afa192cfee860698584c030f4c9db1", + "difficulty" : "0x020000", + "extraData" : "0x", + "gasLimit" : "0x2fefba", + "gasUsed" : "0x00", + "hash" : "65ebf1b97fb89b14680267e0723d69267ec4bf9a96d4a60ffcb356ae0e81c18f", + "mixHash" : "13735ab4156c9b36327224d92e1692fab8fc362f8e0f868c94d421848ef7cd06", + "nonce" : "931dcc53e5edc514", + "number" : "0x01", + "parentHash" : "5a39ed1020c04d4d84539975b893a4e7c53eab6c2965db8bc3468093a31bc5ae", + "receiptTrie" : "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "stateRoot" : "c5c83ff43741f573a0c9b31d0e56fdd745f4e37d193c4e78544f302777aafcf3", + "timestamp" : "0x56850b7b", + "transactionsTrie" : "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "uncleHash" : "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + "blocknumber" : "1", + "rlp" : "0xf901fcf901f7a05a39ed1020c04d4d84539975b893a4e7c53eab6c2965db8bc3468093a31bc5aea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0c5c83ff43741f573a0c9b31d0e56fdd745f4e37d193c4e78544f302777aafcf3a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefba808456850b7b80a013735ab4156c9b36327224d92e1692fab8fc362f8e0f868c94d421848ef7cd0688931dcc53e5edc514c0c0", + "transaction" : [], + "uncleHeaders" : [] + }"#; + let _deserialized: Block = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/evm-tests/ethjson/src/test_helpers/blockchain/header.rs b/evm-tests/ethjson/src/test_helpers/blockchain/header.rs new file mode 100644 index 000000000..4d512fc07 --- /dev/null +++ b/evm-tests/ethjson/src/test_helpers/blockchain/header.rs @@ -0,0 +1,95 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Blockchain test header deserializer. + +use crate::{ + bytes::Bytes, + hash::{Address, Bloom, H256, H64}, + uint::Uint, +}; +use serde::Deserialize; + +/// Blockchain test header deserializer. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Header { + /// Blocks bloom. + pub bloom: Bloom, + /// Blocks author. + #[serde(rename = "coinbase")] + pub author: Address, + /// Difficulty. + pub difficulty: Uint, + /// Extra data. + pub extra_data: Bytes, + /// Gas limit. + pub gas_limit: Uint, + /// Gas used. + pub gas_used: Uint, + /// Hash. + pub hash: H256, + /// Mix hash. + pub mix_hash: H256, + /// Seal nonce. + pub nonce: H64, + /// Block number. + pub number: Uint, + /// Parent hash. + pub parent_hash: H256, + /// Receipt root. + #[serde(rename = "receiptTrie")] + pub receipts_root: H256, + /// State root. + pub state_root: H256, + /// Timestamp. + pub timestamp: Uint, + /// Transactions root. + #[serde(rename = "transactionsTrie")] + pub transactions_root: H256, + /// Uncles hash. + #[serde(rename = "uncleHash")] + pub uncles_hash: H256, +} + +#[cfg(test)] +mod tests { + use super::Header; + + #[test] + fn header_deserialization() { + let s = r#"{ + "bloom" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "coinbase" : "8888f1f195afa192cfee860698584c030f4c9db1", + "difficulty" : "0x020000", + "extraData" : "0x", + "gasLimit" : "0x2fefba", + "gasUsed" : "0x00", + "hash" : "65ebf1b97fb89b14680267e0723d69267ec4bf9a96d4a60ffcb356ae0e81c18f", + "mixHash" : "13735ab4156c9b36327224d92e1692fab8fc362f8e0f868c94d421848ef7cd06", + "nonce" : "931dcc53e5edc514", + "number" : "0x01", + "parentHash" : "5a39ed1020c04d4d84539975b893a4e7c53eab6c2965db8bc3468093a31bc5ae", + "receiptTrie" : "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "stateRoot" : "c5c83ff43741f573a0c9b31d0e56fdd745f4e37d193c4e78544f302777aafcf3", + "timestamp" : "0x56850b7b", + "transactionsTrie" : "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "uncleHash" : "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }"#; + let _deserialized: Header = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/evm-tests/ethjson/src/test_helpers/blockchain/mod.rs b/evm-tests/ethjson/src/test_helpers/blockchain/mod.rs new file mode 100644 index 000000000..739359506 --- /dev/null +++ b/evm-tests/ethjson/src/test_helpers/blockchain/mod.rs @@ -0,0 +1,207 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Blockchain test deserialization. + +use crate::{ + bytes::Bytes, + hash::H256, + spec::{Ethereum, ForkSpec, Genesis, Seal, State}, +}; +use serde::Deserialize; + +pub mod block; +pub mod header; + +pub use self::block::Block; +pub use self::header::Header; + +/// Type for running `Blockchain` tests +pub type Test = super::tester::GenericTester; + +/// Json Block test possible engine kind. +#[derive(Debug, Default, PartialEq, Eq, Deserialize)] +pub enum Engine { + /// Default (old) behaviour. + #[default] + Ethash, + /// No check of block's difficulty and nonce for tests. + NoProof, +} + +/// Blockchain deserialization. +#[derive(Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BlockChain { + /// Genesis block header. + #[serde(rename = "genesisBlockHeader")] + pub genesis_block: Header, + /// Genesis block rlp. + #[serde(rename = "genesisRLP")] + pub genesis_rlp: Option, + /// Blocks. + pub blocks: Vec, + /// Post state. + pub post_state: Option, + /// Pre state. + #[serde(rename = "pre")] + pub pre_state: State, + /// Hash of best block. + #[serde(rename = "lastblockhash")] + pub best_block: H256, + /// Network. + pub network: ForkSpec, + #[serde(default)] + #[serde(rename = "sealEngine")] + /// Engine + pub engine: Engine, +} + +impl BlockChain { + /// Returns blocks rlp. + pub fn blocks_rlp(&self) -> Vec> { + self.blocks.iter().map(|block| block.rlp()).collect() + } + + /// Returns spec compatible genesis struct. + pub fn genesis(&self) -> Genesis { + Genesis { + seal: Seal::Ethereum(Ethereum { + nonce: self.genesis_block.nonce, + mix_hash: self.genesis_block.mix_hash, + }), + difficulty: self.genesis_block.difficulty, + author: Some(self.genesis_block.author), + timestamp: Some(self.genesis_block.timestamp), + parent_hash: Some(self.genesis_block.parent_hash), + gas_limit: self.genesis_block.gas_limit, + transactions_root: Some(self.genesis_block.transactions_root), + receipts_root: Some(self.genesis_block.receipts_root), + state_root: Some(self.genesis_block.state_root), + gas_used: Some(self.genesis_block.gas_used), + extra_data: Some(self.genesis_block.extra_data.clone()), + } + } +} + +#[cfg(test)] +mod tests { + use super::BlockChain; + + #[test] + fn blockchain_deserialization() { + let s = r#"{ + "blocks" : [{ + "blockHeader" : { + "bloom" : "00000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000040000000000000000000000000000000000000000000000000000000", + "coinbase" : "8888f1f195afa192cfee860698584c030f4c9db1", + "difficulty" : "0x020000", + "extraData" : "0x0102030405060708091011121314151617181920212223242526272829303132", + "gasLimit" : "0x2fefba", + "gasUsed" : "0x560b", + "hash" : "06b5b1742bde29468510c92641f36b719c61b3fc3e9a21c92a23978f4f7faa2a", + "mixHash" : "5266ca43e81d25925a9ba573c3e4f9180bc076d316d90e63c6f8708b272f5ce2", + "nonce" : "59ba4daed1898e21", + "number" : "0x01", + "parentHash" : "f052d217bd5275a5177a3c3b7debdfe2670f1c8394b2965ccd5c1883cc1a524d", + "receiptTrie" : "c7778a7376099ee2e5c455791c1885b5c361b95713fddcbe32d97fd01334d296", + "stateRoot" : "bac6177a79e910c98d86ec31a09ae37ac2de15b754fd7bed1ba52362c49416bf", + "timestamp" : "0x56850c2c", + "transactionsTrie" : "498785da562aa0c5dd5937cf15f22139b0b1bcf3b4fc48986e1bb1dae9292796", + "uncleHash" : "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + "rlp" : "0xf90285f90219a0f052d217bd5275a5177a3c3b7debdfe2670f1c8394b2965ccd5c1883cc1a524da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0bac6177a79e910c98d86ec31a09ae37ac2de15b754fd7bed1ba52362c49416bfa0498785da562aa0c5dd5937cf15f22139b0b1bcf3b4fc48986e1bb1dae9292796a0c7778a7376099ee2e5c455791c1885b5c361b95713fddcbe32d97fd01334d296b90100000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000400000000000000000000000000000000000000000000000000000008302000001832fefba82560b8456850c2ca00102030405060708091011121314151617181920212223242526272829303132a05266ca43e81d25925a9ba573c3e4f9180bc076d316d90e63c6f8708b272f5ce28859ba4daed1898e21f866f864800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d8785012a05f200801ca0ee0b9ec878fbd4258a9473199d8ecc32996a20c323c004e79e0cda20e0418ce3a04e6bc63927d1510bab54f37e46fa036faf4b2c465d271920d9afea1fadf7bd21c0", + "transactions" : [ + { + "data" : "0x00", + "gasLimit" : "0xc350", + "gasPrice" : "0x0a", + "nonce" : "0x00", + "r" : "0xee0b9ec878fbd4258a9473199d8ecc32996a20c323c004e79e0cda20e0418ce3", + "s" : "0x4e6bc63927d1510bab54f37e46fa036faf4b2c465d271920d9afea1fadf7bd21", + "to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87", + "v" : "0x1c", + "value" : "0x012a05f200" + } + ], + "uncleHeaders" : [ + ] + }], + "network" : "Frontier", + "genesisBlockHeader" : { + "bloom" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "coinbase" : "8888f1f195afa192cfee860698584c030f4c9db1", + "difficulty" : "0x020000", + "extraData" : "0x42", + "gasLimit" : "0x2fefd8", + "gasUsed" : "0x00", + "hash" : "f052d217bd5275a5177a3c3b7debdfe2670f1c8394b2965ccd5c1883cc1a524d", + "mixHash" : "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "nonce" : "0102030405060708", + "number" : "0x00", + "parentHash" : "0000000000000000000000000000000000000000000000000000000000000000", + "receiptTrie" : "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "stateRoot" : "925002c3260b44e44c3edebad1cc442142b03020209df1ab8bb86752edbd2cd7", + "timestamp" : "0x54c98c81", + "transactionsTrie" : "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "uncleHash" : "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + "genesisRLP" : "0xf901fcf901f7a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0925002c3260b44e44c3edebad1cc442142b03020209df1ab8bb86752edbd2cd7a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000080832fefd8808454c98c8142a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421880102030405060708c0c0", + "lastblockhash" : "06b5b1742bde29468510c92641f36b719c61b3fc3e9a21c92a23978f4f7faa2a", + "postState" : { + "095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x012a05f264", + "code" : "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600052600060206000a1", + "nonce" : "0x00", + "storage" : { + } + }, + "8888f1f195afa192cfee860698584c030f4c9db1" : { + "balance" : "0x4563918244f75c6e", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x012a029592", + "code" : "0x", + "nonce" : "0x01", + "storage" : { + } + } + }, + "pre" : { + "095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x64", + "code" : "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600052600060206000a1", + "nonce" : "0x00", + "storage" : { + } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x02540be400", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } + } + }"#; + let _deserialized: BlockChain = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/evm-tests/ethjson/src/test_helpers/difficulty.rs b/evm-tests/ethjson/src/test_helpers/difficulty.rs new file mode 100644 index 000000000..b00e4a850 --- /dev/null +++ b/evm-tests/ethjson/src/test_helpers/difficulty.rs @@ -0,0 +1,39 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +use crate::{hash::H256, uint::Uint}; +use serde::Deserialize; + +/// Blockchain test header deserializer. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DifficultyTestCase { + /// Parent timestamp. + pub parent_timestamp: Uint, + /// Parent difficulty. + pub parent_difficulty: Uint, + /// Parent uncle hash. + pub parent_uncles: H256, + /// Current timestamp. + pub current_timestamp: Uint, + /// Current difficulty. + pub current_difficulty: Uint, + /// Current block number. + pub current_block_number: Uint, +} + +/// Type for running `Difficulty` tests +pub type DifficultyTest = super::tester::GenericTester; diff --git a/evm-tests/ethjson/src/test_helpers/mod.rs b/evm-tests/ethjson/src/test_helpers/mod.rs new file mode 100644 index 000000000..70a3b1e93 --- /dev/null +++ b/evm-tests/ethjson/src/test_helpers/mod.rs @@ -0,0 +1,37 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Test structures for JSON deserialization. + +/// Blockchain test helpers +pub mod blockchain; +/// Difficulty test helpers +pub mod difficulty; +/// Tests to skip helpers +pub mod skip; +/// State test helpers +pub mod state; +/// Test primitives +pub mod tester; +/// Transaction test helpers +pub mod transaction; +/// Trie test helpers +pub mod trie; +/// Vm test helpers +pub mod vm { + /// Type for running `vm` tests + pub type Test = super::tester::GenericTester; +} diff --git a/evm-tests/ethjson/src/test_helpers/skip.rs b/evm-tests/ethjson/src/test_helpers/skip.rs new file mode 100644 index 000000000..38a319383 --- /dev/null +++ b/evm-tests/ethjson/src/test_helpers/skip.rs @@ -0,0 +1,82 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +use serde::Deserialize; +use std::collections::BTreeMap; + +/// Test to skip (only if issue ongoing) +#[derive(Debug, PartialEq, Eq, Deserialize)] +pub struct SkipTests { + /// Block tests + pub block: Vec, + /// State tests + pub state: Vec, + /// Legacy block tests + pub legacy_block: Vec, + /// Legacy state tests + pub legacy_state: Vec, +} + +/// Block test to skip. +#[derive(Debug, PartialEq, Eq, Deserialize)] +pub struct SkipBlockchainTest { + /// Issue reference. + pub reference: String, + /// Test failing name. + pub failing: String, + /// Items failing for the test. + pub subtests: Vec, +} + +/// State test to skip. +#[derive(Debug, PartialEq, Eq, Deserialize)] +pub struct SkipStateTest { + /// Issue reference. + pub reference: String, + /// Test failing name. + pub failing: String, + /// Items failing for the test. + pub subtests: BTreeMap, +} + +/// State subtest to skip. +#[derive(Debug, PartialEq, Eq, Deserialize)] +pub struct StateSkipSubStates { + /// State test number of this item. Or '*' for all state. + pub subnumbers: Vec, + /// Chain for this items. + pub chain: String, +} + +impl SkipTests { + /// Empty skip states. + pub fn empty() -> Self { + Self { + block: Vec::new(), + state: Vec::new(), + legacy_block: Vec::new(), + legacy_state: Vec::new(), + } + } + + /// Loads test from json. + pub fn load(reader: R) -> Result + where + R: std::io::Read, + { + serde_json::from_reader(reader) + } +} diff --git a/evm-tests/ethjson/src/test_helpers/state.rs b/evm-tests/ethjson/src/test_helpers/state.rs new file mode 100644 index 000000000..34fef578c --- /dev/null +++ b/evm-tests/ethjson/src/test_helpers/state.rs @@ -0,0 +1,277 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! State test deserialization. + +/// Type for running `State` tests +pub type Test = super::tester::GenericTester; + +use crate::{ + bytes::Bytes, + hash::{Address, H256}, + maybe::MaybeEmpty, + spec::{ForkSpec, State as AccountState}, + transaction::Transaction, + uint::Uint, + vm::Env, +}; +use ethereum_types::U256; +use serde::Deserialize; +use std::collections::BTreeMap; + +/// State test deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct State { + /// Environment. + pub env: Env, + /// Pre state. + #[serde(rename = "pre")] + pub pre_state: AccountState, + /// Post state. + #[serde(rename = "post")] + pub post_states: BTreeMap>, + /// Transaction. + pub transaction: MultiTransaction, +} + +/// State test transaction deserialization. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MultiTransaction { + /// Transaction data set. + pub data: Vec, + /// Access lists (see EIP-2930) + #[serde(default)] + pub access_lists: Vec>, + /// Gas limit set. + pub gas_limit: Vec, + /// Gas price. + #[serde(default)] + pub gas_price: Uint, + /// for details on `maxFeePerGas` see EIP-1559 + #[serde(default)] + pub max_fee_per_gas: Uint, + /// for details on `maxPriorityFeePerGas` see EIP-1559 + #[serde(default)] + pub max_priority_fee_per_gas: Uint, + /// Nonce. + pub nonce: Uint, + /// Secret key. + #[serde(rename = "secretKey")] + pub secret: Option, + /// To. + pub to: MaybeEmpty
, + /// Value set. + pub value: Vec, +} + +impl MultiTransaction { + /// max_priority_fee_per_gas (see EIP-1559) + pub const fn max_priority_fee_per_gas(&self) -> U256 { + if self.max_priority_fee_per_gas.0.is_zero() { + self.gas_price.0 + } else { + self.max_priority_fee_per_gas.0 + } + } + + /// max_fee_per_gas (see EIP-1559) + pub const fn max_fee_per_gas(&self) -> U256 { + if self.max_fee_per_gas.0.is_zero() { + self.gas_price.0 + } else { + self.max_fee_per_gas.0 + } + } + + /// Build transaction with given indexes. + pub fn select(&self, indexes: &PostStateIndexes) -> Transaction { + let data_index = indexes.data as usize; + let access_list = if data_index < self.access_lists.len() { + self.access_lists + .get(data_index) + .unwrap() + .as_ref() + .cloned() + .unwrap_or_default() + .into_iter() + .map(|a| (a.address, a.storage_keys)) + .collect() + } else { + Vec::new() + }; + + let gas_price = if self.gas_price.0.is_zero() { + self.max_fee_per_gas.0 + self.max_priority_fee_per_gas.0 + } else { + self.gas_price.0 + }; + + Transaction { + data: self.data[data_index].clone(), + gas_limit: self.gas_limit[indexes.gas as usize], + gas_price: Uint(gas_price), + nonce: self.nonce, + to: self.to.clone(), + value: self.value[indexes.value as usize], + r: Default::default(), + s: Default::default(), + v: Default::default(), + secret: self.secret, + access_list, + } + } +} + +/// Type alias for access lists (see EIP-2930) +pub type AccessList = Vec; + +/// Access list tuple (see https://eips.ethereum.org/EIPS/eip-2930). +/// Example test spec: https://github.com/ethereum/tests/blob/5490db3ff58d371c0c74826280256ba016b0bd5c/GeneralStateTests/stExample/accessListExample.json +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccessListTuple { + /// Address to access + pub address: Address, + /// Keys (slots) to access at that address + pub storage_keys: Vec, +} + +/// State test indexes deserialization. +#[derive(Debug, PartialEq, Eq, Deserialize)] +pub struct PostStateIndexes { + /// Index into transaction data set. + pub data: u64, + /// Index into transaction gas limit set. + pub gas: u64, + /// Index into transaction value set. + pub value: u64, +} + +/// State test indexed state result deserialization. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PostStateResult { + /// Post state hash + pub hash: H256, + /// Indexes + pub indexes: PostStateIndexes, + /// Expected error if the test is meant to fail + pub expect_exception: Option, + /// Transaction bytes + pub txbytes: Bytes, +} + +#[cfg(test)] +mod tests { + use super::{MultiTransaction, State}; + use serde_json; + + #[test] + fn multi_transaction_deserialization() { + let s = r#"{ + "data": [ "" ], + "gasLimit": [ "0x2dc6c0", "0x222222" ], + "gasPrice": "0x01", + "nonce": "0x00", + "secretKey": "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "to": "1000000000000000000000000000000000000000", + "value": [ "0x00", "0x01", "0x02" ] + }"#; + let _deserialized: MultiTransaction = serde_json::from_str(s).unwrap(); + } + + #[test] + fn state_deserialization() { + let s = r#"{ + "env": { + "currentCoinbase": "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty": "0x0100", + "currentGasLimit": "0x01c9c380", + "currentNumber": "0x00", + "currentTimestamp": "0x01", + "previousHash": "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6" + }, + "post": { + "EIP150": [ + { + "hash": "3e6dacc1575c6a8c76422255eca03529bbf4c0dda75dfc110b22d6dc4152396f", + "txbytes" : "0xf861800a84042c1d8094b94f5374fce5edbc8e2a8697c15331677e6ebf0b80801ca0f141d67812db948c9a4ea43c27d695248205c121ae8d924d23517ab09e38f369a03fe3cfedb4c9a7e61340b6fec87917690e92082f752ad820ad5006c7d49185ed", + "indexes": { "data": 0, "gas": 0, "value": 0 } + }, + { + "hash": "99a450d8ce5b987a71346d8a0a1203711f770745c7ef326912e46761f14cd764", + "txbytes" : "0xf861800a84042c1d8094b94f5374fce5edbc8e2a8697c15331677e6ebf0b80801ca0f141d67812db948c9a4ea43c27d695248205c121ae8d924d23517ab09e38f369a03fe3cfedb4c9a7e61340b6fec87917690e92082f752ad820ad5006c7d49185ed", + "indexes": { "data": 0, "gas": 0, "value": 1 } + } + ], + "EIP158": [ + { + "hash": "3e6dacc1575c6a8c76422255eca03529bbf4c0dda75dfc110b22d6dc4152396f", + "txbytes" : "0xf861800a84042c1d8094b94f5374fce5edbc8e2a8697c15331677e6ebf0b80801ca0f141d67812db948c9a4ea43c27d695248205c121ae8d924d23517ab09e38f369a03fe3cfedb4c9a7e61340b6fec87917690e92082f752ad820ad5006c7d49185ed", + "indexes": { "data": 0, "gas": 0, "value": 0 } + }, + { + "hash": "99a450d8ce5b987a71346d8a0a1203711f770745c7ef326912e46761f14cd764", + "txbytes" : "0xf861800a84042c1d8094b94f5374fce5edbc8e2a8697c15331677e6ebf0b80801ca0f141d67812db948c9a4ea43c27d695248205c121ae8d924d23517ab09e38f369a03fe3cfedb4c9a7e61340b6fec87917690e92082f752ad820ad5006c7d49185ed", + "indexes": { "data": 0, "gas": 0, "value": 1 } + } + ] + }, + "pre": { + "1000000000000000000000000000000000000000": { + "balance": "0x0de0b6b3a7640000", + "code": "0x6040600060406000600173100000000000000000000000000000000000000162055730f1600055", + "nonce": "0x00", + "storage": { + } + }, + "1000000000000000000000000000000000000001": { + "balance": "0x0de0b6b3a7640000", + "code": "0x604060006040600060027310000000000000000000000000000000000000026203d090f1600155", + "nonce": "0x00", + "storage": { + } + }, + "1000000000000000000000000000000000000002": { + "balance": "0x00", + "code": "0x600160025533600455346007553060e6553260e8553660ec553860ee553a60f055", + "nonce": "0x00", + "storage": { + } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x0de0b6b3a7640000", + "code": "0x", + "nonce": "0x00", + "storage": { + } + } + }, + "transaction": { + "data": [ "" ], + "gasLimit": [ "285000", "100000", "6000" ], + "gasPrice": "0x01", + "nonce": "0x00", + "secretKey": "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "to": "095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value": [ "10", "0" ] + } + }"#; + let _deserialized: State = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/evm-tests/ethjson/src/test_helpers/tester.rs b/evm-tests/ethjson/src/test_helpers/tester.rs new file mode 100644 index 000000000..947aa6dff --- /dev/null +++ b/evm-tests/ethjson/src/test_helpers/tester.rs @@ -0,0 +1,46 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +use serde::de::DeserializeOwned; +use serde::Deserialize; +use std::collections::BTreeMap; + +/// A genric wrapper over a `BTreeMap` for tests +#[derive(Deserialize)] +pub struct GenericTester(BTreeMap); + +impl IntoIterator for GenericTester { + type Item = as IntoIterator>::Item; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl GenericTester +where + T: DeserializeOwned + Ord, + U: DeserializeOwned, +{ + /// Loads test from json. + pub fn load(reader: R) -> Result + where + R: std::io::Read, + { + serde_json::from_reader(reader) + } +} diff --git a/evm-tests/ethjson/src/test_helpers/transaction.rs b/evm-tests/ethjson/src/test_helpers/transaction.rs new file mode 100644 index 000000000..6163e6740 --- /dev/null +++ b/evm-tests/ethjson/src/test_helpers/transaction.rs @@ -0,0 +1,90 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Transaction test deserialization. + +use crate::{ + bytes::Bytes, + hash::{Address, H256}, + spec::ForkSpec, +}; +use serde::Deserialize; +use std::collections::BTreeMap; + +/// Type for running `Transaction` tests +pub type Test = super::tester::GenericTester; + +/// Transaction test deserialization. +#[derive(Debug, Deserialize)] +pub struct TransactionTest { + /// RLP of the transaction + pub rlp: Bytes, + #[allow(missing_docs)] + pub _info: serde::de::IgnoredAny, + /// State of the transaction after the test runs + #[serde(flatten)] + pub post_state: BTreeMap, +} + +/// TransactionTest post state. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PostState { + /// Transaction sender. + pub sender: Option
, + /// Transaction hash. + pub hash: Option, +} + +#[cfg(test)] +mod tests { + use super::TransactionTest; + + #[test] + fn transaction_deserialization() { + let s = r#"{ + "Byzantium" : { + "hash" : "4782cb5edcaeda1f0aef204b161214f124cefade9e146245183abbb9ca01bca5", + "sender" : "2ea991808ba979ba103147edfd72304ebd95c028" + }, + "Constantinople" : { + "hash" : "4782cb5edcaeda1f0aef204b161214f124cefade9e146245183abbb9ca01bca5", + "sender" : "2ea991808ba979ba103147edfd72304ebd95c028" + }, + "EIP150" : { + }, + "EIP158" : { + "hash" : "4782cb5edcaeda1f0aef204b161214f124cefade9e146245183abbb9ca01bca5", + "sender" : "2ea991808ba979ba103147edfd72304ebd95c028" + }, + "Frontier" : { + }, + "Homestead" : { + }, + "_info" : { + "comment" : "", + "filledwith" : "cpp-1.3.0+commit.1829957d.Linux.g++", + "lllcversion" : "Version: 0.4.18-develop.2017.10.11+commit.81f9f86c.Linux.g++", + "source" : "src/TransactionTestsFiller/ttVValue/V_equals37Filler.json", + "sourceHash" : "89ef69312d4c0b4e3742da501263d23d2a64f180258ac93940997ac6a17b9b19" + }, + "rlp" : "0xf865808698852840a46f82d6d894095e7baea6a6c7c4c2dfeb977efac326af552d87808025a098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa01887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3" + }"#; + + let _deserialized: TransactionTest = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/evm-tests/ethjson/src/test_helpers/trie/input.rs b/evm-tests/ethjson/src/test_helpers/trie/input.rs new file mode 100644 index 000000000..f2bb445b1 --- /dev/null +++ b/evm-tests/ethjson/src/test_helpers/trie/input.rs @@ -0,0 +1,176 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Trie test input deserialization. + +use crate::bytes::Bytes; +use serde::de::{Error as ErrorTrait, MapAccess, SeqAccess, Visitor}; +use serde::{Deserialize, Deserializer}; +use std::collections::BTreeMap; +use std::fmt; +use std::str::FromStr; + +/// Trie test input. +#[derive(Debug, PartialEq, Eq)] +pub struct Input { + /// Input params. + pub data: BTreeMap>, +} + +impl<'a> Deserialize<'a> for Input { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + deserializer.deserialize_any(InputVisitor) + } +} + +struct InputVisitor; + +impl<'a> Visitor<'a> for InputVisitor { + type Value = Input; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a map of bytes into bytes") + } + + fn visit_map(self, mut visitor: V) -> Result + where + V: MapAccess<'a>, + { + let mut result = BTreeMap::new(); + + loop { + let key_str: Option = visitor.next_key()?; + let key = match key_str { + Some(ref k) if k.starts_with("0x") => { + Bytes::from_str(k).map_err(V::Error::custom)? + } + Some(k) => Bytes::new(k.into_bytes()), + None => { + break; + } + }; + + let val_str: Option = visitor.next_value()?; + let val = match val_str { + Some(ref v) if v.starts_with("0x") => { + Some(Bytes::from_str(v).map_err(V::Error::custom)?) + } + Some(v) => Some(Bytes::new(v.into_bytes())), + None => None, + }; + + result.insert(key, val); + } + + let input = Input { data: result }; + + Ok(input) + } + + fn visit_seq(self, mut visitor: V) -> Result + where + V: SeqAccess<'a>, + { + let mut result = BTreeMap::new(); + + loop { + let keyval: Option>> = visitor.next_element()?; + let keyval = match keyval { + Some(k) => k, + _ => { + break; + } + }; + + if keyval.len() != 2 { + return Err(V::Error::custom("Invalid key value pair.")); + } + + let key_str = &keyval[0]; + let val_str = &keyval[1]; + + let key = match *key_str { + Some(ref k) if k.starts_with("0x") => { + Bytes::from_str(k).map_err(V::Error::custom)? + } + Some(ref k) => Bytes::new(k.clone().into_bytes()), + None => { + break; + } + }; + + let val = match *val_str { + Some(ref v) if v.starts_with("0x") => { + Some(Bytes::from_str(v).map_err(V::Error::custom)?) + } + Some(ref v) => Some(Bytes::new(v.clone().into_bytes())), + None => None, + }; + + result.insert(key, val); + } + + let input = Input { data: result }; + + Ok(input) + } +} + +#[cfg(test)] +mod tests { + use super::{BTreeMap, Bytes, Input}; + + #[test] + fn input_deserialization_from_map() { + let s = r#"{ + "0x0045" : "0x0123456789", + "be" : "e", + "0x0a" : null + }"#; + + let input: Input = serde_json::from_str(s).unwrap(); + let mut map = BTreeMap::new(); + map.insert( + Bytes::new(vec![0, 0x45]), + Some(Bytes::new(vec![0x01, 0x23, 0x45, 0x67, 0x89])), + ); + map.insert(Bytes::new(vec![0x62, 0x65]), Some(Bytes::new(vec![0x65]))); + map.insert(Bytes::new(vec![0x0a]), None); + assert_eq!(input.data, map); + } + + #[test] + fn input_deserialization_from_array() { + let s = r#"[ + ["0x0045", "0x0123456789"], + ["be", "e"], + ["0x0a", null] + ]"#; + + let input: Input = serde_json::from_str(s).unwrap(); + let mut map = BTreeMap::new(); + map.insert( + Bytes::new(vec![0, 0x45]), + Some(Bytes::new(vec![0x01, 0x23, 0x45, 0x67, 0x89])), + ); + map.insert(Bytes::new(vec![0x62, 0x65]), Some(Bytes::new(vec![0x65]))); + map.insert(Bytes::new(vec![0x0a]), None); + assert_eq!(input.data, map); + } +} diff --git a/evm-tests/ethjson/src/test_helpers/trie/mod.rs b/evm-tests/ethjson/src/test_helpers/trie/mod.rs new file mode 100644 index 000000000..421dd8489 --- /dev/null +++ b/evm-tests/ethjson/src/test_helpers/trie/mod.rs @@ -0,0 +1,37 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Trie test deserialization. + +mod input; + +pub use self::input::Input; + +/// Type used by `trie` tests +pub type Test = super::tester::GenericTester; + +use crate::hash::H256; +use serde::Deserialize; + +/// Trie test deserialization. +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub struct Trie { + /// Trie test input. + #[serde(rename = "in")] + pub input: Input, + /// Trie root hash. + pub root: H256, +} diff --git a/evm-tests/ethjson/src/transaction.rs b/evm-tests/ethjson/src/transaction.rs new file mode 100644 index 000000000..479fce065 --- /dev/null +++ b/evm-tests/ethjson/src/transaction.rs @@ -0,0 +1,91 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Transaction deserialization. + +use crate::{ + bytes::Bytes, + hash::{Address, H256}, + maybe::MaybeEmpty, + uint::Uint, +}; +use serde::Deserialize; + +/// Unsigned transaction with signing information deserialization. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Transaction { + /// Transaction data. + pub data: Bytes, + /// Transaction access list (see EIP-2930). + #[serde(default)] + pub access_list: Vec<(Address, Vec)>, + /// Gas limit. + pub gas_limit: Uint, + /// Gas price. + pub gas_price: Uint, + /// Nonce. + pub nonce: Uint, + /// To. + pub to: MaybeEmpty
, + /// Value. + pub value: Uint, + /// R. + #[serde(default)] + pub r: MaybeEmpty, + /// S. + #[serde(default)] + pub s: MaybeEmpty, + /// V. + #[serde(default)] + pub v: MaybeEmpty, + /// Secret + #[serde(rename = "secretKey")] + pub secret: Option, +} + +#[cfg(test)] +mod tests { + use super::{Bytes, MaybeEmpty, Transaction, Uint, H256}; + use ethereum_types::{H256 as Eth256, U256}; + + #[test] + fn transaction_deserialization() { + let s = r#"{ + "data" : "0x", + "gasLimit" : "0xf388", + "gasPrice" : "0x09184e72a000", + "nonce" : "0x00", + "to" : "", + "value" : "0x00", + "r": "0", + "s": "1", + "v": "2", + "secretKey": "0x0000000000000000000000000000000000000000000000000000000000000000" + }"#; + let tx: Transaction = serde_json::from_str(s).expect("JSON string is valid"); + assert_eq!(tx.data, Bytes::new(Vec::new())); + assert_eq!(tx.gas_limit, Uint(U256::from(0xf388))); + assert_eq!(tx.gas_price, Uint(U256::from(0x09184e72a000_u64))); + assert_eq!(tx.nonce, Uint(U256::zero())); + assert_eq!(tx.to, MaybeEmpty::None); + assert_eq!(tx.value, Uint(U256::zero())); + assert_eq!(tx.r, Uint(U256::zero()).into()); + assert_eq!(tx.s, Uint(U256::one()).into()); + assert_eq!(tx.v, Uint(U256::from(2)).into()); + assert_eq!(tx.secret, Some(H256(Eth256::zero()))); + } +} diff --git a/evm-tests/ethjson/src/uint.rs b/evm-tests/ethjson/src/uint.rs new file mode 100644 index 000000000..818ad4796 --- /dev/null +++ b/evm-tests/ethjson/src/uint.rs @@ -0,0 +1,196 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Lenient uint json deserialization for test json files. + +use ethereum_types::U256; +use serde::de::{Error, Unexpected, Visitor}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; +use std::str::FromStr; + +/// Lenient uint json deserialization for test json files. +#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub struct Uint(pub U256); + +impl From for U256 { + fn from(val: Uint) -> Self { + val.0 + } +} + +impl From for u64 { + fn from(val: Uint) -> Self { + val.0.low_u64() + } +} + +impl From for usize { + fn from(val: Uint) -> Self { + val.0.low_u64() as Self + } +} + +impl Serialize for Uint { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.to_string().serialize(serializer) + } +} + +impl<'a> Deserialize<'a> for Uint { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + deserializer.deserialize_any(UintVisitor) + } +} + +struct UintVisitor; + +impl<'a> Visitor<'a> for UintVisitor { + type Value = Uint; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a hex encoded or decimal uint") + } + + fn visit_u64(self, value: u64) -> Result + where + E: Error, + { + Ok(Uint(U256::from(value))) + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + let parse = |value: &str| { + if value.len() > 64 { + return Err(Error::custom( + format!( + "Invalid hex value 0x{value}: value too big (length={})", + value.len() + ) + .as_str(), + )); + } + U256::from_str(value) + .map_err(|e| Error::custom(format!("Invalid hex value 0x{value}: {e}").as_str())) + }; + + let value = match value.len() { + 0 => U256::from(0), + 2 if value.starts_with("0x") => U256::from(0), + _ if value.starts_with("0x:bigint 0x") => parse(&value[12..])?, + _ if value.starts_with("0x") => parse(&value[2..])?, + _ => U256::from_dec_str(value).map_err(|e| { + Error::custom(format!("Invalid decimal value {}: {:?}", value, e).as_str()) + })?, + }; + + Ok(Uint(value)) + } + + fn visit_string(self, value: String) -> Result + where + E: Error, + { + self.visit_str(value.as_ref()) + } +} + +/// Deserialize and validate that the value is non-zero +pub fn validate_non_zero<'de, D>(d: D) -> Result +where + D: Deserializer<'de>, +{ + let value = Uint::deserialize(d)?; + + if value == Uint(U256::from(0)) { + return Err(Error::invalid_value( + Unexpected::Unsigned(0), + &"a non-zero value", + )); + } + + Ok(value) +} + +/// Deserialize and validate that the value is non-zero +pub fn validate_optional_non_zero<'de, D>(d: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let value: Option = Option::deserialize(d)?; + + if let Some(value) = value { + if value == Uint(U256::from(0)) { + return Err(Error::invalid_value( + Unexpected::Unsigned(0), + &"a non-zero value", + )); + } + } + + Ok(value) +} + +#[cfg(test)] +mod test { + use super::Uint; + use ethereum_types::U256; + + #[test] + fn uint_deserialization() { + let s = r#"["0xa", "10", "", "0x", 0]"#; + let deserialized: Vec = serde_json::from_str(s).unwrap(); + assert_eq!( + deserialized, + vec![ + Uint(U256::from(10)), + Uint(U256::from(10)), + Uint(U256::from(0)), + Uint(U256::from(0)), + Uint(U256::from(0)) + ] + ); + } + + #[test] + fn uint_deserialization_error_for_hex_too_large() { + let hex = format!("0x{}", "1".repeat(65)); + let result: Result = serde_json::from_str(&format!(r#""{}""#, hex)); + let err = result.unwrap_err(); + assert!(err.is_data()); + assert_eq!( + err.to_string(), + format!( + "Invalid hex value {}: value too big (length=65) at line 1 column 69", + hex + ) + ); + } + + #[test] + fn uint_into() { + assert_eq!(U256::from(10), Uint(U256::from(10)).into()); + } +} diff --git a/evm-tests/ethjson/src/vm.rs b/evm-tests/ethjson/src/vm.rs new file mode 100644 index 000000000..f028c6c10 --- /dev/null +++ b/evm-tests/ethjson/src/vm.rs @@ -0,0 +1,311 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! Vm json deserialization + +use crate::{ + bytes::Bytes, + hash::{Address, H256}, + maybe::MaybeEmpty, + spec::State, + uint::Uint, +}; +use serde::Deserialize; + +/// Represents vm execution environment before and after execution of transaction. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Vm { + /// Contract calls made internaly by executed transaction. + #[serde(rename = "callcreates")] + pub calls: Option>, + /// Env info. + pub env: Env, + /// Executed transaction + #[serde(rename = "exec")] + pub transaction: Transaction, + /// Gas left after transaction execution. + #[serde(rename = "gas")] + pub gas_left: Option, + /// Hash of logs created during execution of transaction. + pub logs: Option, + /// Transaction output. + #[serde(rename = "out")] + pub output: Option, + /// Post execution vm state. + #[serde(rename = "post")] + pub post_state: Option, + /// Pre execution vm state. + #[serde(rename = "pre")] + pub pre_state: State, +} + +impl Vm { + /// Returns true if transaction execution run out of gas. + pub const fn out_of_gas(&self) -> bool { + self.calls.is_none() + } +} + +/// Call deserialization. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Call { + /// Call data. + pub data: Bytes, + /// Call destination. + pub destination: MaybeEmpty
, + /// Gas limit. + pub gas_limit: Uint, + /// Call value. + pub value: Uint, +} + +/// Executed transaction. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Transaction { + /// Contract address. + pub address: Address, + /// Transaction sender. + #[serde(rename = "caller")] + pub sender: Address, + /// Contract code. + pub code: Bytes, + /// Input data. + pub data: Bytes, + /// Gas. + pub gas: Uint, + /// Gas price. + pub gas_price: Uint, + /// Transaction origin. + pub origin: Address, + /// Sent value. + pub value: Uint, + /// Contract code version. + #[serde(default)] + pub code_version: Uint, +} + +/// Environment. +#[derive(Debug, PartialEq, Eq, Deserialize)] +pub struct Env { + /// Address. + #[serde(rename = "currentCoinbase")] + pub author: Address, + /// Difficulty + #[serde(rename = "currentDifficulty")] + pub difficulty: Uint, + /// Gas limit. + #[serde(rename = "currentGasLimit")] + pub gas_limit: Uint, + /// Number. + #[serde(rename = "currentNumber")] + pub number: Uint, + /// Timestamp. + #[serde(rename = "currentTimestamp")] + pub timestamp: Uint, + /// Block base fee (see EIP-1559) + #[serde(rename = "currentBaseFee")] + #[serde(default)] + pub block_base_fee_per_gas: Uint, + /// Pre-seeded random value for testing + #[serde(rename = "currentRandom")] + #[serde(default)] + pub random: Option, +} + +#[cfg(test)] +mod tests { + use super::{Address, Bytes, Call, Env, MaybeEmpty, State, Transaction, Uint, Vm, H256}; + use std::str::FromStr; + + use crate::spec::{Account, HashOrMap}; + use ethereum_types::{H160 as Hash160, H256 as Hash256, U256}; + use maplit::btreemap; + use rustc_hex::FromHex; + + const TEST_CODE: &str = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01600055"; + + #[test] + fn vm_deserialization() { + let s = r#"{ + "callcreates" : [ + ], + "env" : { + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x0100", + "currentGasLimit" : "0x0f4240", + "currentNumber" : "0x00", + "currentTimestamp" : "0x01", + "currentRandom" : "0x01" + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "caller" : "cd1722f2947def4cf144679da39c4c32bdc35681", + "code" : "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01600055", + "data" : "0x", + "gas" : "0x0186a0", + "gasPrice" : "0x5af3107a4000", + "origin" : "cd1722f2947def4cf144679da39c4c32bdc35681", + "value" : "0x0de0b6b3a7640000" + }, + "gas" : "0x013874", + "logs" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "out" : "0x", + "post" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01600055", + "nonce" : "0x00", + "storage" : { + "0x00" : "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" + } + } + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01600055", + "nonce" : "0x00", + "storage" : { + } + } + } + }"#; + let vm: Vm = serde_json::from_str(s).expect("JSON is valid"); + assert_eq!(vm.calls, Some(Vec::new())); + assert_eq!( + vm.env, + Env { + author: Address( + Hash160::from_str("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba").unwrap() + ), + difficulty: Uint(0x0100.into()), + gas_limit: Uint(0x0f4240.into()), + number: Uint(0.into()), + timestamp: Uint(1.into()), + block_base_fee_per_gas: Uint(0.into()), + random: Some(Uint(1.into())), + } + ); + assert_eq!( + vm.transaction, + Transaction { + address: Address( + Hash160::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap() + ), + sender: Address( + Hash160::from_str("cd1722f2947def4cf144679da39c4c32bdc35681").unwrap() + ), + code: Bytes::new(TEST_CODE.from_hex().unwrap()), + code_version: Uint(0.into()), + data: Bytes::new(Vec::new()), + gas: Uint(0x0186a0.into()), + gas_price: Uint(0x5af3107a4000_u64.into()), + origin: Address( + Hash160::from_str("cd1722f2947def4cf144679da39c4c32bdc35681").unwrap() + ), + value: Uint(0x0de0b6b3a7640000_u64.into()) + } + ); + assert_eq!(vm.gas_left, Some(Uint(0x013874.into()))); + assert_eq!( + vm.logs, + Some(H256( + Hash256::from_str( + "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + ) + .unwrap() + )) + ); + assert_eq!(vm.output, Some(Bytes::new(Vec::new()))); + assert_eq!( + vm.pre_state, + State(HashOrMap::Map(btreemap![ + Address(Hash160::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap()) => Account { + builtin: None, + balance: Some(Uint(0x0de0b6b3a7640000_u64.into())), + code: Some(Bytes::new(TEST_CODE.from_hex().unwrap())), + constructor: None, + nonce: Some(Uint(0.into())), + storage: Some(btreemap![]), + version: None, + } + ])) + ); + assert_eq!( + vm.post_state, + Some(State(HashOrMap::Map(btreemap![ + Address(Hash160::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap()) => Account { + builtin: None, + balance: Some(Uint(0x0de0b6b3a7640000_u64.into())), + code: Some(Bytes::new(TEST_CODE.from_hex().unwrap())), + constructor: None, + nonce: Some(Uint(0.into())), + storage: Some(btreemap![ + Uint(0.into()) => Uint(U256::from_str("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe").unwrap()) + ]), + version: None, + }]))) + ); + } + + #[test] + fn call_deserialization_empty_dest() { + let s = r#"{ + "data" : "0x1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff", + "destination" : "", + "gasLimit" : "0x1748766aa5", + "value" : "0x00" + }"#; + let call: Call = serde_json::from_str(s).unwrap(); + + assert_eq!( + &call.data[..], + &[ + 0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, + 0x88, 0x88, 0x99, 0x99, 0x00, 0x00, 0xaa, 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, + 0xee, 0xee, 0xff, 0xff + ] + ); + + assert_eq!(call.destination, MaybeEmpty::None); + assert_eq!(call.gas_limit, Uint(U256::from(0x1748766aa5u64))); + assert_eq!(call.value, Uint(U256::from(0))); + } + + #[test] + fn call_deserialization_full_dest() { + let s = r#"{ + "data" : "0x1234", + "destination" : "5a39ed1020c04d4d84539975b893a4e7c53eab6c", + "gasLimit" : "0x1748766aa5", + "value" : "0x00" + }"#; + + let call: Call = serde_json::from_str(s).unwrap(); + + assert_eq!(&call.data[..], &[0x12, 0x34]); + assert_eq!( + call.destination, + MaybeEmpty::Some(Address( + Hash160::from_str("5a39ed1020c04d4d84539975b893a4e7c53eab6c").unwrap() + )) + ); + assert_eq!(call.gas_limit, Uint(U256::from(0x1748766aa5u64))); + assert_eq!(call.value, Uint(U256::from(0))); + } +} diff --git a/evm-tests/jsontests/Cargo.toml b/evm-tests/jsontests/Cargo.toml new file mode 100644 index 000000000..d4f740f9a --- /dev/null +++ b/evm-tests/jsontests/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "evm-jsontests" +version = "0.13.1" +license = "GPL-3.0" +authors = ["Wei Tang ", "Parity Technologies "] +description = "SputnikVM - a Portable Blockchain Virtual Machine" +repository = "https://github.com/sorpaas/rust-evm" +keywords = ["no_std", "ethereum"] +edition = "2018" + +[dependencies] +evm = { path = "../.." } +ethereum = "0.15.0" +primitive-types = "0.12" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +hex = "0.4" +clap = "2.34" +ethjson = { path = "../ethjson", features = ["test-helpers"] } +libsecp256k1 = "0.7" +ethcore-builtin = { path = "../ethcore-builtin" } +rlp = "0.5" +sha3 = "0.10" +parity-bytes = "0.1" +env_logger = "0.10" +lazy_static = "1.4.0" diff --git a/evm-tests/jsontests/res/berlin_builtins.json b/evm-tests/jsontests/res/berlin_builtins.json new file mode 100644 index 000000000..66d77e80c --- /dev/null +++ b/evm-tests/jsontests/res/berlin_builtins.json @@ -0,0 +1,52 @@ +{ + "0000000000000000000000000000000000000001": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } }, + "0000000000000000000000000000000000000002": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } }, + "0000000000000000000000000000000000000003": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } }, + "0000000000000000000000000000000000000004": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } }, + "0000000000000000000000000000000000000005": { "name": "modexp", "activate_at": "0x00", "pricing": { "modexp": { "divisor": 3, "is_eip_2565": true } } }, + "0000000000000000000000000000000000000006": { + "name": "alt_bn128_add", + "pricing": { + "0": { + "price": { "alt_bn128_const_operations": { "price": 500 }} + }, + "0": { + "info": "EIP 1108 transition", + "price": { "alt_bn128_const_operations": { "price": 150 }} + } + } + }, + "0000000000000000000000000000000000000007": { + "name": "alt_bn128_mul", + "pricing": { + "0": { + "price": { "alt_bn128_const_operations": { "price": 40000 }} + }, + "0": { + "info": "EIP 1108 transition", + "price": { "alt_bn128_const_operations": { "price": 6000 }} + } + } + }, + "0000000000000000000000000000000000000008": { + "name": "alt_bn128_pairing", + "pricing": { + "0": { + "price": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 }} + }, + "0": { + "info": "EIP 1108 transition", + "price": { "alt_bn128_pairing": { "base": 45000, "pair": 34000 }} + } + } + }, + "0000000000000000000000000000000000000009": { + "name": "blake2_f", + "activate_at": "0x00", + "pricing": { + "blake2_f": { + "gas_per_round": 1 + } + } + } +} diff --git a/evm-tests/jsontests/res/istanbul_builtins.json b/evm-tests/jsontests/res/istanbul_builtins.json new file mode 100644 index 000000000..02e6de7c4 --- /dev/null +++ b/evm-tests/jsontests/res/istanbul_builtins.json @@ -0,0 +1,52 @@ +{ + "0000000000000000000000000000000000000001": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } }, + "0000000000000000000000000000000000000002": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } }, + "0000000000000000000000000000000000000003": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } }, + "0000000000000000000000000000000000000004": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } }, + "0000000000000000000000000000000000000005": { "name": "modexp", "activate_at": "0x00", "pricing": { "modexp": { "divisor": 20, "is_eip_2565": false } } }, + "0000000000000000000000000000000000000006": { + "name": "alt_bn128_add", + "pricing": { + "0": { + "price": { "alt_bn128_const_operations": { "price": 500 }} + }, + "0": { + "info": "EIP 1108 transition", + "price": { "alt_bn128_const_operations": { "price": 150 }} + } + } + }, + "0000000000000000000000000000000000000007": { + "name": "alt_bn128_mul", + "pricing": { + "0": { + "price": { "alt_bn128_const_operations": { "price": 40000 }} + }, + "0": { + "info": "EIP 1108 transition", + "price": { "alt_bn128_const_operations": { "price": 6000 }} + } + } + }, + "0000000000000000000000000000000000000008": { + "name": "alt_bn128_pairing", + "pricing": { + "0": { + "price": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 }} + }, + "0": { + "info": "EIP 1108 transition", + "price": { "alt_bn128_pairing": { "base": 45000, "pair": 34000 }} + } + } + }, + "0000000000000000000000000000000000000009": { + "name": "blake2_f", + "activate_at": "0x00", + "pricing": { + "blake2_f": { + "gas_per_round": 1 + } + } + } +} diff --git a/evm-tests/jsontests/src/lib.rs b/evm-tests/jsontests/src/lib.rs new file mode 100644 index 000000000..8018d2c53 --- /dev/null +++ b/evm-tests/jsontests/src/lib.rs @@ -0,0 +1,4 @@ +mod utils; + +pub mod state; +pub mod vm; diff --git a/evm-tests/jsontests/src/main.rs b/evm-tests/jsontests/src/main.rs new file mode 100644 index 000000000..3a0472c27 --- /dev/null +++ b/evm-tests/jsontests/src/main.rs @@ -0,0 +1,58 @@ +use clap::{App, Arg, SubCommand}; +use evm_jsontests::state as statetests; +use evm_jsontests::vm as vmtests; +use std::collections::HashMap; +use std::fs::File; +use std::io::BufReader; + +fn main() { + let matches = App::new("jsontests") + .version("0.1.0") + .author("Wei Tang ") + .about("EVM json test utilities") + .subcommand( + SubCommand::with_name("vm").arg( + Arg::with_name("FILE") + .help("Target yaml file to import") + .required(true) + .min_values(1), + ), + ) + .subcommand( + SubCommand::with_name("state").arg( + Arg::with_name("FILE") + .help("Target yaml file to import") + .required(true) + .min_values(1), + ), + ) + .get_matches(); + + if let Some(matches) = matches.subcommand_matches("vm") { + for file_name in matches.values_of("FILE").unwrap() { + let file = File::open(file_name).expect("Open file failed"); + + let reader = BufReader::new(file); + let coll = serde_json::from_reader::<_, HashMap>(reader) + .expect("Parse test cases failed"); + + for (name, test) in coll { + vmtests::test(&name, test); + } + } + } + + if let Some(matches) = matches.subcommand_matches("state") { + for file_name in matches.values_of("FILE").unwrap() { + let file = File::open(file_name).expect("Open file failed"); + + let reader = BufReader::new(file); + let coll = serde_json::from_reader::<_, HashMap>(reader) + .expect("Parse test cases failed"); + + for (name, test) in coll { + statetests::test(&name, test); + } + } + } +} diff --git a/evm-tests/jsontests/src/state.rs b/evm-tests/jsontests/src/state.rs new file mode 100644 index 000000000..a846ff8c2 --- /dev/null +++ b/evm-tests/jsontests/src/state.rs @@ -0,0 +1,388 @@ +use crate::utils::*; +use ethjson::spec::ForkSpec; +use evm::backend::{ApplyBackend, MemoryAccount, MemoryBackend, MemoryVicinity}; +use evm::executor::stack::{ + MemoryStackState, PrecompileFailure, PrecompileFn, PrecompileOutput, StackExecutor, + StackSubstateMetadata, +}; +use evm::{Config, Context, ExitError, ExitSucceed}; +use lazy_static::lazy_static; +use libsecp256k1::SecretKey; +use primitive_types::{H160, H256, U256}; +use serde::Deserialize; +use sha3::{Digest, Keccak256}; +use std::collections::BTreeMap; +use std::convert::TryInto; + +#[derive(Deserialize, Debug)] +pub struct Test(ethjson::test_helpers::state::State); + +impl Test { + pub fn unwrap_to_pre_state(&self) -> BTreeMap { + unwrap_to_state(&self.0.pre_state) + } + + pub fn unwrap_caller(&self) -> H160 { + let hash: H256 = self.0.transaction.secret.unwrap().into(); + let mut secret_key = [0; 32]; + secret_key.copy_from_slice(hash.as_bytes()); + let secret = SecretKey::parse(&secret_key); + let public = libsecp256k1::PublicKey::from_secret_key(&secret.unwrap()); + let mut res = [0u8; 64]; + res.copy_from_slice(&public.serialize()[1..65]); + + H160::from(H256::from_slice(Keccak256::digest(res).as_slice())) + } + + pub fn unwrap_to_vicinity(&self, spec: &ForkSpec) -> Option { + let block_base_fee_per_gas = self.0.env.block_base_fee_per_gas.0; + let gas_price = if self.0.transaction.gas_price.0.is_zero() { + let max_fee_per_gas = self.0.transaction.max_fee_per_gas.0; + + // max_fee_per_gas is only defined for London and later + if !max_fee_per_gas.is_zero() && spec < &ForkSpec::London { + return None; + } + + // Cannot specify a lower fee than the base fee + if max_fee_per_gas < block_base_fee_per_gas { + return None; + } + + let max_priority_fee_per_gas = self.0.transaction.max_priority_fee_per_gas.0; + + // priority fee must be lower than regaular fee + if max_fee_per_gas < max_priority_fee_per_gas { + return None; + } + + let priority_fee_per_gas = std::cmp::min( + max_priority_fee_per_gas, + max_fee_per_gas - block_base_fee_per_gas, + ); + priority_fee_per_gas + block_base_fee_per_gas + } else { + self.0.transaction.gas_price.0 + }; + + // gas price cannot be lower than base fee + if gas_price < block_base_fee_per_gas { + return None; + } + + let block_randomness = if spec.is_eth2() { + self.0.env.random.map(|r| { + // Convert between U256 and H256. U256 is in little-endian but since H256 is just + // a string-like byte array, it's big endian (MSB is the first element of the array). + // + // Byte order here is important because this opcode has the same value as DIFFICULTY + // (0x44), and so for older forks of Ethereum, the threshold value of 2^64 is used to + // distinguish between the two: if it's below, the value corresponds to the DIFFICULTY + // opcode, otherwise to the PREVRANDAO opcode. + u256_to_h256(r.0) + }) + } else { + None + }; + + Some(MemoryVicinity { + gas_price, + origin: self.unwrap_caller(), + block_hashes: Vec::new(), + block_number: self.0.env.number.into(), + block_coinbase: self.0.env.author.into(), + block_timestamp: self.0.env.timestamp.into(), + block_difficulty: self.0.env.difficulty.into(), + block_gas_limit: self.0.env.gas_limit.into(), + chain_id: U256::one(), + block_base_fee_per_gas, + block_randomness, + }) + } +} + +lazy_static! { + static ref ISTANBUL_BUILTINS: BTreeMap = + JsonPrecompile::builtins("./res/istanbul_builtins.json"); +} + +lazy_static! { + static ref BERLIN_BUILTINS: BTreeMap = + JsonPrecompile::builtins("./res/berlin_builtins.json"); +} + +macro_rules! precompile_entry { + ($map:expr, $builtins:expr, $index:expr) => { + let x: PrecompileFn = + |input: &[u8], gas_limit: Option, _context: &Context, _is_static: bool| { + let builtin = $builtins.get(&H160::from_low_u64_be($index)).unwrap(); + Self::exec_as_precompile(builtin, input, gas_limit) + }; + $map.insert(H160::from_low_u64_be($index), x); + }; +} + +pub struct JsonPrecompile; + +impl JsonPrecompile { + pub fn precompile(spec: &ForkSpec) -> Option> { + match spec { + ForkSpec::Istanbul => { + let mut map = BTreeMap::new(); + precompile_entry!(map, ISTANBUL_BUILTINS, 1); + precompile_entry!(map, ISTANBUL_BUILTINS, 2); + precompile_entry!(map, ISTANBUL_BUILTINS, 3); + precompile_entry!(map, ISTANBUL_BUILTINS, 4); + precompile_entry!(map, ISTANBUL_BUILTINS, 5); + precompile_entry!(map, ISTANBUL_BUILTINS, 6); + precompile_entry!(map, ISTANBUL_BUILTINS, 7); + precompile_entry!(map, ISTANBUL_BUILTINS, 8); + precompile_entry!(map, ISTANBUL_BUILTINS, 9); + Some(map) + } + ForkSpec::Berlin => { + let mut map = BTreeMap::new(); + precompile_entry!(map, BERLIN_BUILTINS, 1); + precompile_entry!(map, BERLIN_BUILTINS, 2); + precompile_entry!(map, BERLIN_BUILTINS, 3); + precompile_entry!(map, BERLIN_BUILTINS, 4); + precompile_entry!(map, BERLIN_BUILTINS, 5); + precompile_entry!(map, BERLIN_BUILTINS, 6); + precompile_entry!(map, BERLIN_BUILTINS, 7); + precompile_entry!(map, BERLIN_BUILTINS, 8); + precompile_entry!(map, BERLIN_BUILTINS, 9); + Some(map) + } + // precompiles for London and Berlin are the same + ForkSpec::London => Self::precompile(&ForkSpec::Berlin), + // precompiles for Merge and Berlin are the same + ForkSpec::Merge => Self::precompile(&ForkSpec::Berlin), + // precompiles for Shanghai and Berlin are the same + ForkSpec::Shanghai => Self::precompile(&ForkSpec::Berlin), + ForkSpec::Cancun => Self::precompile(&ForkSpec::Berlin), + _ => None, + } + } + + fn builtins(spec_path: &str) -> BTreeMap { + let reader = std::fs::File::open(spec_path).unwrap(); + let builtins: BTreeMap = + serde_json::from_reader(reader).unwrap(); + builtins + .into_iter() + .map(|(address, builtin)| { + ( + address.into(), + ethjson::spec::Builtin::from(builtin).try_into().unwrap(), + ) + }) + .collect() + } + + fn exec_as_precompile( + builtin: ðcore_builtin::Builtin, + input: &[u8], + gas_limit: Option, + ) -> Result<(PrecompileOutput, u64), PrecompileFailure> { + let cost = builtin.cost(input, 0); + + if let Some(target_gas) = gas_limit { + if cost > U256::from(u64::MAX) || target_gas < cost.as_u64() { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + let mut output = Vec::new(); + match builtin.execute(input, &mut parity_bytes::BytesRef::Flexible(&mut output)) { + Ok(()) => Ok(( + PrecompileOutput { + exit_status: ExitSucceed::Stopped, + output, + }, + cost.as_u64(), + )), + Err(e) => Err(PrecompileFailure::Error { + exit_status: ExitError::Other(e.into()), + }), + } + } +} + +pub fn test(name: &str, test: Test) { + use std::thread; + + const STACK_SIZE: usize = 16 * 1024 * 1024; + + let name = name.to_string(); + // Spawn thread with explicit stack size + let child = thread::Builder::new() + .stack_size(STACK_SIZE) + .spawn(move || test_run(&name, test)) + .unwrap(); + + // Wait for thread to join + child.join().unwrap(); +} + +fn test_run(name: &str, test: Test) { + for (spec, states) in &test.0.post_states { + let (gasometer_config, delete_empty) = match spec { + ethjson::spec::ForkSpec::Istanbul => (Config::istanbul(), true), + ethjson::spec::ForkSpec::Berlin => (Config::berlin(), true), + ethjson::spec::ForkSpec::London => (Config::london(), true), + ethjson::spec::ForkSpec::Merge => (Config::merge(), true), + ethjson::spec::ForkSpec::Shanghai => (Config::shanghai(), true), + ethjson::spec::ForkSpec::Cancun => (Config::shanghai(), true), + spec => { + println!("Skip spec {spec:?}"); + continue; + } + }; + + let original_state = test.unwrap_to_pre_state(); + let vicinity = test.unwrap_to_vicinity(spec); + if vicinity.is_none() { + // if vicinity could not be computed then the transaction was invalid so we simply + // check the original state and move on + assert_valid_hash(&states.first().unwrap().hash.0, &original_state); + continue; + } + let vicinity = vicinity.unwrap(); + let caller = test.unwrap_caller(); + let caller_balance = original_state.get(&caller).unwrap().balance; + + for (i, state) in states.iter().enumerate() { + print!("Running {}:{:?}:{} ... ", name, spec, i); + flush(); + + let transaction = test.0.transaction.select(&state.indexes); + let mut backend = MemoryBackend::new(&vicinity, original_state.clone()); + + // Test case may be expected to fail with an unsupported tx type if the current fork is + // older than Berlin (see EIP-2718). However, this is not implemented in sputnik itself and rather + // in the code hosting sputnik. https://github.com/rust-blockchain/evm/pull/40 + let expect_tx_type_not_supported = + matches!( + spec, + ForkSpec::EIP150 + | ForkSpec::EIP158 | ForkSpec::Frontier + | ForkSpec::Homestead | ForkSpec::Byzantium + | ForkSpec::Constantinople + | ForkSpec::ConstantinopleFix + | ForkSpec::Istanbul + ) && TxType::from_txbytes(&state.txbytes) != TxType::Legacy + && state.expect_exception.as_deref() == Some("TR_TypeNotSupported"); + if expect_tx_type_not_supported { + continue; + } + + // Only execute valid transactions + if let Ok(transaction) = crate::utils::transaction::validate( + transaction, + test.0.env.gas_limit.0, + caller_balance, + &gasometer_config, + ) { + let gas_limit: u64 = transaction.gas_limit.into(); + let data: Vec = transaction.data.into(); + let metadata = + StackSubstateMetadata::new(transaction.gas_limit.into(), &gasometer_config); + let executor_state = MemoryStackState::new(metadata, &backend); + let precompile = JsonPrecompile::precompile(spec).unwrap(); + let mut executor = StackExecutor::new_with_precompiles( + executor_state, + &gasometer_config, + &precompile, + ); + let total_fee = vicinity.gas_price * gas_limit; + + executor.state_mut().withdraw(caller, total_fee).unwrap(); + + let access_list = transaction + .access_list + .into_iter() + .map(|(address, keys)| (address.0, keys.into_iter().map(|k| k.0).collect())) + .collect(); + + match transaction.to { + ethjson::maybe::MaybeEmpty::Some(to) => { + let value = transaction.value.into(); + + let _reason = executor.transact_call( + caller, + to.into(), + value, + data, + gas_limit, + access_list, + ); + } + ethjson::maybe::MaybeEmpty::None => { + let code = data; + let value = transaction.value.into(); + + let _reason = + executor.transact_create(caller, value, code, gas_limit, access_list); + } + } + + let actual_fee = executor.fee(vicinity.gas_price); + // Forks after London burn miner rewards and thus have different gas fee + // calculation (see EIP-1559) + let miner_reward = if spec.is_eth2() { + let max_priority_fee_per_gas = test.0.transaction.max_priority_fee_per_gas(); + let max_fee_per_gas = test.0.transaction.max_fee_per_gas(); + let base_fee_per_gas = vicinity.block_base_fee_per_gas; + let priority_fee_per_gas = + std::cmp::min(max_priority_fee_per_gas, max_fee_per_gas - base_fee_per_gas); + executor.fee(priority_fee_per_gas) + } else { + actual_fee + }; + + executor + .state_mut() + .deposit(vicinity.block_coinbase, miner_reward); + executor.state_mut().deposit(caller, total_fee - actual_fee); + + let (values, logs) = executor.into_state().deconstruct(); + + backend.apply(values, logs, delete_empty); + } + + assert_valid_hash(&state.hash.0, backend.state()); + + println!("passed"); + } + } +} + +/// Denotes the type of transaction. +#[derive(Debug, PartialEq)] +enum TxType { + /// All transactions before EIP-2718 are legacy. + Legacy, + /// https://eips.ethereum.org/EIPS/eip-2718 + AccessList, + /// https://eips.ethereum.org/EIPS/eip-1559 + DynamicFee, +} + +impl TxType { + /// Whether this is a legacy, access list, dynamic fee, etc transaction + // Taken from geth's core/types/transaction.go/UnmarshalBinary, but we only detect the transaction + // type rather than unmarshal the entire payload. + const fn from_txbytes(txbytes: &[u8]) -> Self { + match txbytes[0] { + b if b > 0x7f => Self::Legacy, + 1 => Self::AccessList, + 2 => Self::DynamicFee, + _ => panic!( + "Unknown tx type. \ +You may need to update the TxType enum if Ethereum introduced new enveloped transaction types." + ), + } + } +} diff --git a/evm-tests/jsontests/src/utils.rs b/evm-tests/jsontests/src/utils.rs new file mode 100644 index 000000000..163a20de0 --- /dev/null +++ b/evm-tests/jsontests/src/utils.rs @@ -0,0 +1,223 @@ +use evm::backend::MemoryAccount; +use primitive_types::{H160, H256, U256}; +use sha3::{Digest, Keccak256}; +use std::collections::BTreeMap; + +pub fn u256_to_h256(u: U256) -> H256 { + let mut h = H256::default(); + u.to_big_endian(&mut h[..]); + h +} + +pub fn unwrap_to_account(s: ðjson::spec::Account) -> MemoryAccount { + MemoryAccount { + balance: s.balance.unwrap().into(), + nonce: s.nonce.unwrap().0, + code: s.code.clone().unwrap().into(), + storage: s + .storage + .as_ref() + .unwrap() + .iter() + .filter_map(|(k, v)| { + if v.0.is_zero() { + // If value is zero then the key is not really there + None + } else { + Some((u256_to_h256((*k).into()), u256_to_h256((*v).into()))) + } + }) + .collect(), + } +} + +pub fn unwrap_to_state(a: ðjson::spec::State) -> BTreeMap { + match &a.0 { + ethjson::spec::HashOrMap::Map(m) => m + .iter() + .map(|(k, v)| ((*k).into(), unwrap_to_account(v))) + .collect(), + ethjson::spec::HashOrMap::Hash(_) => panic!("Hash can not be converted."), + } +} + +/// Basic account type. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TrieAccount { + /// Nonce of the account. + pub nonce: U256, + /// Balance of the account. + pub balance: U256, + /// Storage root of the account. + pub storage_root: H256, + /// Code hash of the account. + pub code_hash: H256, + /// Code version of the account. + pub code_version: U256, +} + +impl rlp::Encodable for TrieAccount { + fn rlp_append(&self, stream: &mut rlp::RlpStream) { + let use_short_version = self.code_version == U256::zero(); + + match use_short_version { + true => { + stream.begin_list(4); + } + false => { + stream.begin_list(5); + } + } + + stream.append(&self.nonce); + stream.append(&self.balance); + stream.append(&self.storage_root); + stream.append(&self.code_hash); + + if !use_short_version { + stream.append(&self.code_version); + } + } +} + +impl rlp::Decodable for TrieAccount { + fn decode(rlp: &rlp::Rlp) -> Result { + let use_short_version = match rlp.item_count()? { + 4 => true, + 5 => false, + _ => return Err(rlp::DecoderError::RlpIncorrectListLen), + }; + + Ok(Self { + nonce: rlp.val_at(0)?, + balance: rlp.val_at(1)?, + storage_root: rlp.val_at(2)?, + code_hash: rlp.val_at(3)?, + code_version: if use_short_version { + U256::zero() + } else { + rlp.val_at(4)? + }, + }) + } +} + +pub fn assert_valid_state(a: ðjson::spec::State, b: &BTreeMap) { + match &a.0 { + ethjson::spec::HashOrMap::Map(m) => { + assert_eq!( + &m.iter() + .map(|(k, v)| { ((*k).into(), unwrap_to_account(v)) }) + .collect::>(), + b + ); + } + ethjson::spec::HashOrMap::Hash(h) => assert_valid_hash(&(*h).into(), b), + } +} + +pub fn assert_valid_hash(h: &H256, b: &BTreeMap) { + let tree = b + .iter() + .map(|(address, account)| { + let storage_root = ethereum::util::sec_trie_root( + account + .storage + .iter() + .map(|(k, v)| (k, rlp::encode(&U256::from_big_endian(&v[..])))), + ); + let code_hash = H256::from_slice(Keccak256::digest(&account.code).as_slice()); + + let account = TrieAccount { + nonce: account.nonce, + balance: account.balance, + storage_root, + code_hash, + code_version: U256::zero(), + }; + + (address, rlp::encode(&account)) + }) + .collect::>(); + + let root = ethereum::util::sec_trie_root(tree); + let expect = h; + + if root != *expect { + panic!( + "Hash not equal; calculated: {:?}, expect: {:?}\nState: {:#x?}", + root, expect, b + ); + } +} + +pub fn flush() { + use std::io::{self, Write}; + + io::stdout().flush().expect("Could not flush stdout"); +} + +pub mod transaction { + use ethjson::maybe::MaybeEmpty; + use ethjson::transaction::Transaction; + use ethjson::uint::Uint; + use evm::gasometer::{self, Gasometer}; + use primitive_types::{H160, H256, U256}; + + pub fn validate( + tx: Transaction, + block_gas_limit: U256, + caller_balance: U256, + config: &evm::Config, + ) -> Result { + match intrinsic_gas(&tx, config) { + None => return Err(InvalidTxReason::IntrinsicGas), + Some(required_gas) => { + if tx.gas_limit < Uint(U256::from(required_gas)) { + return Err(InvalidTxReason::IntrinsicGas); + } + } + } + + if block_gas_limit < tx.gas_limit.0 { + return Err(InvalidTxReason::GasLimitReached); + } + + let required_funds = tx.gas_limit.0 * tx.gas_price.0 + tx.value.0; + if caller_balance < required_funds { + return Err(InvalidTxReason::OutOfFund); + } + + Ok(tx) + } + + fn intrinsic_gas(tx: &Transaction, config: &evm::Config) -> Option { + let is_contract_creation = match tx.to { + MaybeEmpty::None => true, + MaybeEmpty::Some(_) => false, + }; + let data = &tx.data; + let access_list: Vec<(H160, Vec)> = tx + .access_list + .iter() + .map(|(a, s)| (a.0, s.iter().map(|h| h.0).collect())) + .collect(); + + let cost = if is_contract_creation { + gasometer::create_transaction_cost(data, &access_list) + } else { + gasometer::call_transaction_cost(data, &access_list) + }; + + let mut g = Gasometer::new(u64::MAX, config); + g.record_transaction(cost).ok()?; + + Some(g.total_used_gas()) + } + + pub enum InvalidTxReason { + IntrinsicGas, + OutOfFund, + GasLimitReached, + } +} diff --git a/evm-tests/jsontests/src/vm.rs b/evm-tests/jsontests/src/vm.rs new file mode 100644 index 000000000..cc0d69b5d --- /dev/null +++ b/evm-tests/jsontests/src/vm.rs @@ -0,0 +1,118 @@ +use crate::utils::*; +use evm::backend::{ApplyBackend, MemoryAccount, MemoryBackend, MemoryVicinity}; +use evm::executor::stack::{MemoryStackState, StackExecutor, StackSubstateMetadata}; +use evm::Config; +use primitive_types::{H160, H256, U256}; +use serde::Deserialize; +use std::collections::BTreeMap; +use std::rc::Rc; + +#[derive(Deserialize, Debug)] +pub struct Test(ethjson::vm::Vm); + +impl Test { + pub fn unwrap_to_pre_state(&self) -> BTreeMap { + unwrap_to_state(&self.0.pre_state) + } + + pub fn unwrap_to_vicinity(&self) -> MemoryVicinity { + let block_randomness = self.0.env.random.map(|r| { + // Convert between U256 and H256. U256 is in little-endian but since H256 is just + // a string-like byte array, it's big endian (MSB is the first element of the array). + // + // Byte order here is important because this opcode has the same value as DIFFICULTY + // (0x44), and so for older forks of Ethereum, the threshold value of 2^64 is used to + // distinguish between the two: if it's below, the value corresponds to the DIFFICULTY + // opcode, otherwise to the PREVRANDAO opcode. + let mut buf = [0u8; 32]; + r.0.to_big_endian(&mut buf); + H256(buf) + }); + + MemoryVicinity { + gas_price: self.0.transaction.gas_price.into(), + origin: self.0.transaction.origin.into(), + block_hashes: Vec::new(), + block_number: self.0.env.number.into(), + block_coinbase: self.0.env.author.into(), + block_timestamp: self.0.env.timestamp.into(), + block_difficulty: self.0.env.difficulty.into(), + block_gas_limit: self.0.env.gas_limit.into(), + chain_id: U256::zero(), + block_base_fee_per_gas: self.0.transaction.gas_price.into(), + block_randomness, + } + } + + pub fn unwrap_to_code(&self) -> Rc> { + Rc::new(self.0.transaction.code.clone().into()) + } + + pub fn unwrap_to_data(&self) -> Rc> { + Rc::new(self.0.transaction.data.clone().into()) + } + + pub fn unwrap_to_context(&self) -> evm::Context { + evm::Context { + address: self.0.transaction.address.into(), + caller: self.0.transaction.sender.into(), + apparent_value: self.0.transaction.value.into(), + } + } + + pub fn unwrap_to_return_value(&self) -> Vec { + self.0.output.clone().unwrap().into() + } + + pub fn unwrap_to_gas_limit(&self) -> u64 { + self.0.transaction.gas.into() + } + + pub fn unwrap_to_post_gas(&self) -> u64 { + self.0.gas_left.unwrap().into() + } +} + +pub fn test(name: &str, test: Test) { + print!("Running test {} ... ", name); + flush(); + + let original_state = test.unwrap_to_pre_state(); + let vicinity = test.unwrap_to_vicinity(); + let config = Config::frontier(); + let mut backend = MemoryBackend::new(&vicinity, original_state); + let metadata = StackSubstateMetadata::new(test.unwrap_to_gas_limit(), &config); + let state = MemoryStackState::new(metadata, &backend); + let precompile = BTreeMap::new(); + let mut executor = StackExecutor::new_with_precompiles(state, &config, &precompile); + + let code = test.unwrap_to_code(); + let data = test.unwrap_to_data(); + let context = test.unwrap_to_context(); + let mut runtime = + evm::Runtime::new(code, data, context, config.stack_limit, config.memory_limit); + + let reason = executor.execute(&mut runtime); + let gas = executor.gas(); + let (values, logs) = executor.into_state().deconstruct(); + backend.apply(values, logs, false); + + if test.0.output.is_none() { + print!("{:?} ", reason); + + assert!(!reason.is_succeed()); + assert!(test.0.post_state.is_none() && test.0.gas_left.is_none()); + } else { + let expected_post_gas = test.unwrap_to_post_gas(); + print!("{:?} ", reason); + + assert_eq!( + runtime.machine().return_value(), + test.unwrap_to_return_value() + ); + assert_valid_state(test.0.post_state.as_ref().unwrap(), backend.state()); + assert_eq!(gas, expected_post_gas); + } + + println!("succeed"); +} diff --git a/evm-tests/jsontests/tests/state.rs b/evm-tests/jsontests/tests/state.rs new file mode 100644 index 000000000..49034f02e --- /dev/null +++ b/evm-tests/jsontests/tests/state.rs @@ -0,0 +1,295 @@ +use evm_jsontests::state as statetests; +use std::fs::{self, File}; +use std::io::BufReader; +use std::path::PathBuf; +use std::{collections::HashMap, path::Path}; + +pub fn run(dir: &str) { + let _ = env_logger::try_init(); + + let mut dest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + dest.push(dir); + + for entry in fs::read_dir(dest).unwrap() { + let entry = entry.unwrap(); + if let Some(s) = entry.file_name().to_str() { + if s.starts_with('.') { + continue; + } + } + + let path = entry.path(); + + if should_skip(&path) { + println!("Skipping test case {path:?}"); + continue; + } + + let file = File::open(&path).expect("Open file failed"); + + let reader = BufReader::new(file); + let coll: HashMap = serde_json::from_reader(reader) + .unwrap_or_else(|e| { + panic!("Parsing test case {:?} failed: {:?}", path, e); + }); + + for (name, test) in coll { + statetests::test(&name, test); + } + } +} + +// NOTE: Add a comment here explaining why you're skipping a test case. +const SKIPPED_CASES: &[&str] = &[ + // This is an expected failure case for testing that the VM rejects + // transactions with values that are too large, but it's geth + // specific because geth parses the hex string later in the test + // run, whereas this test runner parses everything up-front before + // running the test. + "stTransactionTest/ValueOverflow", + // The below test cases are failing in geth too and as such are + // skipped here until they are fixed there (otherwise we don't know + // what the expected value should be for each test output). + "stTransactionTest/HighGasPrice", + "stCreateTest/CreateTransactionHighNonce", +]; + +fn should_skip(path: &Path) -> bool { + let matches = |case: &str| { + let file_stem = path.file_stem().unwrap(); + let dir_path = path.parent().unwrap(); + let dir_name = dir_path.file_name().unwrap(); + Path::new(dir_name).join(file_stem) == Path::new(case) + }; + + for case in SKIPPED_CASES { + if matches(case) { + return true; + } + } + + false +} + +#[test] +fn st_args_zero_one_balance() { + run("res/ethtests/GeneralStateTests/stArgsZeroOneBalance") +} +#[test] +fn st_attack() { + run("res/ethtests/GeneralStateTests/stAttackTest") +} +#[test] +fn st_bad_opcode() { + run("res/ethtests/GeneralStateTests/stBadOpcode") +} +#[test] +fn st_bugs() { + run("res/ethtests/GeneralStateTests/stBugs") +} +#[test] +fn st_call_code() { + run("res/ethtests/GeneralStateTests/stCallCodes") +} +#[test] +fn st_call_create_call_code() { + run("res/ethtests/GeneralStateTests/stCallCreateCallCodeTest") +} +#[test] +fn st_call_delegate_codes_call_code_homestead() { + run("res/ethtests/GeneralStateTests/stCallDelegateCodesCallCodeHomestead") +} +#[test] +fn st_call_delegate_codes_homestead() { + run("res/ethtests/GeneralStateTests/stCallDelegateCodesHomestead") +} +#[test] +fn st_chain_id() { + run("res/ethtests/GeneralStateTests/stChainId") +} +#[test] +fn st_code_copy() { + run("res/ethtests/GeneralStateTests/stCodeCopyTest") +} +#[test] +fn st_code_size_limit() { + run("res/ethtests/GeneralStateTests/stCodeSizeLimit") +} +#[test] +#[ignore] +fn st_create2() { + run("res/ethtests/GeneralStateTests/stCreate2") +} +#[test] +fn st_create() { + run("res/ethtests/GeneralStateTests/stCreateTest") +} +#[test] +fn st_delegate_call_homestead() { + run("res/ethtests/GeneralStateTests/stDelegatecallTestHomestead") +} +#[test] +fn st_eip150_single_code_gas_prices() { + run("res/ethtests/GeneralStateTests/stEIP150singleCodeGasPrices") +} +#[test] +fn st_eip150_specific() { + run("res/ethtests/GeneralStateTests/stEIP150Specific") +} +#[test] +fn st_eip1559() { + run("res/ethtests/GeneralStateTests/stEIP1559") +} +#[test] +fn st_eip158_specific() { + run("res/ethtests/GeneralStateTests/stEIP158Specific") +} +#[test] +fn st_eip2930() { + run("res/ethtests/GeneralStateTests/stEIP2930") +} +#[test] +fn st_example() { + run("res/ethtests/GeneralStateTests/stExample") +} +#[test] +fn st_ext_code_hash() { + run("res/ethtests/GeneralStateTests/stExtCodeHash") +} +#[test] +fn st_homestead_specific() { + run("res/ethtests/GeneralStateTests/stHomesteadSpecific") +} +#[test] +fn st_init_code() { + run("res/ethtests/GeneralStateTests/stInitCodeTest") +} +#[test] +fn st_log() { + run("res/ethtests/GeneralStateTests/stLogTests") +} +#[test] +fn st_mem_expanding_eip_150_calls() { + run("res/ethtests/GeneralStateTests/stMemExpandingEIP150Calls") +} +#[test] +fn st_memory_stress() { + run("res/ethtests/GeneralStateTests/stMemoryStressTest") +} +#[test] +fn st_memory() { + run("res/ethtests/GeneralStateTests/stMemoryTest") +} +#[test] +fn st_non_zero_calls() { + run("res/ethtests/GeneralStateTests/stNonZeroCallsTest") +} +#[test] +fn st_precompiled_contracts() { + run("res/ethtests/GeneralStateTests/stPreCompiledContracts") +} +#[test] +#[ignore] +fn st_precompiled_contracts2() { + run("res/ethtests/GeneralStateTests/stPreCompiledContracts2") +} +#[test] +#[ignore] +fn st_quadratic_complexity() { + run("res/ethtests/GeneralStateTests/stQuadraticComplexityTest") +} +#[test] +fn st_random() { + run("res/ethtests/GeneralStateTests/stRandom") +} +#[test] +fn st_random2() { + run("res/ethtests/GeneralStateTests/stRandom2") +} +#[test] +fn st_recursive_create() { + run("res/ethtests/GeneralStateTests/stRecursiveCreate") +} +#[test] +fn st_refund() { + run("res/ethtests/GeneralStateTests/stRefundTest") +} +#[test] +fn st_return_data() { + run("res/ethtests/GeneralStateTests/stReturnDataTest") +} +#[test] +#[ignore] +fn st_revert() { + run("res/ethtests/GeneralStateTests/stRevertTest") +} +#[test] +fn st_self_balance() { + run("res/ethtests/GeneralStateTests/stSelfBalance") +} +#[test] +fn st_shift() { + run("res/ethtests/GeneralStateTests/stShift") +} +#[test] +fn st_sload() { + run("res/ethtests/GeneralStateTests/stSLoadTest") +} +#[test] +fn st_solidity() { + run("res/ethtests/GeneralStateTests/stSolidityTest") +} +#[test] +#[ignore] +fn st_special() { + run("res/ethtests/GeneralStateTests/stSpecialTest") +} +// Some of the collison test in sstore conflicts with evm's internal +// handlings. Those situations will never happen on a production chain (an empty +// account with storage values), so we can safely ignore them. +#[test] +#[ignore] +fn st_sstore() { + run("res/ethtests/GeneralStateTests/stSStoreTest") +} +#[test] +fn st_stack() { + run("res/ethtests/GeneralStateTests/stStackTests") +} +#[test] +#[ignore] +fn st_static_call() { + run("res/ethtests/GeneralStateTests/stStaticCall") +} +#[test] +fn st_system_operations() { + run("res/ethtests/GeneralStateTests/stSystemOperationsTest") +} +#[test] +fn st_transaction() { + run("res/ethtests/GeneralStateTests/stTransactionTest") +} +#[test] +fn st_transition() { + run("res/ethtests/GeneralStateTests/stTransitionTest") +} +#[test] +fn st_wallet() { + run("res/ethtests/GeneralStateTests/stWalletTest") +} +#[test] +fn st_zero_calls_revert() { + run("res/ethtests/GeneralStateTests/stZeroCallsRevert"); +} +#[test] +fn st_zero_calls() { + run("res/ethtests/GeneralStateTests/stZeroCallsTest") +} +#[test] +fn st_zero_knowledge() { + run("res/ethtests/GeneralStateTests/stZeroKnowledge") +} +#[test] +fn st_zero_knowledge2() { + run("res/ethtests/GeneralStateTests/stZeroKnowledge2") +} diff --git a/evm-tests/jsontests/tests/vm.rs b/evm-tests/jsontests/tests/vm.rs new file mode 100644 index 000000000..8291a536a --- /dev/null +++ b/evm-tests/jsontests/tests/vm.rs @@ -0,0 +1,78 @@ +use evm_jsontests::vm as vmtests; +use std::collections::HashMap; +use std::fs::{self, File}; +use std::io::BufReader; +use std::path::PathBuf; + +pub fn run(dir: &str) { + let _ = env_logger::try_init(); + + let mut dest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + dest.push(dir); + + for entry in fs::read_dir(dest).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + + let file = File::open(path).expect("Open file failed"); + + let reader = BufReader::new(file); + let coll = serde_json::from_reader::<_, HashMap>(reader) + .expect("Parse test cases failed"); + + for (name, test) in coll { + vmtests::test(&name, test); + } + } +} + +// TODO: upgrade to GeneralStateTests/VMTests instead of using LegacyTests version +#[test] +fn vm_arithmetic() { + run("res/ethtests/LegacyTests/Constantinople/VMTests/vmArithmeticTest"); +} +#[test] +fn vm_bitwise_logic() { + run("res/ethtests/LegacyTests/Constantinople/VMTests/vmBitwiseLogicOperation"); +} +#[test] +fn vm_block_info() { + run("res/ethtests/LegacyTests/Constantinople/VMTests/vmBlockInfoTest"); +} +#[test] +fn vm_environmental_info() { + run("res/ethtests/LegacyTests/Constantinople/VMTests/vmEnvironmentalInfo"); +} +#[test] +fn vm_io_and_flow() { + run("res/ethtests/LegacyTests/Constantinople/VMTests/vmIOandFlowOperations"); +} +#[test] +fn vm_log() { + run("res/ethtests/LegacyTests/Constantinople/VMTests/vmLogTest"); +} +#[test] +#[ignore] +fn vm_performance() { + run("res/ethtests/LegacyTests/Constantinople/VMTests/vmPerformance"); +} +#[test] +fn vm_push_dup_swap() { + run("res/ethtests/LegacyTests/Constantinople/VMTests/vmPushDupSwapTest"); +} +#[test] +fn vm_random() { + run("res/ethtests/LegacyTests/Constantinople/VMTests/vmRandomTest"); +} +#[test] +fn vm_sha3() { + run("res/ethtests/LegacyTests/Constantinople/VMTests/vmSha3Test"); +} +#[test] +fn vm_system() { + run("res/ethtests/LegacyTests/Constantinople/VMTests/vmSystemOperations"); +} +#[test] +fn vm_other() { + run("res/ethtests/LegacyTests/Constantinople/VMTests/vmTests"); +}