From cbfc5ea114ad962f0272580e15034ea28c0a6afc Mon Sep 17 00:00:00 2001 From: pikatos Date: Wed, 6 Dec 2023 16:14:40 +0100 Subject: [PATCH 1/8] EVM/Kernel: implement precompiled blake2f EVM/Kernel: add blake2f to precompte_set + decoding optimization EVM/Kernel: make linter happy --- .../kernel_evm/evm_execution/src/eip152.rs | 75 +++++++++++ etherlink/kernel_evm/evm_execution/src/lib.rs | 2 + .../evm_execution/src/precompiles.rs | 116 ++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 etherlink/kernel_evm/evm_execution/src/eip152.rs diff --git a/etherlink/kernel_evm/evm_execution/src/eip152.rs b/etherlink/kernel_evm/evm_execution/src/eip152.rs new file mode 100644 index 000000000000..00eb68e4714d --- /dev/null +++ b/etherlink/kernel_evm/evm_execution/src/eip152.rs @@ -0,0 +1,75 @@ +/// 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, +]; + +#[inline(always)] +/// The G mixing function. See https://tools.ietf.org/html/rfc7693#section-3.1 +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 Blake2 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[..h.len()].copy_from_slice(h); // First half from state. + v[h.len()..].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/etherlink/kernel_evm/evm_execution/src/lib.rs b/etherlink/kernel_evm/evm_execution/src/lib.rs index f25038f4e3d0..31cf20d1e332 100644 --- a/etherlink/kernel_evm/evm_execution/src/lib.rs +++ b/etherlink/kernel_evm/evm_execution/src/lib.rs @@ -19,6 +19,8 @@ use tezos_evm_logging::{log, Level::*}; use tezos_smart_rollup_storage::StorageError; use thiserror::Error; +mod eip152; + pub mod abi; mod access_record; pub mod account_storage; diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles.rs b/etherlink/kernel_evm/evm_execution/src/precompiles.rs index 54d3d98b4885..24da01ce7a5d 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles.rs @@ -13,6 +13,7 @@ use std::{cmp::min, str::FromStr, vec}; +use crate::eip152; use crate::handler::EvmHandler; use crate::zk_precompiled::{ecadd_precompile, ecmul_precompile, ecpairing_precompile}; use crate::EthereumError; @@ -357,6 +358,113 @@ fn ripemd160_precompile( }) } +fn blake2f_output_for_wrong_input() -> PrecompileOutcome { + PrecompileOutcome { + exit_status: ExitReason::Succeed(ExitSucceed::Returned), + output: vec![], + withdrawals: vec![], + estimated_ticks: 0, + } +} + +trait Decodable { + fn decode_from_le_slice(&mut self, source: &[u8]); +} + +impl Decodable for [u64; N] { + fn decode_from_le_slice(&mut self, src: &[u8]) { + let mut word_buf = [0_u8; 8]; + for (i, word) in self.iter_mut().enumerate() { + word_buf.copy_from_slice(&src[i * 8..(i + 1) * 8]); + *word = u64::from_le_bytes(word_buf); + } + } +} + +fn blake2f_precompile( + handler: &mut EvmHandler, + input: &[u8], + _context: &Context, + _is_static: bool, + _transfer: Option, +) -> Result { + log!(handler.borrow_host(), Info, "Calling blake2f precompile"); + + // The precompile requires 6 inputs tightly encoded, taking exactly 213 bytes + if input.len() != 213 { + return Ok(blake2f_output_for_wrong_input()); + } + + // the number of rounds - 32-bit unsigned big-endian word + let mut rounds_buf = [0_u8; 4]; + rounds_buf.copy_from_slice(&input[0..4]); + let rounds: u32 = u32::from_be_bytes(rounds_buf); + + // check that enough ressources to execute (gas / ticks) are available + let estimated_ticks = + fail_if_too_much!(tick_model::ticks_of_blake2f(rounds), handler); + let cost = rounds as u64; // static_gas + dynamic_gas + if let Err(err) = handler.record_cost(cost) { + log!( + handler.borrow_host(), + Info, + "Couldn't record the cost of blake2f {:?}", + err + ); + return Ok(PrecompileOutcome { + exit_status: ExitReason::Error(err), + output: vec![], + withdrawals: vec![], + estimated_ticks, + }); + } + log!( + handler.borrow_host(), + Debug, + "Input is {:?}", + hex::encode(input) + ); + + // parse inputs + // the state vector - 8 unsigned 64-bit little-endian words + let mut h = [0_u64; 8]; + h.decode_from_le_slice(&input[4..68]); + + // the message block vector - 16 unsigned 64-bit little-endian words + let mut m = [0_u64; 16]; + m.decode_from_le_slice(&input[68..196]); + + // offset counters - 2 unsigned 64-bit little-endian words + let mut t = [0_u64; 2]; + t.decode_from_le_slice(&input[196..212]); + + // the final block indicator flag - 8-bit word (true if 1 or false if 0) + let f = match input[212] { + 1 => true, + 0 => false, + _ => return Ok(blake2f_output_for_wrong_input()), + }; + + eip152::compress(&mut h, m, t, f, rounds as usize); + + let mut output = [0_u8; 64]; + for (i, state_word) in h.iter().enumerate() { + output[i * 8..(i + 1) * 8].copy_from_slice(&state_word.to_le_bytes()); + } + log!( + handler.borrow_host(), + Debug, + "Output is {:?}", + hex::encode(output) + ); + Ok(PrecompileOutcome { + exit_status: ExitReason::Succeed(ExitSucceed::Returned), + output: output.to_vec(), + withdrawals: vec![], + estimated_ticks, + }) +} + /// Implementation of Etherelink specific withdrawals precompiled contract. fn withdrawal_precompile( handler: &mut EvmHandler, @@ -474,6 +582,10 @@ pub fn precompile_set() -> PrecompileBTreeMap { H160::from_low_u64_be(8u64), ecpairing_precompile as PrecompileFn, ), + ( + H160::from_low_u64_be(9u64), + blake2f_precompile as PrecompileFn, + ), ( // Prefixed by 'ff' to make sure we will not conflict with any // upcoming Ethereum upgrades. @@ -506,6 +618,10 @@ mod tick_model { pub fn ticks_of_ecrecover() -> u64 { 30_000_000 } + + pub fn ticks_of_blake2f(rounds: u32) -> u64 { + 1_000_000 * rounds as u64 // TODO: determine the number of ticks + } } #[cfg(test)] -- GitLab From f18ac01094245a2378f8b731e49561c47748135d Mon Sep 17 00:00:00 2001 From: pikatos Date: Tue, 9 Jan 2024 09:43:22 +0100 Subject: [PATCH 2/8] EVM/Kernel: add unit tests for blake2f EVM/Kernel: add more tests from spec --- .../evm_execution/src/precompiles.rs | 102 +++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles.rs b/etherlink/kernel_evm/evm_execution/src/precompiles.rs index 24da01ce7a5d..25ecc68e0f39 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles.rs @@ -193,7 +193,7 @@ fn ecrecover_precompile( ) -> Result { log!(handler.borrow_host(), Info, "Calling ecrecover precompile"); - // check that enough ressources to execute (gas / ticks) are available + // check that enough resources to execute (gas / ticks) are available let estimated_ticks = fail_if_too_much!(tick_model::ticks_of_ecrecover(), handler); let cost = 3000; if let Err(err) = handler.record_cost(cost) { @@ -1150,6 +1150,106 @@ mod tests { ); } + #[test] + fn test_blake2f_invalid_empty() { + // act + let input = [0; 0]; + let result = + execute_precompiled(H160::from_low_u64_be(9), &input, None, Some(25000)); + + // assert + // expected outcome is OK and empty output + + assert!(result.is_ok()); + let outcome = result.unwrap(); + assert!(outcome.is_success); + assert_eq!(Some(vec![]), outcome.result); + } + + #[test] + fn text_blake2f_invalid_flag() { + let input = hex::decode( + "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbab\ + d9831f79217e1319cde05b616263000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000300000000000000000000000000000002" + ).unwrap(); + + let result = + execute_precompiled(H160::from_low_u64_be(9), &input, None, Some(25000)); + + assert!(result.is_ok()); + let outcome = result.unwrap(); + assert!(outcome.is_success); + assert_eq!(Some(vec![]), outcome.result); + } + + struct Blake2fTest { + input: &'static str, + expected: &'static str, + } + + const BLAKE2F_TESTS: [Blake2fTest; 4] = [ + Blake2fTest { + input: "\ + 0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbab\ + d9831f79217e1319cde05b616263000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000300000000000000000000000000000001", + expected: "\ + 08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079\ + 217e1319cde05b" + }, + Blake2fTest { + input: "\ + 0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbab\ + d9831f79217e1319cde05b616263000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000300000000000000000000000000000001", + expected: "\ + ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab9\ + 2386edd4009923" + }, + Blake2fTest { + input: "\ + 0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbab\ + d9831f79217e1319cde05b616263000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000300000000000000000000000000000000", + expected: "\ + 75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a6\ + 10c2116fde4735" + }, + Blake2fTest { + input: "\ + 0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbab\ + d9831f79217e1319cde05b616263000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000300000000000000000000000000000001", + expected: "\ + b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e71\ + 7b9293fed7a421" + } + ]; + + #[test] + fn test_blake2f_input_spec() { + for test in BLAKE2F_TESTS.iter() { + let input = hex::decode(test.input).unwrap(); + let result = + execute_precompiled(H160::from_low_u64_be(9), &input, None, Some(25000)); + + assert!(result.is_ok()); + let outcome = result.unwrap(); + println!("{}", outcome.gas_used); + assert!(outcome.is_success); + + let expected = hex::decode(test.expected).unwrap(); + + assert_eq!(Some(expected), outcome.result); + } + } + struct ModexpTestCase { input: &'static str, expected: &'static str, -- GitLab From 752c3b41ecd5b76ba9a7b6698f7a30cdf7586d0f Mon Sep 17 00:00:00 2001 From: pikatos Date: Tue, 16 Jan 2024 09:58:17 +0100 Subject: [PATCH 3/8] EVM/Kernel: add blake2f benchmark --- .../scripts/benchmarks/bench_blake2f.js | 30 +++++++++++++++++++ .../benchmarks/scripts/benchmarks_list.json | 1 + 2 files changed, 31 insertions(+) create mode 100644 etherlink/kernel_evm/benchmarks/scripts/benchmarks/bench_blake2f.js diff --git a/etherlink/kernel_evm/benchmarks/scripts/benchmarks/bench_blake2f.js b/etherlink/kernel_evm/benchmarks/scripts/benchmarks/bench_blake2f.js new file mode 100644 index 000000000000..d8911cef7c76 --- /dev/null +++ b/etherlink/kernel_evm/benchmarks/scripts/benchmarks/bench_blake2f.js @@ -0,0 +1,30 @@ +const utils = require('./utils'); +const { ethers } = require('ethers'); +let faucet = require('./players/faucet.json'); +let player1 = require('./players/player1.json'); + +let txs = []; + +txs.push(utils.transfer(faucet, player1, 100000000)) + +let long_string = Buffer.from('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. In fermentum et sollicitudin ac orci phasellus. Augue interdum velit euismod in pellentesque massa placerat duis ultricies. Sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Convallis convallis tellus id interdum velit laoreet. At tempor commodo ullamcorper a lacus. Sit amet purus gravida quis blandit turpis cursus in. Ornare suspendisse sed nisi lacus sed. Vitae tempus quam pellentesque nec nam. Sit amet nisl suscipit adipiscing bibendum est. Eu nisl nunc mi ipsum faucibus vitae aliquet nec. Posuere urna nec tincidunt praesent semper feugiat nibh. Hendrerit dolor magna eget est lorem ipsum dolor sit amet. Tristique et egestas quis ipsum suspendisse ultrices gravida dictum. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Fringilla urna porttitor rhoncus dolor purus non enim. Enim lobortis scelerisque fermentum dui faucibus in ornare quam. Dictumst vestibulum rhoncus est pellentesque elit ullamcorper.').toString('hex') +let blake2f_precompile_address = "0000000000000000000000000000000000000009" + + +function build_precompile_call(address, input) { + return utils.send(player1, address, 0, input) +} +let input = "48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e\ +511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b\ +616263000000000000000000000000000000000000000000000000000000000000000000000000\ +000000000000000000000000000000000000000000000000000000000000000000000000000000\ +000000000000000000000000000000000000000000000000000000000000000000000000000000\ +0000000000000000000000\ +03000000000000000000000000000000\ +01" +for (var i = 0; i < 128; i++) { + // we add after the input an growing arbitrary number of bytes + // they should be ignored by the precompiled contract and not impact the performances + txs.push(build_precompile_call(blake2f_precompile_address, '0x' + i.toString(16).padStart(8, '0') + input)); +} +utils.print_bench([txs]) \ No newline at end of file diff --git a/etherlink/kernel_evm/benchmarks/scripts/benchmarks_list.json b/etherlink/kernel_evm/benchmarks/scripts/benchmarks_list.json index 307b9548814d..a30abe3e093c 100644 --- a/etherlink/kernel_evm/benchmarks/scripts/benchmarks_list.json +++ b/etherlink/kernel_evm/benchmarks/scripts/benchmarks_list.json @@ -43,6 +43,7 @@ "benchmarks/bench_creates_erc1155.js", "benchmarks/bench_precompile.js", "benchmarks/bench_ecdsa.js", + "benchmarks/bench_blake2f.js", "benchmarks/bench_linear_transfers.js 0", "benchmarks/bench_linear_transfers.js 5", "benchmarks/bench_linear_transfers.js 10", -- GitLab From bca94007f360b2b6e03592decc5145071a51d496 Mon Sep 17 00:00:00 2001 From: pikatos Date: Thu, 18 Jan 2024 09:03:53 +0100 Subject: [PATCH 4/8] EVM/Kernel: add blake2f tick model --- etherlink/kernel_evm/benchmarks/scripts/run_benchmarks.js | 2 +- etherlink/kernel_evm/evm_execution/src/precompiles.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etherlink/kernel_evm/benchmarks/scripts/run_benchmarks.js b/etherlink/kernel_evm/benchmarks/scripts/run_benchmarks.js index 9000f5ac8798..572f95b9e1a0 100644 --- a/etherlink/kernel_evm/benchmarks/scripts/run_benchmarks.js +++ b/etherlink/kernel_evm/benchmarks/scripts/run_benchmarks.js @@ -76,7 +76,7 @@ function parse_data(opcode, gas_and_result) { /// Parses the section and push the sample into the set of opcodes. function push_profiler_sections(output, opcodes, precompiles) { const section_regex = /\__wasm_debugger__::Section{ticks:(\d+);data:\((0x[0-9a-fA-F]*),0x([0-9a-fA-F]*)\)}/g; - let precompiled_address_set = new Set([1, 2, 3, 4, 32]); + let precompiled_address_set = new Set([1, 2, 3, 4, 9, 32]); for (const match of output.matchAll(section_regex)) { let is_opcode_data = match[2].length == 4; diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles.rs b/etherlink/kernel_evm/evm_execution/src/precompiles.rs index 25ecc68e0f39..77d841f24db3 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles.rs @@ -620,7 +620,7 @@ mod tick_model { } pub fn ticks_of_blake2f(rounds: u32) -> u64 { - 1_000_000 * rounds as u64 // TODO: determine the number of ticks + 1_850_000 + 3_200 * rounds as u64 } } -- GitLab From 76b65a778af2617a1ba358c037023297b549ee54 Mon Sep 17 00:00:00 2001 From: pikatos Date: Thu, 25 Jan 2024 09:06:09 +0100 Subject: [PATCH 5/8] EVM/Kernel: add Apache 2 license EVM/Kernel: add SputnikVM reference --- etherlink/kernel_evm/evm_execution/src/eip152.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etherlink/kernel_evm/evm_execution/src/eip152.rs b/etherlink/kernel_evm/evm_execution/src/eip152.rs index 00eb68e4714d..45eb1ac1d386 100644 --- a/etherlink/kernel_evm/evm_execution/src/eip152.rs +++ b/etherlink/kernel_evm/evm_execution/src/eip152.rs @@ -1,3 +1,5 @@ +// SputnikVM - Apache 2.0 LICENSE - https://github.com/rust-ethereum/evm/blob/master/LICENSE + /// 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. -- GitLab From 1f6645a29e9e4289e9f4dfdb40c0c58976cd0809 Mon Sep 17 00:00:00 2001 From: pikatos Date: Tue, 30 Jan 2024 17:19:17 +0100 Subject: [PATCH 6/8] EVM/Kernel: comsume remaining gas for wrong input --- .../evm_execution/src/precompiles.rs | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles.rs b/etherlink/kernel_evm/evm_execution/src/precompiles.rs index 77d841f24db3..8f319e6813fc 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles.rs @@ -358,12 +358,29 @@ fn ripemd160_precompile( }) } -fn blake2f_output_for_wrong_input() -> PrecompileOutcome { - PrecompileOutcome { - exit_status: ExitReason::Succeed(ExitSucceed::Returned), - output: vec![], - withdrawals: vec![], - estimated_ticks: 0, +fn blake2f_output_for_wrong_input( + handler: &mut EvmHandler, +) -> PrecompileOutcome { + if let Err(err) = handler.record_cost(handler.gas_left().as_u64()) { + log!( + handler.borrow_host(), + Info, + "Couldn't record the cost of blake2f {:?}", + err + ); + PrecompileOutcome { + exit_status: ExitReason::Error(err), + output: vec![], + withdrawals: vec![], + estimated_ticks: 0, + } + } else { + PrecompileOutcome { + exit_status: ExitReason::Succeed(ExitSucceed::Returned), + output: vec![], + withdrawals: vec![], + estimated_ticks: 0, + } } } @@ -392,7 +409,7 @@ fn blake2f_precompile( // The precompile requires 6 inputs tightly encoded, taking exactly 213 bytes if input.len() != 213 { - return Ok(blake2f_output_for_wrong_input()); + return Ok(blake2f_output_for_wrong_input(handler)); } // the number of rounds - 32-bit unsigned big-endian word @@ -400,7 +417,7 @@ fn blake2f_precompile( rounds_buf.copy_from_slice(&input[0..4]); let rounds: u32 = u32::from_be_bytes(rounds_buf); - // check that enough ressources to execute (gas / ticks) are available + // check that enough resources to execute (gas / ticks) are available let estimated_ticks = fail_if_too_much!(tick_model::ticks_of_blake2f(rounds), handler); let cost = rounds as u64; // static_gas + dynamic_gas @@ -442,7 +459,7 @@ fn blake2f_precompile( let f = match input[212] { 1 => true, 0 => false, - _ => return Ok(blake2f_output_for_wrong_input()), + _ => return Ok(blake2f_output_for_wrong_input(handler)), }; eip152::compress(&mut h, m, t, f, rounds as usize); @@ -1154,33 +1171,38 @@ mod tests { fn test_blake2f_invalid_empty() { // act let input = [0; 0]; + let gas_limit: u64 = 25000; let result = - execute_precompiled(H160::from_low_u64_be(9), &input, None, Some(25000)); + execute_precompiled(H160::from_low_u64_be(9), &input, None, Some(gas_limit)); // assert // expected outcome is OK and empty output assert!(result.is_ok()); let outcome = result.unwrap(); + println!("{}", outcome.gas_used); assert!(outcome.is_success); + assert!(outcome.gas_used == gas_limit); // all gas should be consumed assert_eq!(Some(vec![]), outcome.result); } #[test] - fn text_blake2f_invalid_flag() { + fn test_blake2f_invalid_flag() { let input = hex::decode( "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbab\ d9831f79217e1319cde05b616263000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ 0000000000000000000000000000000000000000000000000300000000000000000000000000000002" ).unwrap(); - + let gas_limit: u64 = 25000; let result = - execute_precompiled(H160::from_low_u64_be(9), &input, None, Some(25000)); + execute_precompiled(H160::from_low_u64_be(9), &input, None, Some(gas_limit)); assert!(result.is_ok()); let outcome = result.unwrap(); + println!("{}", outcome.gas_used); assert!(outcome.is_success); + assert!(outcome.gas_used == gas_limit); // all gas shoule be consumed assert_eq!(Some(vec![]), outcome.result); } -- GitLab From 68ac8bb90a410103f6e3d6787ff72f186bfb5a09 Mon Sep 17 00:00:00 2001 From: pikatos Date: Tue, 6 Feb 2024 09:27:23 +0100 Subject: [PATCH 7/8] EVM/Kernel: return an error for invalid input --- .../evm_execution/src/precompiles.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles.rs b/etherlink/kernel_evm/evm_execution/src/precompiles.rs index 8f319e6813fc..8d6ba4822116 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles.rs @@ -18,8 +18,9 @@ use crate::handler::EvmHandler; use crate::zk_precompiled::{ecadd_precompile, ecmul_precompile, ecpairing_precompile}; use crate::EthereumError; use crate::{abi, modexp::modexp_precompile}; +use alloc::borrow::Cow; use alloc::collections::btree_map::BTreeMap; -use evm::{Context, ExitReason, ExitRevert, ExitSucceed, Handler, Transfer}; +use evm::{Context, ExitError, ExitReason, ExitRevert, ExitSucceed, Handler, Transfer}; use host::runtime::Runtime; use libsecp256k1::{recover, Message, RecoveryId, Signature}; use primitive_types::{H160, U256}; @@ -376,7 +377,9 @@ fn blake2f_output_for_wrong_input( } } else { PrecompileOutcome { - exit_status: ExitReason::Succeed(ExitSucceed::Returned), + exit_status: ExitReason::Error(ExitError::Other(Cow::from( + "Wrong input for blake2f precompile", + ))), output: vec![], withdrawals: vec![], estimated_ticks: 0, @@ -1181,9 +1184,9 @@ mod tests { assert!(result.is_ok()); let outcome = result.unwrap(); println!("{}", outcome.gas_used); - assert!(outcome.is_success); - assert!(outcome.gas_used == gas_limit); // all gas should be consumed - assert_eq!(Some(vec![]), outcome.result); + assert!(!outcome.is_success); + assert_eq!(gas_limit, outcome.gas_used); // all gas should be consumed + assert_eq!(None, outcome.result); } #[test] @@ -1201,9 +1204,9 @@ mod tests { assert!(result.is_ok()); let outcome = result.unwrap(); println!("{}", outcome.gas_used); - assert!(outcome.is_success); - assert!(outcome.gas_used == gas_limit); // all gas shoule be consumed - assert_eq!(Some(vec![]), outcome.result); + assert!(!outcome.is_success); + assert_eq!(gas_limit, outcome.gas_used); // all gas shoule be consumed + assert_eq!(None, outcome.result); } struct Blake2fTest { -- GitLab From be7249736394b6f260b2de7c6137ee888c0297a3 Mon Sep 17 00:00:00 2001 From: pikatos Date: Wed, 14 Feb 2024 14:10:28 +0100 Subject: [PATCH 8/8] EVM/Kernel: refactor blake2f to use call_precompile_with_gas_draining --- .../evm_execution/src/precompiles.rs | 84 ++++++++----------- 1 file changed, 33 insertions(+), 51 deletions(-) diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles.rs b/etherlink/kernel_evm/evm_execution/src/precompiles.rs index 8d6ba4822116..258d8b585ab8 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles.rs @@ -20,7 +20,10 @@ use crate::EthereumError; use crate::{abi, modexp::modexp_precompile}; use alloc::borrow::Cow; use alloc::collections::btree_map::BTreeMap; -use evm::{Context, ExitError, ExitReason, ExitRevert, ExitSucceed, Handler, Transfer}; +use evm::{ + executor::stack::PrecompileFailure, Context, ExitError, ExitReason, ExitRevert, + ExitSucceed, Handler, Transfer, +}; use host::runtime::Runtime; use libsecp256k1::{recover, Message, RecoveryId, Signature}; use primitive_types::{H160, U256}; @@ -359,32 +362,10 @@ fn ripemd160_precompile( }) } -fn blake2f_output_for_wrong_input( - handler: &mut EvmHandler, -) -> PrecompileOutcome { - if let Err(err) = handler.record_cost(handler.gas_left().as_u64()) { - log!( - handler.borrow_host(), - Info, - "Couldn't record the cost of blake2f {:?}", - err - ); - PrecompileOutcome { - exit_status: ExitReason::Error(err), - output: vec![], - withdrawals: vec![], - estimated_ticks: 0, - } - } else { - PrecompileOutcome { - exit_status: ExitReason::Error(ExitError::Other(Cow::from( - "Wrong input for blake2f precompile", - ))), - output: vec![], - withdrawals: vec![], - estimated_ticks: 0, - } - } +fn blake2f_output_for_wrong_input() -> EthereumError { + EthereumError::PrecompileFailed(PrecompileFailure::Error { + exit_status: ExitError::Other(Cow::from("Wrong input for blake2f precompile")), + }) } trait Decodable { @@ -401,18 +382,15 @@ impl Decodable for [u64; N] { } } -fn blake2f_precompile( +fn blake2f_precompile_without_gas_draining( handler: &mut EvmHandler, input: &[u8], - _context: &Context, - _is_static: bool, - _transfer: Option, ) -> Result { log!(handler.borrow_host(), Info, "Calling blake2f precompile"); // The precompile requires 6 inputs tightly encoded, taking exactly 213 bytes if input.len() != 213 { - return Ok(blake2f_output_for_wrong_input(handler)); + return Err(blake2f_output_for_wrong_input()); } // the number of rounds - 32-bit unsigned big-endian word @@ -462,7 +440,7 @@ fn blake2f_precompile( let f = match input[212] { 1 => true, 0 => false, - _ => return Ok(blake2f_output_for_wrong_input(handler)), + _ => return Err(blake2f_output_for_wrong_input()), }; eip152::compress(&mut h, m, t, f, rounds as usize); @@ -485,6 +463,20 @@ fn blake2f_precompile( }) } +fn blake2f_precompile( + handler: &mut EvmHandler, + input: &[u8], + _context: &Context, + _is_static: bool, + _transfer: Option, +) -> Result { + call_precompile_with_gas_draining( + handler, + input, + blake2f_precompile_without_gas_draining, + ) +} + /// Implementation of Etherelink specific withdrawals precompiled contract. fn withdrawal_precompile( handler: &mut EvmHandler, @@ -1172,21 +1164,16 @@ mod tests { #[test] fn test_blake2f_invalid_empty() { - // act let input = [0; 0]; - let gas_limit: u64 = 25000; + + // act let result = - execute_precompiled(H160::from_low_u64_be(9), &input, None, Some(gas_limit)); + execute_precompiled(H160::from_low_u64_be(9), &input, None, Some(25000)); // assert - // expected outcome is OK and empty output + // expected outcome is Err - assert!(result.is_ok()); - let outcome = result.unwrap(); - println!("{}", outcome.gas_used); - assert!(!outcome.is_success); - assert_eq!(gas_limit, outcome.gas_used); // all gas should be consumed - assert_eq!(None, outcome.result); + assert!(result.is_err()); } #[test] @@ -1197,16 +1184,11 @@ mod tests { 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ 0000000000000000000000000000000000000000000000000300000000000000000000000000000002" ).unwrap(); - let gas_limit: u64 = 25000; + let result = - execute_precompiled(H160::from_low_u64_be(9), &input, None, Some(gas_limit)); + execute_precompiled(H160::from_low_u64_be(9), &input, None, Some(25000)); - assert!(result.is_ok()); - let outcome = result.unwrap(); - println!("{}", outcome.gas_used); - assert!(!outcome.is_success); - assert_eq!(gas_limit, outcome.gas_used); // all gas shoule be consumed - assert_eq!(None, outcome.result); + assert!(result.is_err()); } struct Blake2fTest { -- GitLab