From 78518a513e9b299f9531bdeca80400f840685ce6 Mon Sep 17 00:00:00 2001 From: Christoph Burgdorf Date: Mon, 19 Apr 2021 16:21:52 +0200 Subject: [PATCH] Add revert_with_reason_string runtime function --- compiler/src/yul/runtime/functions/data.rs | 56 +++++++++---- compiler/tests/runtime.rs | 93 +++++++++++++--------- compiler/tests/utils.rs | 79 +++++++++++++----- 3 files changed, 155 insertions(+), 73 deletions(-) diff --git a/compiler/src/yul/runtime/functions/data.rs b/compiler/src/yul/runtime/functions/data.rs index 8b8fc49355..fabe5075b8 100644 --- a/compiler/src/yul/runtime/functions/data.rs +++ b/compiler/src/yul/runtime/functions/data.rs @@ -3,30 +3,31 @@ use yultsur::*; /// Return all data runtime functions pub fn all() -> Vec { vec![ - avail(), - alloc(), alloc_mstoren(), - free(), + alloc(), + avail(), + bytes_mcopys(), + bytes_scopym(), + bytes_scopys(), + bytes_sloadn(), + bytes_sstoren(), ccopym(), + ceil32(), + cloadn(), + free(), load_data_string(), + map_value_ptr(), + mcopym(), mcopys(), + mloadn(), + mstoren(), + revert_with_reason_string(), scopym(), - mcopym(), scopys(), - mloadn(), + set_zero(), sloadn(), - bytes_sloadn(), - cloadn(), - mstoren(), sstoren(), - bytes_sstoren(), - bytes_mcopys(), - bytes_scopym(), - bytes_scopys(), - map_value_ptr(), - ceil32(), ternary(), - set_zero(), ] } @@ -380,3 +381,28 @@ pub fn load_data_string() -> yul::Statement { } } } + +/// Revert with encoded reason string +pub fn revert_with_reason_string() -> yul::Statement { + function_definition! { + function revert_with_reason_string(reason) { + // Function selector for Error(string) + (let ptr := alloc_mstoren(0x08C379A0, 4)) + + // Write the (fixed) data offset into the next 32 bytes of memory + (pop((alloc_mstoren(0x0000000000000000000000000000000000000000000000000000000000000020, 32)))) + + // Read the size of the string + (let reason_size := mloadn(reason, 32)) + + //Copy the whole reason string (length + data) to the current segment of memory + (pop((mcopym(reason , (add(reason_size, 32)))))) + + // Right pad the reason bytes to a multiple of 32 bytes + (let padding := sub((ceil32(reason_size)), reason_size)) + (pop((alloc(padding)))) + + (revert(ptr, (add(68, (add(reason_size, padding)))))) + } + } +} diff --git a/compiler/tests/runtime.rs b/compiler/tests/runtime.rs index 7dc2d3db8e..cc1cbcf417 100644 --- a/compiler/tests/runtime.rs +++ b/compiler/tests/runtime.rs @@ -2,6 +2,7 @@ #![cfg(feature = "solc-backend")] use fe_compiler::yul::runtime::functions; +use rstest::rstest; use yultsur::*; mod utils; @@ -11,6 +12,7 @@ use fe_analyzer::namespace::types::{ Integer, Struct, }; +use fe_common::utils::keccak; use utils::*; macro_rules! assert_eq { @@ -23,13 +25,37 @@ macro_rules! assert_eq { }; } +#[rstest( + reason, + case("foo"), + case("A very looooooooooooooong reason that consumes multiple words") +)] +fn test_revert_with_reason_string(reason: &str) { + let reason_id = format!(r#""{}""#, keccak::full(reason.as_bytes())); + + with_executor(&|mut executor| { + test_runtime_functions_revert( + &mut executor, + Runtime::default() + .with_data( + vec![yul::Data { name: keccak::full(reason.as_bytes()), value: reason.to_owned() }] + ) + .with_test_statements( + statements! { + (let reason := load_data_string((dataoffset([literal_expression! { (reason_id) }])), (datasize([literal_expression! { (reason_id) }])))) + (revert_with_reason_string(reason)) + }), + &encode_error_reason(reason) + ); + }) +} + #[test] fn test_runtime_alloc_and_avail() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), - statements! { + Runtime::default().with_test_statements(statements! { (let a := avail()) (let b := alloc(5)) (let c := alloc(10)) @@ -38,7 +64,7 @@ fn test_runtime_alloc_and_avail() { [assert_eq!(b, a)] [assert_eq!(c, (add(b, 5)))] [assert_eq!(d, (add(c, 10)))] - }, + }), ); }) } @@ -48,8 +74,7 @@ fn test_runtime_mcopys() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), - statements! { + Runtime::default().with_test_statements(statements! { (let a := 0x0111114211111111011111112342311101111112221151110111111111111111) (let b := 0x0111111234111111011123411111111101112431111111110111111234411111) (let c := 0x0111341111111111011111111123411101111123411111110111111234111111) @@ -75,7 +100,7 @@ fn test_runtime_mcopys() { [assert_eq!(b, (sload(47)))] [assert_eq!(c, (sload(48)))] [assert_eq!(d, (sload(49)))] - }, + }), ); }) } @@ -85,8 +110,7 @@ fn test_runtime_scopym() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), - statements! { + Runtime::default().with_test_statements(statements! { (let a := 0x0111114211111111011111112342311101111112221151110111111111111111) (let b := 0x0111111234111111011123411111111101112431111111110111111234411111) (let c := 0x0111341111111111011111111123411101111123411111110111111234111111) @@ -118,7 +142,7 @@ fn test_runtime_scopym() { [assert_eq!(b, (mload(ptr6)))] [assert_eq!(c, (mload(ptr7)))] [assert_eq!(d, (mload(ptr8)))] - }, + }), ); }) } @@ -128,8 +152,7 @@ fn test_runtime_mloadn() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), - statements! { + Runtime::default().with_test_statements(statements! { (let a := 0x4200000000000000000000000000000000000000000000000000000000420026) (mstore(100, a)) @@ -137,7 +160,7 @@ fn test_runtime_mloadn() { [assert_eq!(0x420026, (mloadn(129, 3)))] [assert_eq!(0x26, (mloadn(130, 2)))] [assert_eq!(0x26, (mloadn(131, 1)))] - }, + }), ); }) } @@ -147,8 +170,7 @@ fn test_runtime_storage_sanity() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - vec![], - statements! { + Runtime::new().with_test_statements(statements! { (let a := 0x4200000000000000000000000000000000000000000000000000000000000026) (let b := 0x9900000000000000000000000000000000000000000000000000000000000077) (sstore(0, a)) @@ -156,7 +178,7 @@ fn test_runtime_storage_sanity() { [assert_eq!(a, (sload(0)))] [assert_eq!(b, (sload(1)))] - }, + }), ); }) } @@ -166,8 +188,7 @@ fn test_runtime_sloadn() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), - statements! { + Runtime::default().with_test_statements(statements! { (let a := 0x4200000000000000000000000000000000000000000000000000000000000026) (let b := 0x9900530000003900000000000000000000000000000000000000000000000077) (sstore(1000, a)) @@ -184,7 +205,7 @@ fn test_runtime_sloadn() { [assert_eq!(0x77, (sloadn(1001, 31, 1)))] [assert_eq!(0x990053, (sloadn(1001, 0, 3)))] [assert_eq!(0x5300000039, (sloadn(1001, 2, 5)))] - }, + }), ); }) } @@ -194,8 +215,7 @@ fn test_runtime_sstoren() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), - statements! { + Runtime::default().with_test_statements(statements! { (let a := 0x0111111111111111011111111111111101111111111111110111111111111111) // dashes indicate which bytes are to be replaced in this test // 0----2 8----10 15----------------23 31--32 @@ -213,7 +233,7 @@ fn test_runtime_sstoren() { (sstoren(1000, 0, 32, c)) [assert_eq!(c, (sload(1000)))] - }, + }), ); }) } @@ -223,11 +243,10 @@ fn test_runtime_ceil32() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), - statements! { + Runtime::default().with_test_statements(statements! { [assert_eq!(32, (ceil32(29)))] [assert_eq!(256, (ceil32(225)))] - }, + }), ); }) } @@ -237,14 +256,13 @@ fn test_runtime_ternary() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), - statements! { + Runtime::default().with_test_statements(statements! { (let a := ternary(0, 42, 26)) (let b := ternary(1, 42, 26)) [assert_eq!(a, 26)] [assert_eq!(b, 42)] - }, + }), ); }) } @@ -254,8 +272,7 @@ fn test_runtime_abi_unpack() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), - statements! { + Runtime::default().with_test_statements(statements! { (let a := 0x0042002600530000000000000000000000000000000000000000000000000000) (let packed := alloc_mstoren(a, 32)) (let unpacked := avail()) @@ -268,7 +285,7 @@ fn test_runtime_abi_unpack() { [assert_eq!(elem0, 0x0042)] [assert_eq!(elem1, 0x0026)] [assert_eq!(elem2, 0x0053)] - }, + }), ); }) } @@ -278,7 +295,7 @@ fn test_keccak256() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), + Runtime::default().with_test_statements( statements! { (let num := 63806209331542711802848847270949280092855778197726125910674179583545433573378) (let result :=109966633016701122630199943745061001312678661825260870342362413625737614346915) @@ -288,6 +305,7 @@ fn test_keccak256() { (mstore(0, num)) [assert_eq!(result, (keccak256(0, 32)))] }, + ) ); }) } @@ -297,8 +315,7 @@ fn test_runtime_set_zero() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), - statements! { + Runtime::default().with_test_statements(statements! { (let a := 0x1111111111111111111111111111111111111111111111111111111111111111) (let b := 0x1111110000000000000000000000000000000000000000000000001111111111) (let c := 0x1111100000000000000000000000000000000000000000000000000000000000) @@ -307,7 +324,7 @@ fn test_runtime_set_zero() { [assert_eq!(0x11, (set_zero(0, 248, a)))] [assert_eq!(b, (set_zero(24, 216, a)))] [assert_eq!(c, (set_zero(20, 256, a)))] - }, + }), ); }) } @@ -330,7 +347,7 @@ fn test_runtime_house_struct() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - [functions::std(), house_api.clone()].concat(), + Runtime::new().with_functions([functions::std(), house_api.clone()].concat()).with_test_statements( statements! { (let price := 42) (let size := 26) @@ -369,6 +386,7 @@ fn test_runtime_house_struct() { [assert_eq!(rooms, (bytes_sloadn((struct_House_get_rooms_ptr(house_storage)), 1)))] [assert_eq!(vacant, (bytes_sloadn((struct_House_get_vacant_ptr(house_storage)), 1)))] }, + ) ); }) } @@ -378,10 +396,9 @@ fn checked_exp_signed() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), - statements! { + Runtime::default().with_test_statements(statements! { [assert_eq!(4, (checked_exp_signed(2, 2, 0, 100)))] - }, + }), ); }) } diff --git a/compiler/tests/utils.rs b/compiler/tests/utils.rs index d573f51474..7c8f7b451e 100644 --- a/compiler/tests/utils.rs +++ b/compiler/tests/utils.rs @@ -4,6 +4,7 @@ use evm_runtime::{ Handler, }; use fe_compiler as compiler; +use fe_compiler::yul::runtime::functions; use primitive_types::{ H160, H256, @@ -326,12 +327,8 @@ pub fn load_contract(address: H160, fixture: &str, contract_name: &str) -> Contr } #[allow(dead_code)] -pub fn test_runtime_functions( - executor: &mut Executor, - functions: Vec, - test_statements: Vec, -) { - let (reason, _) = execute_runtime_functions(executor, functions, test_statements); +pub fn test_runtime_functions(executor: &mut Executor, runtime: Runtime) { + let (reason, _) = execute_runtime_functions(executor, runtime); if !matches!(reason, ExitReason::Succeed(_)) { panic!("Runtime function test failed: {:?}", reason) } @@ -340,11 +337,10 @@ pub fn test_runtime_functions( #[allow(dead_code)] pub fn test_runtime_functions_revert( executor: &mut Executor, - functions: Vec, - test_statements: Vec, + runtime: Runtime, expected_output: &[u8], ) { - let (reason, output) = execute_runtime_functions(executor, functions, test_statements); + let (reason, output) = execute_runtime_functions(executor, runtime); if output != expected_output { panic!("Runtime function test failed (wrong output): {:?}", output) } @@ -354,20 +350,63 @@ pub fn test_runtime_functions_revert( } } -fn execute_runtime_functions( - executor: &mut Executor, +pub struct Runtime { functions: Vec, test_statements: Vec, -) -> (ExitReason, Vec) { - let all_statements = [functions, test_statements].concat(); - let yul_code = yul::Object { - name: identifier! { Contract }, - code: code! { [all_statements...] }, - objects: vec![], - data: vec![], + data: Vec, +} + +#[allow(dead_code)] +impl Runtime { + /// Create a `Runtime` instance with all `std` functions. + pub fn default() -> Runtime { + Runtime::new().with_functions(functions::std()) + } + + /// Create a new `Runtime` instance. + pub fn new() -> Runtime { + Runtime { + functions: vec![], + test_statements: vec![], + data: vec![], + } } - .to_string() - .replace("\"", "\\\""); + + /// Add the given set of functions + pub fn with_functions(self, fns: Vec) -> Runtime { + Runtime { + functions: fns, + ..self + } + } + + /// Add the given set of test statements + pub fn with_test_statements(self, statements: Vec) -> Runtime { + Runtime { + test_statements: statements, + ..self + } + } + + // Add the given set of data + pub fn with_data(self, data: Vec) -> Runtime { + Runtime { data: data, ..self } + } + + /// Generate the top level YUL object + pub fn to_yul(&self) -> yul::Object { + let all_statements = [self.functions.clone(), self.test_statements.clone()].concat(); + yul::Object { + name: identifier! { Contract }, + code: code! { [all_statements...] }, + objects: vec![], + data: self.data.clone(), + } + } +} + +fn execute_runtime_functions(executor: &mut Executor, runtime: Runtime) -> (ExitReason, Vec) { + let yul_code = runtime.to_yul().to_string().replace("\"", "\\\""); let bytecode = compiler::evm::compile_single_contract("Contract", yul_code, false) .expect("failed to compile Yul"); let bytecode = hex::decode(&bytecode).expect("failed to decode bytecode");