diff --git a/Cargo.lock b/Cargo.lock index 7a96c87e..7cc2c90a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "arrayvec" @@ -938,7 +938,7 @@ dependencies = [ [[package]] name = "massa-proto-rs" version = "0.1.0" -source = "git+https://github.com/massalabs/massa-proto-rs.git?rev=84678fb77cf6d06d85d55e2c20502908ff292e61#84678fb77cf6d06d85d55e2c20502908ff292e61" +source = "git+https://github.com/massalabs/massa-proto-rs.git?rev=25638b3b7d387afbca81afcb02bae1af03697f18#25638b3b7d387afbca81afcb02bae1af03697f18" dependencies = [ "glob", "prost", @@ -967,6 +967,7 @@ dependencies = [ "prost-types", "rand", "regex", + "rust_decimal", "serde", "serde_json", "serial_test", @@ -1448,6 +1449,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rust_decimal" +version = "1.34.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39449a79f45e8da28c57c341891b69a183044b29518bb8f86dbac9df60bb7df" +dependencies = [ + "arrayvec", + "num-traits", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1677,18 +1688,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "6e3de26b0965292219b4287ff031fcba86837900fe9cd2b34ea8ad893c0953d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5033ec8a..b9b4ddb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ chrono = { version = "=0.4", features = ["clock"], default-features = false } displaydoc = "0.2" function_name = "0.3" loupe = "0.1" -massa-proto-rs = { git = "https://github.com/massalabs/massa-proto-rs.git", rev = "84678fb77cf6d06d85d55e2c20502908ff292e61"} +massa-proto-rs = { git = "https://github.com/massalabs/massa-proto-rs.git", rev = "25638b3b7d387afbca81afcb02bae1af03697f18"} more-asserts = "0.3" num_enum = "0.7" parking_lot = "0.12" @@ -20,7 +20,7 @@ prost-types = "=0.12" rand = "=0.8" # for gas_calibration middleware regex = "1" -serde = "=1.0" +serde = { version = "=1.0", features = ["derive"] } serde_json = "=1.0" serial_test = "2" sha2 = "=0.10.8" @@ -32,6 +32,7 @@ wasmer-compiler-cranelift = "=4.2.4" wasmer-compiler-singlepass = "=4.2.4" wasmer-middlewares = "=4.2.4" wasmer-types = "=4.2.4" +rust_decimal = { version = "1.32", default-features = false, optional = true } [dev-dependencies] bs58 = { version = "=0.5.0", features = ["check"] } @@ -46,3 +47,4 @@ gas_calibration = [] testing = [] dumper = [] build-wasm = [] +execution-trace = ["rust_decimal"] \ No newline at end of file diff --git a/src/as_execution/abi.rs b/src/as_execution/abi.rs index ca95e446..d38a58e4 100644 --- a/src/as_execution/abi.rs +++ b/src/as_execution/abi.rs @@ -12,6 +12,11 @@ use wasmer::{AsStoreMut, AsStoreRef, FunctionEnvMut, Memory}; use super::env::{get_remaining_points, sub_remaining_gas_abi, ASEnv}; use crate::settings; +#[cfg(feature = "execution-trace")] +use crate::{ + into_trace_value, + types::{AbiTrace, AbiTraceType}, +}; use super::common::{call_module, create_sc, function_exists, local_call}; use super::error::{abi_bail, ABIResult}; @@ -44,7 +49,15 @@ pub(crate) fn get_env(ctx: &FunctionEnvMut) -> ABIResult { pub(crate) fn assembly_script_get_call_coins(mut ctx: FunctionEnvMut) -> ABIResult { let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; - Ok(env.get_interface().get_call_coins()? as i64) + let res = env.get_interface().get_call_coins()? as i64; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: res.into(), + sub_calls: None, + }); + Ok(res) } /// Transfer an amount from the address on the current call stack to a target @@ -61,15 +74,36 @@ pub(crate) fn assembly_script_transfer_coins( abi_bail!("Negative raw amount."); } let memory = get_memory!(env); - let to_address = &read_string(memory, &ctx, to_address)?; + let to_address = read_string(memory, &ctx, to_address)?; // Do not remove this. It could be used for gas_calibration in future. // if cfg!(feature = "gas_calibration") { // let fname = format!("massa.{}:0", function_name!()); // param_size_update(&env, &mut ctx, &fname, to_address.len(), true); // } - Ok(env - .get_interface() - .transfer_coins(to_address, raw_amount as u64)?) + env.get_interface() + .transfer_coins(&to_address, raw_amount as u64)?; + #[cfg(feature = "execution-trace")] + { + let call_stack = env.get_interface().get_call_stack(); + // TODO: check if this is always correct (with nested of nested call?) + let from_address = call_stack + .unwrap_or_default() + .last() + .cloned() + .unwrap_or_else(|| "".to_string()); + + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + into_trace_value!(from_address), + into_trace_value!(to_address), + (stringify!(raw_amount), raw_amount as u64).into(), + ], + return_value: AbiTraceType::None, + sub_calls: None, + }); + } + Ok(()) } /// Transfer an amount from the specified address to a target address. @@ -86,8 +120,8 @@ pub(crate) fn assembly_script_transfer_coins_for( abi_bail!("Negative raw amount."); } let memory = get_memory!(env); - let from_address = &read_string(memory, &ctx, from_address)?; - let to_address = &read_string(memory, &ctx, to_address)?; + let from_address = read_string(memory, &ctx, from_address)?; + let to_address = read_string(memory, &ctx, to_address)?; // Do not remove this. It could be used for gas_calibration in future. // if cfg!(feature = "gas_calibration") { // let fname = format!("massa.{}:0", function_name!()); @@ -95,16 +129,35 @@ pub(crate) fn assembly_script_transfer_coins_for( // let fname = format!("massa.{}:1", function_name!()); // param_size_update(&env, &mut ctx, &fname, to_address.len(), true); // } - Ok(env - .get_interface() - .transfer_coins_for(from_address, to_address, raw_amount as u64)?) + env.get_interface() + .transfer_coins_for(&from_address, &to_address, raw_amount as u64)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + into_trace_value!(from_address), + into_trace_value!(to_address), + (stringify!(raw_amount), raw_amount as u64).into(), + ], + return_value: AbiTraceType::None, + sub_calls: None, + }); + Ok(()) } #[named] pub(crate) fn assembly_script_get_balance(mut ctx: FunctionEnvMut) -> ABIResult { let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; - Ok(env.get_interface().get_balance()? as i64) + let res = env.get_interface().get_balance()? as i64; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: res.into(), + sub_calls: None, + }); + Ok(res) } #[named] @@ -115,13 +168,21 @@ pub(crate) fn assembly_script_get_balance_for( let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; let memory = get_memory!(env); - let address = &read_string(memory, &ctx, address)?; + let address = read_string(memory, &ctx, address)?; // Do not remove this. It could be used for gas_calibration in future. // if cfg!(feature = "gas_calibration") { // let fname = format!("massa.{}:0", function_name!()); // param_size_update(&env, &mut ctx, &fname, address.len(), true); // } - Ok(env.get_interface().get_balance_for(address)? as i64) + let res = env.get_interface().get_balance_for(&address)? as i64; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(address)], + return_value: res.into(), + sub_calls: None, + }); + Ok(res) } /// Raw call that have the right type signature to be able to be call a module @@ -137,9 +198,9 @@ pub(crate) fn assembly_script_call( let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; let memory = get_memory!(env); - let address = &read_string(memory, &ctx, address)?; - let function = &read_string(memory, &ctx, function)?; - let param = &read_buffer(memory, &ctx, param)?; + let address = read_string(memory, &ctx, address)?; + let function = read_string(memory, &ctx, function)?; + let param = read_buffer(memory, &ctx, param)?; // Do not remove this. It could be used for gas_calibration in future. // if cfg!(feature = "gas_calibration") { @@ -151,7 +212,19 @@ pub(crate) fn assembly_script_call( // param_size_update(&env, &mut ctx, &fname, param.len(), true); // } - let response = call_module(&mut ctx, address, function, param, call_coins)?; + let response = call_module(&mut ctx, &address, &function, ¶m, call_coins)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + ("address", address.clone()).into(), + ("function", function.clone()).into(), + into_trace_value!(param), + into_trace_value!(call_coins), + ], + return_value: response.ret.clone().into(), + sub_calls: Some(response.trace), + }); match BufferPtr::alloc(&response.ret, env.get_ffi_env(), &mut ctx) { Ok(ret) => Ok(ret.offset() as i32), _ => abi_bail!(format!( @@ -165,7 +238,15 @@ pub(crate) fn assembly_script_call( pub(crate) fn assembly_script_get_remaining_gas(mut ctx: FunctionEnvMut) -> ABIResult { let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; - Ok(get_remaining_points(&env, &mut ctx)? as i64) + let res = get_remaining_points(&env, &mut ctx)? as i64; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: res.into(), + sub_calls: None, + }); + Ok(res) } /// Create an instance of VM from a module with a @@ -186,6 +267,13 @@ pub(crate) fn assembly_script_print(mut ctx: FunctionEnvMut, arg: i32) -> // } env.get_interface().print(&message)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(message)], + return_value: AbiTraceType::None, + sub_calls: None, + }); Ok(()) } @@ -200,6 +288,14 @@ pub(crate) fn assembly_script_get_op_keys(mut ctx: FunctionEnvMut) -> ABI let fmt_keys = ser_bytearray_vec(&keys, keys.len(), settings::max_op_datastore_entry_count())?; let ptr = pointer_from_bytearray(&env, &mut ctx, &fmt_keys)?.offset(); + + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: fmt_keys.into(), + sub_calls: None, + }); Ok(ptr as i32) } } @@ -226,6 +322,14 @@ pub(crate) fn assembly_script_get_op_keys_prefix( let fmt_keys = ser_bytearray_vec(&keys, keys.len(), settings::max_op_datastore_entry_count())?; let ptr = pointer_from_bytearray(&env, &mut ctx, &fmt_keys)?.offset(); + + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(prefix)], + return_value: AbiTraceType::ByteArray(fmt_keys), + sub_calls: None, + }); Ok(ptr as i32) } } @@ -255,6 +359,15 @@ pub(crate) fn assembly_script_has_op_key( // 'true' is explicitly defined as: 0x01 while 'false' is: 0x00 let b_vec: Vec = vec![b as u8]; let a = pointer_from_bytearray(&env, &mut ctx, &b_vec)?.offset(); + + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(key_bytes)], + return_value: b_vec.into(), + sub_calls: None, + }); + Ok(a as i32) } } @@ -277,6 +390,14 @@ pub(crate) fn assembly_script_get_op_data( // } let data = env.get_interface().get_op_data(&key_bytes)?; let ptr = pointer_from_bytearray(&env, &mut ctx, &data)?.offset() as i32; + + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(key_bytes)], + return_value: data.into(), + sub_calls: None, + }); Ok(ptr) } @@ -297,7 +418,16 @@ pub(crate) fn assembly_script_create_sc( // param_size_update(&env, &mut ctx, &fname, bytecode.len(), true); // } let address = create_sc(&mut ctx, &bytecode)?; - Ok(StringPtr::alloc(&address, env.get_ffi_env(), &mut ctx)?.offset() as i32) + let ptr = StringPtr::alloc(&address, env.get_ffi_env(), &mut ctx)?.offset() as i32; + + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(bytecode)], + return_value: address.clone().into(), + sub_calls: None, + }); + Ok(ptr) } /// performs a hash on a bytearray and returns the hash @@ -314,6 +444,13 @@ pub(crate) fn assembly_script_hash(mut ctx: FunctionEnvMut, value: i32) - // } let hash = env.get_interface().hash(&bytes)?.to_vec(); let ptr = pointer_from_bytearray(&env, &mut ctx, &hash)?.offset(); + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(bytes)], + return_value: hash.into(), + sub_calls: None, + }); Ok(ptr as i32) } @@ -329,6 +466,15 @@ pub(crate) fn assembly_script_keccak256_hash( let bytes = read_buffer(memory, &ctx, value)?; let hash = env.get_interface().hash_keccak256(&bytes)?.to_vec(); let ptr = pointer_from_bytearray(&env, &mut ctx, &hash)?.offset(); + + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(bytes)], + return_value: hash.into(), + sub_calls: None, + }); + Ok(ptr as i32) } @@ -350,6 +496,14 @@ pub(crate) fn assembly_script_get_keys( let keys = env.get_interface().get_keys(prefix_opt)?; let fmt_keys = ser_bytearray_vec(&keys, keys.len(), settings::max_datastore_entry_count())?; let ptr = pointer_from_bytearray(&env, &mut ctx, &fmt_keys)?.offset(); + + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(prefix)], + return_value: fmt_keys.into(), + sub_calls: None, + }); Ok(ptr as i32) } @@ -373,6 +527,14 @@ pub(crate) fn assembly_script_get_keys_for( let keys = env.get_interface().get_keys_for(&address, prefix_opt)?; let fmt_keys = ser_bytearray_vec(&keys, keys.len(), settings::max_datastore_entry_count())?; let ptr = pointer_from_bytearray(&env, &mut ctx, &fmt_keys)?.offset(); + + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(address), into_trace_value!(prefix)], + return_value: AbiTraceType::ByteArrays(keys.iter().cloned().collect()), + sub_calls: None, + }); Ok(ptr as i32) } @@ -409,6 +571,13 @@ pub(crate) fn assembly_script_set_data( // } env.get_interface().raw_set_data(&key, &value)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(key), into_trace_value!(value)], + return_value: AbiTraceType::None, + sub_calls: None, + }); Ok(()) } @@ -433,6 +602,13 @@ pub(crate) fn assembly_script_append_data( // param_size_update(&env, &mut ctx, &fname, value.len(), true); // } env.get_interface().raw_append_data(&key, &value)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(key), into_trace_value!(value)], + return_value: AbiTraceType::None, + sub_calls: None, + }); Ok(()) } @@ -449,7 +625,15 @@ pub(crate) fn assembly_script_get_data(mut ctx: FunctionEnvMut, key: i32) // param_size_update(&env, &mut ctx, &fname, key.len(), true); // } let data = env.get_interface().raw_get_data(&key)?; - Ok(pointer_from_bytearray(&env, &mut ctx, &data)?.offset() as i32) + let ptr = pointer_from_bytearray(&env, &mut ctx, &data)?.offset() as i32; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(key)], + return_value: data.clone().into(), + sub_calls: None, + }); + Ok(ptr) } /// checks if a key-indexed data entry exists in the datastore @@ -464,7 +648,15 @@ pub(crate) fn assembly_script_has_data(mut ctx: FunctionEnvMut, key: i32) // let fname = format!("massa.{}:0", function_name!()); // param_size_update(&env, &mut ctx, &fname, key.len(), true); // } - Ok(env.get_interface().has_data(&key)? as i32) + let res = env.get_interface().has_data(&key)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(key)], + return_value: res.into(), + sub_calls: None, + }); + Ok(res as i32) } /// deletes a key-indexed data entry in the datastore of the current address, @@ -484,6 +676,13 @@ pub(crate) fn assembly_script_delete_data( // param_size_update(&env, &mut ctx, &fname, key.len(), true); // } env.get_interface().raw_delete_data(&key)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(key)], + return_value: AbiTraceType::None, + sub_calls: None, + }); Ok(()) } @@ -513,6 +712,17 @@ pub(crate) fn assembly_script_set_data_for( // } env.get_interface() .raw_set_data_for(&address, &key, &value)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + into_trace_value!(address), + into_trace_value!(key), + into_trace_value!(value), + ], + return_value: AbiTraceType::None, + sub_calls: None, + }); Ok(()) } @@ -542,6 +752,17 @@ pub(crate) fn assembly_script_append_data_for( // } env.get_interface() .raw_append_data_for(&address, &key, &value)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + into_trace_value!(address), + into_trace_value!(key), + into_trace_value!(value), + ], + return_value: AbiTraceType::None, + sub_calls: None, + }); Ok(()) } @@ -567,7 +788,15 @@ pub(crate) fn assembly_script_get_data_for( // } let data = env.get_interface().raw_get_data_for(&address, &key)?; - Ok(pointer_from_bytearray(&env, &mut ctx, &data)?.offset() as i32) + let ptr = pointer_from_bytearray(&env, &mut ctx, &data)?.offset() as i32; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(address), into_trace_value!(key)], + return_value: data.into(), + sub_calls: None, + }); + Ok(ptr) } /// Deletes a datastore entry for an address. Fails if the entry or address does @@ -591,6 +820,13 @@ pub(crate) fn assembly_script_delete_data_for( // param_size_update(&env, &mut ctx, &fname, key.len(), true); // } env.get_interface().raw_delete_data_for(&address, &key)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(address), into_trace_value!(key)], + return_value: AbiTraceType::None, + sub_calls: None, + }); Ok(()) } @@ -612,7 +848,15 @@ pub(crate) fn assembly_script_has_data_for( // let fname = format!("massa.{}:1", function_name!()); // param_size_update(&env, &mut ctx, &fname, key.len(), true); // } - Ok(env.get_interface().has_data_for(&address, &key)? as i32) + let res = env.get_interface().has_data_for(&address, &key)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(address), into_trace_value!(key)], + return_value: res.into(), + sub_calls: None, + }); + Ok(res as i32) } #[named] @@ -622,7 +866,17 @@ pub(crate) fn assembly_script_get_owned_addresses( let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; let data = env.get_interface().get_owned_addresses()?; - alloc_string_array(&mut ctx, &data) + // prevent data.clone() when enabling execution-trace + #[allow(clippy::let_and_return)] + let ptr = alloc_string_array(&mut ctx, &data); + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: data.into(), + sub_calls: None, + }); + ptr } #[named] @@ -630,7 +884,17 @@ pub(crate) fn assembly_script_get_call_stack(mut ctx: FunctionEnvMut) -> let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; let data = env.get_interface().get_call_stack()?; - alloc_string_array(&mut ctx, &data) + // prevent data.clone() when enabling execution-trace + #[allow(clippy::let_and_return)] + let ptr = alloc_string_array(&mut ctx, &data); + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: data.into(), + sub_calls: None, + }); + ptr } #[named] @@ -647,7 +911,14 @@ pub(crate) fn assembly_script_generate_event( // let fname = format!("massa.{}:0", function_name!()); // param_size_update(&env, &mut ctx, &fname, event.len(), true); // } - env.get_interface().generate_event(event)?; + env.get_interface().generate_event(event.clone())?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(event)], + return_value: AbiTraceType::None, + sub_calls: None, + }); Ok(()) } @@ -675,9 +946,21 @@ pub(crate) fn assembly_script_signature_verify( // let fname = format!("massa.{}:2", function_name!()); // param_size_update(&env, &mut ctx, &fname, public_key.len(), true); // } - Ok(env + let res = env .get_interface() - .signature_verify(data.as_bytes(), &signature, &public_key)? as i32) + .signature_verify(data.as_bytes(), &signature, &public_key)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + into_trace_value!(data.as_bytes().to_vec()), + into_trace_value!(signature), + into_trace_value!(public_key), + ], + return_value: res.into(), + sub_calls: None, + }); + Ok(res as i32) } /// Verify an EVM signature. @@ -695,9 +978,21 @@ pub(crate) fn assembly_script_evm_signature_verify( let data = read_buffer(memory, &ctx, data)?; let signature = read_buffer(memory, &ctx, signature)?; let public_key = read_buffer(memory, &ctx, public_key)?; - Ok(env + let res = env .get_interface() - .evm_signature_verify(&data, &signature, &public_key)? as i32) + .evm_signature_verify(&data, &signature, &public_key)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + into_trace_value!(data), + into_trace_value!(signature), + into_trace_value!(public_key), + ], + return_value: res.into(), + sub_calls: None, + }); + Ok(res as i32) } /// Get address from public key (EVM) @@ -714,6 +1009,13 @@ pub(crate) fn assembly_script_evm_get_address_from_pubkey( .get_interface() .evm_get_address_from_pubkey(&public_key)?; let ptr = pointer_from_bytearray(&env, &mut ctx, &address)?.offset(); + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(public_key)], + return_value: address.into(), + sub_calls: None, + }); Ok(ptr as i32) } @@ -733,6 +1035,13 @@ pub(crate) fn assembly_script_evm_get_pubkey_from_signature( .get_interface() .evm_get_pubkey_from_signature(&data, &signature)?; let ptr = pointer_from_bytearray(&env, &mut ctx, &public_key)?.offset(); + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(data), into_trace_value!(signature)], + return_value: public_key.into(), + sub_calls: None, + }); Ok(ptr as i32) } @@ -746,7 +1055,15 @@ pub(crate) fn assembly_script_is_address_eoa( sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; let memory = get_memory!(env); let address = read_string(memory, &ctx, address)?; - Ok(env.get_interface().is_address_eoa(&address)? as i32) + let res = env.get_interface().is_address_eoa(&address)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(address)], + return_value: res.into(), + sub_calls: None, + }); + Ok(res as i32) } /// converts a public key to an address @@ -765,7 +1082,15 @@ pub(crate) fn assembly_script_address_from_public_key( // param_size_update(&env, &mut ctx, &fname, public_key.len(), true); // } let addr = env.get_interface().address_from_public_key(&public_key)?; - Ok(pointer_from_string(&env, &mut ctx, &addr)?.offset() as i32) + let ptr = pointer_from_string(&env, &mut ctx, &addr)?.offset() as i32; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(public_key)], + return_value: addr.into(), + sub_calls: None, + }); + Ok(ptr) } /// Validates an address is correct @@ -778,7 +1103,15 @@ pub(crate) fn assembly_script_validate_address( sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; let memory = get_memory!(env); let address = read_string(memory, &ctx, address)?; - Ok(env.get_interface().validate_address(&address)? as i32) + let res = env.get_interface().validate_address(&address)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(address)], + return_value: res.into(), + sub_calls: None, + }); + Ok(res as i32) } /// generates an unsafe random number @@ -786,7 +1119,15 @@ pub(crate) fn assembly_script_validate_address( pub(crate) fn assembly_script_unsafe_random(mut ctx: FunctionEnvMut) -> ABIResult { let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; - Ok(env.get_interface().unsafe_random()?) + let res = env.get_interface().unsafe_random()?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: res.into(), + sub_calls: None, + }); + Ok(res) } /// gets the current unix timestamp in milliseconds @@ -794,7 +1135,15 @@ pub(crate) fn assembly_script_unsafe_random(mut ctx: FunctionEnvMut) -> A pub(crate) fn assembly_script_get_time(mut ctx: FunctionEnvMut) -> ABIResult { let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; - Ok(env.get_interface().get_time()? as i64) + let res = env.get_interface().get_time()?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: res.into(), + sub_calls: None, + }); + Ok(res as i64) } /// sends an async message @@ -843,9 +1192,9 @@ pub(crate) fn assembly_script_send_message( abi_bail!("negative coins") } let memory = get_memory!(env); - let target_address = &read_string(memory, &ctx, target_address)?; - let target_handler = &read_string(memory, &ctx, target_handler)?; - let data = &read_buffer(memory, &ctx, data)?; + let target_address = read_string(memory, &ctx, target_address)?; + let target_handler = read_string(memory, &ctx, target_handler)?; + let data = read_buffer(memory, &ctx, data)?; // Do not remove this. It could be used for gas_calibration in future. // if cfg!(feature = "gas_calibration") { // let fname = format!("massa.{}:0", function_name!()); @@ -855,7 +1204,7 @@ pub(crate) fn assembly_script_send_message( // true); let fname = format!("massa.{}:2", function_name!()); // param_size_update(&env, &mut ctx, &fname, data.len(), true); // } - let filter_address_string = &read_string(memory, &ctx, filter_address)?; + let filter_address_string = read_string(memory, &ctx, filter_address)?; let key = read_buffer(memory, &ctx, filter_datastore_key)?; let filter = match (filter_address_string.as_str(), key.as_slice()) { ("", _) => None, @@ -864,16 +1213,38 @@ pub(crate) fn assembly_script_send_message( }; env.get_interface().send_message( - target_address, - target_handler, + &target_address, + &target_handler, validity_start, validity_end, max_gas as u64, raw_fee as u64, raw_coins as u64, - data, + &data, filter, )?; + + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + into_trace_value!(target_address), + into_trace_value!(target_handler), + into_trace_value!(validity_start_period), + into_trace_value!(validity_start_thread), + into_trace_value!(validity_end_period), + into_trace_value!(validity_end_thread), + into_trace_value!(max_gas as u64), + into_trace_value!(raw_fee as u64), + into_trace_value!(raw_coins as u64), + into_trace_value!(data), + into_trace_value!(filter_address_string), + into_trace_value!(key), + ], + return_value: AbiTraceType::None, + sub_calls: None, + }); + Ok(()) } @@ -888,7 +1259,15 @@ pub(crate) fn assembly_script_get_origin_operation_id( .get_interface() .get_origin_operation_id()? .unwrap_or_default(); - Ok(pointer_from_string(&env, &mut ctx, &operation_id)?.offset() as i32) + let ptr = pointer_from_string(&env, &mut ctx, &operation_id)?.offset() as i32; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: operation_id.into(), + sub_calls: None, + }); + Ok(ptr) } /// gets the period of the current execution slot @@ -896,7 +1275,15 @@ pub(crate) fn assembly_script_get_origin_operation_id( pub(crate) fn assembly_script_get_current_period(mut ctx: FunctionEnvMut) -> ABIResult { let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; - Ok(env.get_interface().get_current_period()? as i64) + let current_period = env.get_interface().get_current_period()?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: current_period.into(), + sub_calls: None, + }); + Ok(current_period as i64) } /// gets the thread of the current execution slot @@ -904,7 +1291,15 @@ pub(crate) fn assembly_script_get_current_period(mut ctx: FunctionEnvMut) pub(crate) fn assembly_script_get_current_thread(mut ctx: FunctionEnvMut) -> ABIResult { let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; - Ok(env.get_interface().get_current_thread()? as i32) + let current_thread = env.get_interface().get_current_thread()?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: current_thread.into(), + sub_calls: None, + }); + Ok(current_thread as i32) } /// sets the executable bytecode of an arbitrary address @@ -928,6 +1323,13 @@ pub(crate) fn assembly_script_set_bytecode_for( // } env.get_interface() .raw_set_bytecode_for(&address, &bytecode_raw)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(address), into_trace_value!(bytecode_raw)], + return_value: AbiTraceType::None, + sub_calls: None, + }); Ok(()) } @@ -947,6 +1349,13 @@ pub(crate) fn assembly_script_set_bytecode( // param_size_update(&env, &mut ctx, &fname, bytecode_raw.len(), true); // } env.get_interface().raw_set_bytecode(&bytecode_raw)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(bytecode_raw)], + return_value: AbiTraceType::None, + sub_calls: None, + }); Ok(()) } @@ -956,7 +1365,15 @@ pub(crate) fn assembly_script_get_bytecode(mut ctx: FunctionEnvMut) -> AB let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; let data = env.get_interface().raw_get_bytecode()?; - Ok(pointer_from_bytearray(&env, &mut ctx, &data)?.offset() as i32) + let ptr = pointer_from_bytearray(&env, &mut ctx, &data)?.offset() as i32; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: data.into(), + sub_calls: None, + }); + Ok(ptr) } /// get bytecode of the target address @@ -970,7 +1387,15 @@ pub(crate) fn assembly_script_get_bytecode_for( let memory = get_memory!(env); let address = read_string(memory, &ctx, address)?; let data = env.get_interface().raw_get_bytecode_for(&address)?; - Ok(pointer_from_bytearray(&env, &mut ctx, &data)?.offset() as i32) + let ptr = pointer_from_bytearray(&env, &mut ctx, &data)?.offset() as i32; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(address)], + return_value: data.into(), + sub_calls: None, + }); + Ok(ptr) } /// execute `function` of the given bytecode in the current context @@ -985,18 +1410,29 @@ pub(crate) fn assembly_script_local_execution( sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; let memory = get_memory!(env); - let bytecode = &read_buffer(memory, &ctx, bytecode)?; - let function = &read_string(memory, &ctx, function)?; - let param = &read_buffer(memory, &ctx, param)?; - - let response = local_call(&mut ctx, bytecode, function, param, true)?; - match BufferPtr::alloc(&response.ret, env.get_ffi_env(), &mut ctx) { + let bytecode = read_buffer(memory, &ctx, bytecode)?; + let function = read_string(memory, &ctx, function)?; + let param = read_buffer(memory, &ctx, param)?; + let response = local_call(&mut ctx, &bytecode, &function, ¶m, true)?; + let res = match BufferPtr::alloc(&response.ret, env.get_ffi_env(), &mut ctx) { Ok(ret) => Ok(ret.offset() as i32), _ => abi_bail!(format!( "Cannot allocate response in local call of {}", function )), - } + }; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + into_trace_value!(bytecode), + into_trace_value!(function), + into_trace_value!(param), + ], + return_value: response.ret.clone().into(), + sub_calls: Some(response.trace), + }); + res } /// execute `function` of the bytecode located at `address` in the current @@ -1014,17 +1450,30 @@ pub(crate) fn assembly_script_local_call( let address = &read_string(memory, &ctx, address)?; let bytecode = env.get_interface().raw_get_bytecode_for(address)?; - let function = &read_string(memory, &ctx, function)?; - let param = &read_buffer(memory, &ctx, param)?; + let function = read_string(memory, &ctx, function)?; + let param = read_buffer(memory, &ctx, param)?; - let response = local_call(&mut ctx, &bytecode, function, param, false)?; - match BufferPtr::alloc(&response.ret, env.get_ffi_env(), &mut ctx) { + let response = local_call(&mut ctx, &bytecode, &function, ¶m, false)?; + let res = match BufferPtr::alloc(&response.ret, env.get_ffi_env(), &mut ctx) { Ok(ret) => Ok(ret.offset() as i32), _ => abi_bail!(format!( "Cannot allocate response in local call of {}", function )), - } + }; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + into_trace_value!(bytecode), + into_trace_value!(function), + into_trace_value!(param), + ], + return_value: response.ret.clone().into(), + sub_calls: Some(response.trace), + }); + + res } /// Check whether or not the caller has write access in the current context @@ -1032,10 +1481,18 @@ pub(crate) fn assembly_script_local_call( pub fn assembly_script_caller_has_write_access(mut ctx: FunctionEnvMut) -> ABIResult { let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; - Ok(env.get_interface().caller_has_write_access()? as i32) -} - -/// Check whether or not the given function exists at the given address + let has_write_access = env.get_interface().caller_has_write_access()?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: has_write_access.into(), + sub_calls: None, + }); + Ok(has_write_access as i32) +} + +/// Check whether the given function exists at the given address #[named] pub fn assembly_script_function_exists( mut ctx: FunctionEnvMut, @@ -1045,10 +1502,17 @@ pub fn assembly_script_function_exists( let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; let memory = get_memory!(env); - let address = &read_string(memory, &ctx, address)?; - let function = &read_string(memory, &ctx, function)?; - - Ok(function_exists(&mut ctx, address, function)? as i32) + let address = read_string(memory, &ctx, address)?; + let function = read_string(memory, &ctx, function)?; + let function_exists = function_exists(&mut ctx, &address, &function)?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(address), into_trace_value!(function)], + return_value: function_exists.into(), + sub_calls: None, + }); + Ok(function_exists as i32) } /// Return current chain id @@ -1056,14 +1520,25 @@ pub fn assembly_script_function_exists( pub(crate) fn assembly_script_chain_id(mut ctx: FunctionEnvMut) -> ABIResult { let env = get_env(&ctx)?; sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; - Ok(env.get_interface().chain_id()? as u64) + let chain_id = env.get_interface().chain_id()?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: chain_id.into(), + sub_calls: None, + }); + Ok(chain_id as u64) } /// Assembly script builtin `abort` function. /// /// It prints the origin filename, an error messag, the line and column. +#[allow(unused_macros)] +#[allow(unused_mut)] +#[named] pub fn assembly_script_abort( - ctx: FunctionEnvMut, + mut ctx: FunctionEnvMut, message: StringPtr, filename: StringPtr, line: i32, @@ -1086,6 +1561,18 @@ pub fn assembly_script_abort( if message_.is_err() || filename_.is_err() { abi_bail!("aborting failed to load message or filename") } + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + into_trace_value!(message_.clone().unwrap_or_default()), + into_trace_value!(filename_.clone().unwrap_or_default()), + into_trace_value!(line), + into_trace_value!(col), + ], + return_value: AbiTraceType::None, + sub_calls: None, + }); abi_bail!(format!( "error: {} at {}:{} col: {}", message_.unwrap(), @@ -1102,10 +1589,18 @@ pub fn assembly_script_seed(mut ctx: FunctionEnvMut) -> ABIResult { if cfg!(not(feature = "gas_calibration")) { sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; } - match env.interface.unsafe_random_f64() { - Ok(ret) => Ok(ret), + let seed = match env.interface.unsafe_random_f64() { + Ok(ret) => ret, _ => abi_bail!("failed to get random from interface"), - } + }; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: seed.into(), + sub_calls: None, + }); + Ok(seed) } /// Assembly script builtin `Date.now()` @@ -1120,6 +1615,13 @@ pub fn assembly_script_date_now(mut ctx: FunctionEnvMut) -> ABIResult abi_bail!("failed to get time from interface"), }; let ret = utime as f64; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![], + return_value: ret.into(), + sub_calls: None, + }); Ok(ret) } @@ -1173,6 +1675,7 @@ pub fn assembly_script_console_debug( if cfg!(not(feature = "gas_calibration")) { sub_remaining_gas_abi(&env, &mut ctx, function_name!())?; } + assembly_script_console(ctx, message, "DEBUG") } @@ -1190,8 +1693,11 @@ pub fn assembly_script_console_error( } /// Assembly script console functions +#[allow(unused_macros)] +#[allow(unused_mut)] +#[named] fn assembly_script_console( - ctx: FunctionEnvMut, + mut ctx: FunctionEnvMut, message: StringPtr, prefix: &str, ) -> ABIResult<()> { @@ -1209,7 +1715,14 @@ fn assembly_script_console( .add(" | ") .add(&message.read(&memory, &ctx)?); - env.get_interface().generate_event(message)?; + env.get_interface().generate_event(message.clone())?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(message)], + return_value: AbiTraceType::None, + sub_calls: None, + }); Ok(()) } @@ -1256,7 +1769,15 @@ pub fn assembly_script_trace( _ => message, // Should we warn here or return an error? }; - env.get_interface().generate_event(message_for_event)?; + env.get_interface() + .generate_event(message_for_event.clone())?; + #[cfg(feature = "execution-trace")] + ctx.data_mut().trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![into_trace_value!(message_for_event)], + return_value: AbiTraceType::None, + sub_calls: None, + }); Ok(()) } diff --git a/src/as_execution/context.rs b/src/as_execution/context.rs index 7a669ccc..1628cc83 100644 --- a/src/as_execution/context.rs +++ b/src/as_execution/context.rs @@ -37,7 +37,7 @@ impl ASContext { pub(crate) fn create_vm_instance_and_init_env( &mut self, store: &mut Store, - ) -> Result<(Instance, u64)> { + ) -> Result<(Instance, FunctionEnv, u64)> { let (imports, mut fenv) = self.resolver(store); match Instance::new(store, &self.module, &imports) { Ok(instance) => { @@ -56,7 +56,7 @@ impl ASContext { self.env .abi_enabled .store(true, std::sync::atomic::Ordering::Relaxed); - Ok((instance, post_init_points)) + Ok((instance, fenv, post_init_points)) } Err(err) => { // Filter the error created by the metering middleware when @@ -114,6 +114,8 @@ impl ASContext { ret: Vec::new(), // main return empty vec remaining_gas: remaining_gas?, init_gas_cost: 0, + #[cfg(feature = "execution-trace")] + trace: Default::default(), }); } let ret = if let Some(offset) = value.first() { @@ -136,6 +138,8 @@ impl ASContext { ret, remaining_gas: remaining_gas?, init_gas_cost: 0, + #[cfg(feature = "execution-trace")] + trace: Default::default(), }) } Err(error) => bail!(error), diff --git a/src/as_execution/env.rs b/src/as_execution/env.rs index c7db67d5..f8370946 100644 --- a/src/as_execution/env.rs +++ b/src/as_execution/env.rs @@ -1,5 +1,9 @@ use super::{abi_bail, ABIResult}; use crate::types::Interface; + +#[cfg(feature = "execution-trace")] +use crate::types::AbiTrace; + use crate::GasCosts; use std::{ collections::HashMap, @@ -30,6 +34,8 @@ pub struct ASEnv { gas_costs: GasCosts, /// Initially added for gas calibration but unused at the moment. param_size_map: HashMap>, + #[cfg(feature = "execution-trace")] + pub trace: Vec, } impl ASEnv { @@ -42,6 +48,8 @@ impl ASEnv { remaining_points: None, exhausted_points: None, param_size_map: Default::default(), + #[cfg(feature = "execution-trace")] + trace: Default::default(), } } pub fn get_interface(&self) -> Box { diff --git a/src/as_execution/mod.rs b/src/as_execution/mod.rs index 0d23253a..d48343c7 100644 --- a/src/as_execution/mod.rs +++ b/src/as_execution/mod.rs @@ -211,10 +211,10 @@ pub(crate) fn exec_as_module( let mut store = Store::new(engine); let mut context = ASContext::new(interface, as_module.binary_module, gas_costs); - // save the gas remaining before subexecution: used by readonly execution + // save the gas remaining before sub-execution: used by readonly execution interface.save_gas_remaining_before_subexecution(limit); - let (instance, init_rem_points) = context.create_vm_instance_and_init_env(&mut store)?; + let (instance, _fenv, init_rem_points) = context.create_vm_instance_and_init_env(&mut store)?; let init_cost = as_module.initial_limit.saturating_sub(init_rem_points); if cfg!(not(feature = "gas_calibration")) { @@ -229,6 +229,12 @@ pub(crate) fn exec_as_module( None }; response.init_gas_cost = init_cost; + + #[cfg(feature = "execution-trace")] + { + response.trace = _fenv.as_ref(&store).trace.clone(); + } + Ok((response, gc_result)) } Err(err) => { diff --git a/src/tests/tests_runtime.rs b/src/tests/tests_runtime.rs index c8f880b3..f17b8fff 100644 --- a/src/tests/tests_runtime.rs +++ b/src/tests/tests_runtime.rs @@ -11,6 +11,9 @@ use serial_test::serial; use wasmer::Store; use wasmer::WasmPtr; +#[cfg(feature = "execution-trace")] +use crate::{AbiTrace, AbiTraceType, AbiTraceValue}; + #[test] #[serial] #[ignore] @@ -132,6 +135,30 @@ fn test_run_main() { run_main(&*interface, runtime_module, 100_000, gas_costs).unwrap(); } +#[cfg(feature = "execution-trace")] +#[test] +#[serial] +/// Test basic main-only SC execution +fn test_run_main_get_execution_traces() { + let gas_costs = GasCosts::default(); + let interface: Box = Box::new(TestInterface); + let module = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/wasm/basic_main.wasm")); + + let runtime_module = RuntimeModule::new(module, gas_costs.clone(), Compiler::SP).unwrap(); + let resp = run_main(&*interface, runtime_module, 100_000, gas_costs).unwrap(); + + assert_eq!(resp.trace.is_empty(), false); + assert_eq!( + resp.trace, + vec![AbiTrace { + name: "assembly_script_generate_event".to_string(), + params: vec![("event", "hello world!".to_string()).into()], + return_value: AbiTraceType::None, + sub_calls: None + }] + ) +} + #[test] #[serial] fn test_run_register_wasmv1() { @@ -389,7 +416,25 @@ fn test_transfer_coins_wasmv1_as() { println!("Module type WasmV1Module"); } } - run_main(&*interface, runtime_module, 100_000, gas_costs).unwrap(); + + let _resp = run_main(&*interface, runtime_module, 100_000, gas_costs).unwrap(); + + #[cfg(feature = "execution-trace")] + { + assert_eq!(_resp.trace.is_empty(), false); + let trace_1 = _resp.trace.get(0).unwrap(); + assert_eq!(trace_1.name, "abi_transfer_coins"); + assert_eq!( + trace_1.params, + vec![ + ("target_address", "abcd".to_string()).into(), + ("amount", 100i64).into(), + ("sender_address", "efgh".to_string()).into(), + ] + ); + assert_eq!(trace_1.return_value, AbiTraceType::None); + assert_eq!(trace_1.sub_calls, None); + } } #[test] @@ -809,7 +854,7 @@ fn test_class_id() { let module = ASModule::new(bytecode, 100_000, GasCosts::default(), Compiler::SP).unwrap(); let mut store = Store::new(module._engine); let mut context = ASContext::new(&*interface, module.binary_module, GasCosts::default()); - let (instance, _) = context.create_vm_instance_and_init_env(&mut store).unwrap(); + let (instance, _function_env, _) = context.create_vm_instance_and_init_env(&mut store).unwrap(); // setup test specific context let (_, fenv) = context.resolver(&mut store); @@ -874,3 +919,49 @@ fn test_class_id() { ); assert_eq!(array_class_id, 4); } + +#[cfg(feature = "execution-trace")] +#[test] +#[serial] +/// Non regression test on the AS class id values +fn test_ser() { + let at0 = AbiTraceType::Bool(true); + let at1 = AbiTraceType::String("hello".to_string()); + let at2 = AbiTraceType::U64(33); + let at3 = AbiTraceType::ByteArray(vec![1, 255, 0]); + let at4 = AbiTraceType::ByteArrays(vec![vec![1, 255, 0], vec![11, 42, 33]]); + let at5 = AbiTraceType::Strings(vec!["yo".to_string(), "yo2".to_string()]); + let at6 = AbiTraceType::Slot((111, 22)); + let s0 = serde_json::to_string(&at0).unwrap(); + println!("s0: {}", s0); + assert!(s0.find("bool").is_some()); + let s1 = serde_json::to_string(&at1).unwrap(); + println!("s1: {}", s1); + assert!(s1.find("string").is_some()); + let s2 = serde_json::to_string(&at2).unwrap(); + println!("s2: {}", s2); + assert!(s2.find("u64").is_some()); + let s3 = serde_json::to_string(&at3).unwrap(); + println!("s3: {}", s3); + assert!(s3.find("byteArray").is_some()); + let s4 = serde_json::to_string(&at4).unwrap(); + println!("s4: {}", s4); + assert!(s4.find("byteArrays").is_some()); + let s5 = serde_json::to_string(&at5).unwrap(); + println!("s5: {}", s5); + assert!(s5.find("strings").is_some()); + let s6 = serde_json::to_string(&at6).unwrap(); + println!("s6: {}", s6); + assert!(s6.find("slot").is_some()); + + let atv1 = AbiTraceValue { + name: "foo".to_string(), + value: at6, + }; + let s_atv1_ = serde_json::to_string(&atv1); + println!("s_atv1: {:?}", s_atv1_); + + let s_atv1 = s_atv1_.unwrap(); + assert!(s_atv1.find("foo").is_some()); + assert!(s_atv1.find("slot").is_some()); +} diff --git a/src/types.rs b/src/types.rs index aa897615..961e4de7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -10,6 +10,139 @@ use std::{ use crate::execution::RuntimeModule; +#[cfg(feature = "execution-trace")] +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(tag = "type", content = "value", rename_all = "camelCase")] +pub enum AbiTraceType { + None, + Bool(bool), + U8(u8), + I32(i32), + U32(u32), + I64(i64), + U64(u64), + F64(f64), + ByteArray(Vec), + ByteArrays(Vec>), + String(String), + Strings(Vec), + Slot((u64, u8)), +} + +#[cfg(feature = "execution-trace")] +impl From for AbiTraceType { + fn from(v: bool) -> Self { + Self::Bool(v) + } +} +#[cfg(feature = "execution-trace")] +impl From for AbiTraceType { + fn from(v: u8) -> Self { + Self::U8(v) + } +} +#[cfg(feature = "execution-trace")] +impl From for AbiTraceType { + fn from(v: i32) -> Self { + Self::I32(v) + } +} + +#[cfg(feature = "execution-trace")] +impl From for AbiTraceType { + fn from(v: u32) -> Self { + Self::U32(v) + } +} + +#[cfg(feature = "execution-trace")] +impl From for AbiTraceType { + fn from(v: i64) -> Self { + Self::I64(v) + } +} +#[cfg(feature = "execution-trace")] +impl From for AbiTraceType { + fn from(v: u64) -> Self { + Self::U64(v) + } +} +#[cfg(feature = "execution-trace")] +impl From for AbiTraceType { + fn from(v: f64) -> Self { + Self::F64(v) + } +} +#[cfg(feature = "execution-trace")] +impl From> for AbiTraceType { + fn from(v: Vec) -> Self { + Self::ByteArray(v) + } +} +#[cfg(feature = "execution-trace")] +impl From>> for AbiTraceType { + fn from(v: Vec>) -> Self { + Self::ByteArrays(v) + } +} +#[cfg(feature = "execution-trace")] +impl From for AbiTraceType { + fn from(v: String) -> Self { + Self::String(v) + } +} +#[cfg(feature = "execution-trace")] +impl From> for AbiTraceType { + fn from(v: Vec) -> Self { + Self::Strings(v) + } +} + +#[cfg(feature = "execution-trace")] +impl From<(u64, u8)> for AbiTraceType { + fn from(v: (u64, u8)) -> Self { + Self::Slot(v) + } +} + +#[cfg(feature = "execution-trace")] +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct AbiTraceValue { + pub name: String, + #[serde(flatten)] + pub value: AbiTraceType, +} + +#[cfg(feature = "execution-trace")] +impl From<(&str, T)> for AbiTraceValue +where + T: Into, +{ + fn from((name, value): (&str, T)) -> Self { + Self { + name: name.to_string(), + value: value.into(), + } + } +} + +#[cfg(feature = "execution-trace")] +#[macro_export] +macro_rules! into_trace_value { + ($a: expr) => {{ + (stringify!($a), $a).into() + }}; +} + +#[cfg(feature = "execution-trace")] +#[derive(Debug, Clone, PartialEq)] +pub struct AbiTrace { + pub name: String, + pub params: Vec, + pub return_value: AbiTraceType, + pub sub_calls: Option>, +} + /// That's what is returned when a module is executed correctly since the end #[derive(Debug)] pub struct Response { @@ -19,6 +152,8 @@ pub struct Response { pub remaining_gas: u64, /// number of gas required for the instance creation pub init_gas_cost: u64, + #[cfg(feature = "execution-trace")] + pub trace: Vec, } pub trait InterfaceClone { diff --git a/src/wasmv1_execution/abi/abis.rs b/src/wasmv1_execution/abi/abis.rs index 1b6fa31e..82cc6df8 100644 --- a/src/wasmv1_execution/abi/abis.rs +++ b/src/wasmv1_execution/abi/abis.rs @@ -9,6 +9,14 @@ use massa_proto_rs::massa::{ }; use wasmer::{imports, AsStoreMut, Function, FunctionEnv, FunctionEnvMut, Imports}; +use crate::Interface; +#[cfg(feature = "execution-trace")] +use crate::{into_trace_value, AbiTrace, AbiTraceType}; +#[cfg(feature = "execution-trace")] +use rust_decimal::prelude::ToPrimitive; +#[cfg(feature = "execution-trace")] +use rust_decimal::Decimal; + // This macro ease the construction of the Error variant of the response to an // ABI call. macro_rules! resp_err { @@ -131,17 +139,21 @@ fn abi_call(store_env: FunctionEnvMut, arg_offset: i32) -> Result, arg_offset: i32) -> Result, arg_offset: i32) -> Result< store_env, arg_offset, |handler, req: CallRequest| { - let bytecode = helper_get_bytecode(handler, req.target_sc_address)?; + let bytecode = helper_get_bytecode(handler, req.target_sc_address.clone())?; let remaining_gas = handler.get_remaining_gas(); - let module = helper_get_module(handler, bytecode, remaining_gas)?; + let interface = handler.exec_env.get_interface(); + let module = helper_get_module(interface, bytecode.clone(), remaining_gas)?; let response = crate::execution::run_function( - handler.interface, + interface, module, &req.target_function_name, &req.function_arg, @@ -182,6 +213,22 @@ fn abi_local_call(store_env: FunctionEnvMut, arg_offset: i32) -> Result< .map_err(|err| WasmV1Error::RuntimeError(format!("Could not run function: {}", err)))?; handler.set_remaining_gas(response.remaining_gas); + #[cfg(feature = "execution-trace")] + { + if let Some(exec_env) = handler.store_env.data_mut().lock().as_mut() { + exec_env.trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + into_trace_value!(bytecode), + into_trace_value!(req.target_function_name), + into_trace_value!(req.function_arg), + ], + return_value: AbiTraceType::ByteArray(response.ret.clone()), + sub_calls: Some(response.trace), + }); + } + } + Ok(CallResponse { data: response.ret }) }, ) @@ -195,7 +242,8 @@ fn abi_create_sc(store_env: FunctionEnvMut, arg_offset: i32) -> Result Result { - match handler.interface.create_module(&req.bytecode) { + let interface = handler.exec_env.get_interface(); + match interface.create_module(&req.bytecode) { Ok(sc_address) => { resp_ok!(CreateScResult, { sc_address }) } @@ -222,7 +270,8 @@ fn abi_get_current_slot( // param_size_update(&env, &mut ctx, &fname, to_address.len(), // true); } - match handler.interface.get_current_slot() { + let interface = handler.exec_env.get_interface(); + match interface.get_current_slot() { Ok(slot) => resp_ok!(GetCurrentSlotResult, { slot: Some(slot) }), @@ -246,7 +295,8 @@ fn abi_hash_blake3(store_env: FunctionEnvMut, arg_offset: i32) -> Result // param_size_update(&env, &mut ctx, &fname, to_address.len(), // true); } - match handler.interface.hash_blake3(&req.data) { + let interface = handler.exec_env.get_interface(); + match interface.hash_blake3(&req.data) { Ok(hash) => { resp_ok!(HashBlake3Result, { hash: hash.to_vec() }) } @@ -270,7 +320,8 @@ fn abi_hash_sha256(store_env: FunctionEnvMut, arg_offset: i32) -> Result // param_size_update(&env, &mut ctx, &fname, to_address.len(), // true); } - match handler.interface.hash_sha256(&req.data) { + let interface = handler.exec_env.get_interface(); + match interface.hash_sha256(&req.data) { Ok(hash) => resp_ok!(HashSha256Result, { hash: hash.to_vec() }), Err(e) => resp_err!(e), } @@ -295,7 +346,8 @@ fn abi_hash_keccak256( // param_size_update(&env, &mut ctx, &fname, to_address.len(), // true); } - match handler.interface.hash_keccak256(&req.data) { + let interface = handler.exec_env.get_interface(); + match interface.hash_keccak256(&req.data) { Ok(hash) => resp_ok!(Keccak256Result, { hash: hash.to_vec() }), Err(e) => resp_err!(e), } @@ -324,12 +376,49 @@ fn abi_transfer_coins( // param_size_update(&env, &mut ctx, &fname, to_address.len(), // true); } - match handler.interface.transfer_coins_wasmv1( - req.target_address, + #[cfg(feature = "execution-trace")] + let amount_ = Decimal::try_from_i128_with_scale(amount.mantissa as i128, amount.scale) + .unwrap_or_default(); + + let interface = handler.exec_env.get_interface(); + + match interface.transfer_coins_wasmv1( + req.target_address.clone(), amount, - req.sender_address, + req.sender_address.clone(), ) { - Ok(_) => resp_ok!(TransferCoinsResult, {}), + Ok(_) => { + #[cfg(feature = "execution-trace")] + { + // Build parameters + let mut params = vec![ + ("target_address", req.target_address).into(), + ("amount", amount_.to_i64().unwrap_or_default()).into(), + ]; + + let sender_address = match req.sender_address { + Some(sender_address) => sender_address, + None => { + let call_stack = interface.get_call_stack(); + call_stack + .unwrap_or_default() + .last() + .cloned() + .unwrap_or_default() + } + }; + params.push(into_trace_value!(sender_address)); + + // let mut guard = handler.store_env.data_mut().lock(); + handler.exec_env.trace.push(AbiTrace { + name: function_name!().to_string(), + params, + return_value: AbiTraceType::None, + sub_calls: None, + }); + } + resp_ok!(TransferCoinsResult, {}) + } Err(e) => resp_err!(e), } }, @@ -346,12 +435,10 @@ fn abi_generate_event( store_env, arg_offset, |handler, req: GenerateEventRequest| -> Result { - handler - .interface - .generate_event_wasmv1(req.event) - .map_err(|err| { - WasmV1Error::RuntimeError(format!("Failed to generate event: {}", err)) - })?; + let interface = handler.exec_env.get_interface(); + interface.generate_event_wasmv1(req.event).map_err(|err| { + WasmV1Error::RuntimeError(format!("Failed to generate event: {}", err)) + })?; resp_ok!(GenerateEventResult, {}) }, @@ -383,10 +470,8 @@ fn abi_set_ds_value( store_env, arg_offset, |handler, req: SetDsValueRequest| -> Result { - if let Err(e) = handler - .interface - .set_ds_value_wasmv1(&req.key, &req.value, None) - { + let interface = handler.exec_env.get_interface(); + if let Err(e) = interface.set_ds_value_wasmv1(&req.key, &req.value, None) { return resp_err!(e); } resp_ok!(SetDsValueResult, {}) @@ -404,7 +489,8 @@ fn abi_get_ds_value( store_env, arg_offset, |handler, req: GetDsValueRequest| -> Result { - match handler.interface.get_ds_value_wasmv1(&req.key, req.address) { + let interface = handler.exec_env.get_interface(); + match interface.get_ds_value_wasmv1(&req.key, req.address) { Ok(value) => resp_ok!(GetDsValueResult, { value }), Err(e) => resp_err!(e), } @@ -422,10 +508,8 @@ fn abi_delete_ds_entry( store_env, arg_offset, |handler, req: DeleteDsEntryRequest| -> Result { - if let Err(e) = handler - .interface - .delete_ds_entry_wasmv1(&req.key, req.address) - { + let interface = handler.exec_env.get_interface(); + if let Err(e) = interface.delete_ds_entry_wasmv1(&req.key, req.address) { return resp_err!(e); } resp_ok!(DeleteDsEntryResult, {}) @@ -443,11 +527,8 @@ fn abi_append_ds_value( store_env, arg_offset, |handler, req: AppendDsValueRequest| -> Result { - if let Err(e) = - handler - .interface - .append_ds_value_wasmv1(&req.key, &req.value, req.address) - { + let interface = handler.exec_env.get_interface(); + if let Err(e) = interface.append_ds_value_wasmv1(&req.key, &req.value, req.address) { return resp_err!(e); } resp_ok!(AppendDsValueResult, {}) @@ -465,10 +546,8 @@ fn abi_ds_entry_exists( store_env, arg_offset, |handler, req: DsEntryExistsRequest| -> Result { - match handler - .interface - .ds_entry_exists_wasmv1(&req.key, req.address) - { + let interface = handler.exec_env.get_interface(); + match interface.ds_entry_exists_wasmv1(&req.key, req.address) { Ok(has_data) => resp_ok!(DsEntryExistsResult, { has_data }), Err(e) => resp_err!(e), } @@ -483,7 +562,8 @@ fn abi_get_balance(store_env: FunctionEnvMut, arg_offset: i32) -> Result store_env, arg_offset, |handler, req: GetBalanceRequest| -> Result { - match handler.interface.get_balance_wasmv1(req.address) { + let interface = handler.exec_env.get_interface(); + match interface.get_balance_wasmv1(req.address) { Ok(res) => resp_ok!(GetBalanceResult, { balance: Some(res) }), Err(e) => resp_err!(e), } @@ -501,7 +581,8 @@ fn abi_get_bytecode( store_env, arg_offset, |handler, req: GetBytecodeRequest| -> Result { - match handler.interface.get_bytecode_wasmv1(req.address) { + let interface = handler.exec_env.get_interface(); + match interface.get_bytecode_wasmv1(req.address) { Ok(bytecode) => resp_ok!(GetBytecodeResult, { bytecode }), Err(e) => resp_err!(e), } @@ -519,10 +600,8 @@ fn abi_set_bytecode( store_env, arg_offset, |handler, req: SetBytecodeRequest| -> Result { - match handler - .interface - .set_bytecode_wasmv1(&req.bytecode, req.address) - { + let interface = handler.exec_env.get_interface(); + match interface.set_bytecode_wasmv1(&req.bytecode, req.address) { Ok(_) => resp_ok!(SetBytecodeResult, {}), Err(e) => resp_err!(e), } @@ -537,10 +616,8 @@ fn abi_get_ds_keys(store_env: FunctionEnvMut, arg_offset: i32) -> Result store_env, arg_offset, |handler, req: GetDsKeysRequest| -> Result { - match handler - .interface - .get_ds_keys_wasmv1(&req.prefix, req.address) - { + let interface = handler.exec_env.get_interface(); + match interface.get_ds_keys_wasmv1(&req.prefix, req.address) { Ok(res) => { resp_ok!(GetDsKeysResult, { keys: res.into_iter().collect()}) } @@ -557,7 +634,8 @@ fn abi_get_op_keys(store_env: FunctionEnvMut, arg_offset: i32) -> Result store_env, arg_offset, |handler, req: GetOpKeysRequest| -> Result { - match handler.interface.get_op_keys_wasmv1(&req.prefix) { + let interface = handler.exec_env.get_interface(); + match interface.get_op_keys_wasmv1(&req.prefix) { Ok(keys) => resp_ok!(GetOpKeysResult, { keys }), Err(e) => resp_err!(e), } @@ -575,7 +653,8 @@ fn abi_op_entry_exists( store_env, arg_offset, |handler, req: OpEntryExistsRequest| -> Result { - match handler.interface.op_entry_exists(&req.key) { + let interface = handler.exec_env.get_interface(); + match interface.op_entry_exists(&req.key) { Ok(has_key) => resp_ok!(OpEntryExistsResult, { has_key }), Err(e) => resp_err!(e), } @@ -590,7 +669,8 @@ fn abi_get_op_data(store_env: FunctionEnvMut, arg_offset: i32) -> Result store_env, arg_offset, |handler, req: GetOpDataRequest| -> Result { - match handler.interface.get_op_data(&req.key) { + let interface = handler.exec_env.get_interface(); + match interface.get_op_data(&req.key) { Ok(value) => resp_ok!(GetOpDataResult, { value }), Err(e) => resp_err!(e), } @@ -608,10 +688,8 @@ fn abi_evm_verify_signature( store_env, arg_offset, |handler, req: EvmVerifySigRequest| -> Result { - match handler - .interface - .evm_signature_verify(&req.message, &req.sig, &req.pub_key) - { + let interface = handler.exec_env.get_interface(); + match interface.evm_signature_verify(&req.message, &req.sig, &req.pub_key) { Ok(is_verified) => { resp_ok!(EvmVerifySigResult, { is_verified }) } @@ -633,7 +711,8 @@ fn abi_evm_get_address_from_pubkey( store_env, arg_offset, |handler, req: EvmGetAddressFromPubkeyRequest| -> Result { - match handler.interface.evm_get_address_from_pubkey(&req.pub_key) { + let interface = handler.exec_env.get_interface(); + match interface.evm_get_address_from_pubkey(&req.pub_key) { Ok(address) => { resp_ok!(EvmGetAddressFromPubkeyResult, { address }) } @@ -653,10 +732,8 @@ fn abi_evm_get_pubkey_from_signature( store_env, arg_offset, |handler, req: EvmGetPubkeyFromSignatureRequest| -> Result { - match handler - .interface - .evm_get_pubkey_from_signature(&req.hash, &req.sig) - { + let interface = handler.exec_env.get_interface(); + match interface.evm_get_pubkey_from_signature(&req.hash, &req.sig) { Ok(pub_key) => { resp_ok!(EvmGetPubkeyFromSignatureResult, { pub_key }) } @@ -676,7 +753,8 @@ fn abi_is_address_eoa( store_env, arg_offset, |handler, req: IsAddressEoaRequest| -> Result { - match handler.interface.is_address_eoa(&req.address) { + let interface = handler.exec_env.get_interface(); + match interface.is_address_eoa(&req.address) { Ok(is_eoa) => resp_ok!(IsAddressEoaResult, { is_eoa }), Err(e) => resp_err!(e), } @@ -710,7 +788,8 @@ fn abi_get_owned_addresses( store_env, arg_offset, |handler, _req: GetOwnedAddressesRequest| -> Result { - match handler.interface.get_owned_addresses() { + let interface = handler.exec_env.get_interface(); + match interface.get_owned_addresses() { Ok(addresses) => { resp_ok!(GetOwnedAddressesResult, { addresses }) } @@ -730,7 +809,8 @@ fn abi_get_call_stack( store_env, arg_offset, |handler, _req: GetCallStackRequest| -> Result { - match handler.interface.get_call_stack() { + let interface = handler.exec_env.get_interface(); + match interface.get_call_stack() { Ok(calls) => { resp_ok!(GetCallStackResult, { calls }) } @@ -750,7 +830,8 @@ fn abi_address_from_public_key( store_env, arg_offset, |handler, req: AddressFromPubKeyRequest| -> Result { - match handler.interface.address_from_public_key(&req.pub_key) { + let interface = handler.exec_env.get_interface(); + match interface.address_from_public_key(&req.pub_key) { Ok(address) => { resp_ok!(AddressFromPubKeyResult, { address }) } @@ -773,8 +854,9 @@ fn abi_unsafe_random( if req.num_bytes as u64 > handler.get_max_mem_size() { return resp_err!("Requested random bytes exceed the maximum memory size"); } + let interface = handler.exec_env.get_interface(); - match handler.interface.unsafe_random_wasmv1(req.num_bytes as u64) { + match interface.unsafe_random_wasmv1(req.num_bytes as u64) { Ok(random_bytes) => { resp_ok!(UnsafeRandomResult, { random_bytes }) } @@ -794,7 +876,8 @@ fn abi_get_call_coins( store_env, arg_offset, |handler, _req: GetCallCoinsRequest| -> Result { - match handler.interface.get_call_coins_wasmv1() { + let interface = handler.exec_env.get_interface(); + match interface.get_call_coins_wasmv1() { Ok(coins) => { resp_ok!(GetCallCoinsResult, { coins: Some(coins) }) } @@ -814,7 +897,8 @@ fn abi_get_native_time( store_env, arg_offset, |handler, _req: GetNativeTimeRequest| -> Result { - match handler.interface.get_time() { + let interface = handler.exec_env.get_interface(); + match interface.get_time() { Err(e) => resp_err!(e), Ok(time) => { resp_ok!(GetNativeTimeResult, { time: Some(NativeTime { milliseconds: time }) }) @@ -852,7 +936,8 @@ fn abi_send_async_message( .as_ref() .map(|f| (f.target_address.as_str(), f.target_key.as_deref())); - match handler.interface.send_message( + let interface = handler.exec_env.get_interface(); + match interface.send_message( &req.target_address, &req.target_handler, (start.period, start_thread), @@ -863,7 +948,39 @@ fn abi_send_async_message( &req.data, filter, ) { - Ok(_) => resp_ok!(SendAsyncMessageResult, {}), + Ok(_) => { + #[cfg(feature = "execution-trace")] + { + let filter_key = filter.unwrap_or_default().1.unwrap_or_default().to_vec(); + let filter = filter.unwrap_or_default().0.to_string(); + let params = vec![ + into_trace_value!(req.target_address), + into_trace_value!(req.target_handler), + into_trace_value!(start.period), + into_trace_value!(start.thread), + into_trace_value!(end.period), + into_trace_value!(end.thread), + into_trace_value!(req.execution_gas), + into_trace_value!(req.raw_fee), + into_trace_value!(req.raw_coins), + into_trace_value!(req.data), + // filter address + into_trace_value!(filter), + // filter key + into_trace_value!(filter_key), + ]; + if let Some(exec_env) = handler.store_env.data_mut().lock().as_mut() { + exec_env.trace.push(AbiTrace { + name: function_name!().to_string(), + params, + return_value: AbiTraceType::None, + sub_calls: None, + }); + } + } + + resp_ok!(SendAsyncMessageResult, {}) + } Err(e) => resp_err!(e), } }, @@ -880,7 +997,8 @@ fn abi_get_origin_operation_id( store_env, arg_offset, |handler, _req: GetOriginOperationIdRequest| -> Result { - match handler.interface.get_origin_operation_id() { + let interface = handler.exec_env.get_interface(); + match interface.get_origin_operation_id() { Ok(operation_id) => { resp_ok!(GetOriginOperationIdResult, { operation_id }) } @@ -901,10 +1019,11 @@ fn abi_local_execution( arg_offset, |handler, req: LocalExecutionRequest| { let remaining_gas = handler.get_remaining_gas(); - let module = helper_get_tmp_module(handler, req.bytecode, remaining_gas)?; + let module = helper_get_tmp_module(handler, req.bytecode.clone(), remaining_gas)?; + let interface = handler.exec_env.get_interface(); match crate::execution::run_function( - handler.interface, + interface, module, &req.target_function_name, &req.function_arg, @@ -913,6 +1032,23 @@ fn abi_local_execution( ) { Ok(response) => { handler.set_remaining_gas(response.remaining_gas); + + #[cfg(feature = "execution-trace")] + { + if let Some(exec_env) = handler.store_env.data_mut().lock().as_mut() { + exec_env.trace.push(AbiTrace { + name: function_name!().to_string(), + params: vec![ + into_trace_value!(req.bytecode), + into_trace_value!(req.target_function_name), + into_trace_value!(req.function_arg), + ], + return_value: AbiTraceType::ByteArray(response.ret.clone()), + sub_calls: Some(response.trace), + }); + } + } + resp_ok!(LocalExecutionResponse, { data: response.ret }) } Err(e) => resp_err!(e), @@ -931,7 +1067,8 @@ fn abi_caller_has_write_access( store_env, arg_offset, |handler, _req: CallerHasWriteAccessRequest| -> Result { - match handler.interface.caller_has_write_access() { + let interface = handler.exec_env.get_interface(); + match interface.caller_has_write_access() { Ok(has_write_access) => { resp_ok!(CallerHasWriteAccessResult, { has_write_access }) } @@ -964,7 +1101,8 @@ fn abi_function_exists( }; // FIXME set updated value to store_env - let Ok(module) = helper_get_module(handler, bytecode, remaining_gas) else { + let interface = handler.exec_env.get_interface(); + let Ok(module) = helper_get_module(interface, bytecode, remaining_gas) else { return resp_ok!(FunctionExistsResult, { exists: false }); @@ -980,25 +1118,24 @@ fn helper_get_bytecode( handler: &mut super::handler::ABIHandler, address: String, ) -> Result, WasmV1Error> { - let bytecode = handler - .interface - .raw_get_bytecode_for(&address) - .map_err(|err| { - WasmV1Error::RuntimeError(format!( - "Could not get bytecode for address: {}: {}", - address, err - )) - })?; + let interface = handler.exec_env.get_interface(); + let bytecode = interface.raw_get_bytecode_for(&address).map_err(|err| { + WasmV1Error::RuntimeError(format!( + "Could not get bytecode for address: {}: {}", + address, err + )) + })?; Ok(bytecode) } fn helper_get_module( - handler: &mut super::handler::ABIHandler, + // handler: &mut super::handler::ABIHandler, + interface: &dyn Interface, bytecode: Vec, remaining_gas: u64, ) -> Result { - handler - .interface + // let interface = handler.exec_env.get_interface(); + interface .get_module(&bytecode, remaining_gas) .map_err(|err| WasmV1Error::RuntimeError(format!("Could not get module: {}", err))) } @@ -1008,8 +1145,8 @@ fn helper_get_tmp_module( bytecode: Vec, remaining_gas: u64, ) -> Result { - handler - .interface + let interface = handler.exec_env.get_interface(); + interface .get_tmp_module(&bytecode, remaining_gas) .map_err(|err| WasmV1Error::RuntimeError(format!("Could not get module: {}", err))) } @@ -1028,7 +1165,8 @@ fn abi_check_native_amount( return resp_err!("No amount to check"); }; - match handler.interface.check_native_amount_wasmv1(&amount) { + let interface = handler.exec_env.get_interface(); + match interface.check_native_amount_wasmv1(&amount) { Ok(is_valid) => { resp_ok!(CheckNativeAmountResult, { is_valid }) } @@ -1055,10 +1193,8 @@ fn abi_add_native_amount( return resp_err!("No amount2"); }; - match handler - .interface - .add_native_amount_wasmv1(&amount1, &amount2) - { + let interface = handler.exec_env.get_interface(); + match interface.add_native_amount_wasmv1(&amount1, &amount2) { Ok(res) => { resp_ok!(AddNativeAmountResult, { sum: Some(res)}) } @@ -1085,7 +1221,8 @@ fn abi_sub_native_amount( return resp_err!("No right amount"); }; - match handler.interface.sub_native_amount_wasmv1(&left, &right) { + let interface = handler.exec_env.get_interface(); + match interface.sub_native_amount_wasmv1(&left, &right) { Ok(res) => { resp_ok!(SubNativeAmountResult, { difference: Some(res)}) } @@ -1109,10 +1246,8 @@ fn abi_scalar_mul_native_amount( return resp_err!("No amount"); }; - match handler - .interface - .scalar_mul_native_amount_wasmv1(&amount, req.coefficient) - { + let interface = handler.exec_env.get_interface(); + match interface.scalar_mul_native_amount_wasmv1(&amount, req.coefficient) { Ok(res) => { resp_ok!(ScalarMulNativeAmountResult, { product: Some(res) }) } @@ -1136,10 +1271,8 @@ fn abi_scalar_div_rem_native_amount( return resp_err!("No dividend"); }; - match handler - .interface - .scalar_div_rem_native_amount_wasmv1(÷nd, req.divisor) - { + let interface = handler.exec_env.get_interface(); + match interface.scalar_div_rem_native_amount_wasmv1(÷nd, req.divisor) { Ok(res) => { resp_ok!(ScalarDivRemNativeAmountResult, { quotient: Some(res.0), remainder: Some(res.1)}) @@ -1167,10 +1300,8 @@ fn abi_div_rem_native_amount( return resp_err!("No divisor"); }; - match handler - .interface - .div_rem_native_amount_wasmv1(÷nd, &divisor) - { + let interface = handler.exec_env.get_interface(); + match interface.div_rem_native_amount_wasmv1(÷nd, &divisor) { Ok(res) => { resp_ok!(DivRemNativeAmountResult, { quotient: res.0, remainder: Some(res.1)}) @@ -1195,7 +1326,8 @@ fn abi_native_amount_to_string( return resp_err!("No amount to convert"); }; - match handler.interface.native_amount_to_string_wasmv1(&amount) { + let interface = handler.exec_env.get_interface(); + match interface.native_amount_to_string_wasmv1(&amount) { Ok(converted_amount) => { resp_ok!(NativeAmountToStringResult, { converted_amount }) } @@ -1215,10 +1347,8 @@ fn abi_native_amount_from_string( store_env, arg_offset, |handler, req: NativeAmountFromStringRequest| -> Result { - let Ok(amount) = handler - .interface - .native_amount_from_str_wasmv1(&req.to_convert) - else { + let interface = handler.exec_env.get_interface(); + let Ok(amount) = interface.native_amount_from_str_wasmv1(&req.to_convert) else { return resp_err!("Invalid amount"); }; @@ -1237,10 +1367,8 @@ fn abi_base58_check_to_bytes( store_env, arg_offset, |handler, req: Base58CheckToBytesRequest| -> Result { - match handler - .interface - .base58_check_to_bytes_wasmv1(&req.base58_check) - { + let interface = handler.exec_env.get_interface(); + match interface.base58_check_to_bytes_wasmv1(&req.base58_check) { Ok(bytes) => resp_ok!(Base58CheckToBytesResult, { bytes }), Err(e) => resp_err!(e), } @@ -1258,7 +1386,8 @@ fn abi_bytes_to_base58_check( store_env, arg_offset, |handler, req: BytesToBase58CheckRequest| -> Result { - let base58_check = handler.interface.bytes_to_base58_check_wasmv1(&req.bytes); + let interface = handler.exec_env.get_interface(); + let base58_check = interface.bytes_to_base58_check_wasmv1(&req.bytes); resp_ok!(BytesToBase58CheckResult, { base58_check }) }, ) @@ -1274,10 +1403,8 @@ fn abi_compare_address( store_env, arg_offset, |handler, req: CompareAddressRequest| -> Result { - match handler - .interface - .compare_address_wasmv1(&req.left, &req.right) - { + let interface = handler.exec_env.get_interface(); + match interface.compare_address_wasmv1(&req.left, &req.right) { Ok(result) => { resp_ok!(CompareAddressResult, { result : result.into() }) } @@ -1300,10 +1427,8 @@ fn abi_compare_native_amount( let (Some(left), Some(right)) = (req.left, req.right) else { return resp_err!("Either left or right argument is none"); }; - match handler - .interface - .compare_native_amount_wasmv1(&left, &right) - { + let interface = handler.exec_env.get_interface(); + match interface.compare_native_amount_wasmv1(&left, &right) { Ok(result) => { resp_ok!(CompareNativeAmountResult, { result : result.into() }) } @@ -1326,7 +1451,8 @@ fn abi_compare_native_time( let (Some(left), Some(right)) = (req.left, req.right) else { return resp_err!("Either left or right argument is none"); }; - match handler.interface.compare_native_time_wasmv1(&left, &right) { + let interface = handler.exec_env.get_interface(); + match interface.compare_native_time_wasmv1(&left, &right) { Ok(result) => { resp_ok!(CompareNativeTimeResult, { result : result.into() }) } @@ -1346,10 +1472,8 @@ fn abi_compare_pub_key( store_env, arg_offset, |handler, req: ComparePubKeyRequest| -> Result { - match handler - .interface - .compare_pub_key_wasmv1(&req.left, &req.right) - { + let interface = handler.exec_env.get_interface(); + match interface.compare_pub_key_wasmv1(&req.left, &req.right) { Ok(result) => { resp_ok!(ComparePubKeyResult, { result : result.into() }) } @@ -1369,7 +1493,8 @@ fn abi_check_address( store_env, arg_offset, |handler, req: CheckAddressRequest| -> Result { - match handler.interface.check_address_wasmv1(&req.to_check) { + let interface = handler.exec_env.get_interface(); + match interface.check_address_wasmv1(&req.to_check) { Ok(is_valid) => { resp_ok!(CheckAddressResult, { is_valid }) } @@ -1389,7 +1514,8 @@ fn abi_check_pubkey( store_env, arg_offset, |handler, req: CheckPubKeyRequest| -> Result { - match handler.interface.check_pubkey_wasmv1(&req.to_check) { + let interface = handler.exec_env.get_interface(); + match interface.check_pubkey_wasmv1(&req.to_check) { Ok(is_valid) => resp_ok!(CheckPubKeyResult, { is_valid }), Err(e) => resp_err!(e), } @@ -1407,7 +1533,8 @@ fn abi_check_signature( store_env, arg_offset, |handler, req: CheckSigRequest| -> Result { - match handler.interface.check_signature_wasmv1(&req.to_check) { + let interface = handler.exec_env.get_interface(); + match interface.check_signature_wasmv1(&req.to_check) { Ok(is_valid) => resp_ok!(CheckSigResult, { is_valid }), Err(e) => resp_err!(e), } @@ -1425,7 +1552,8 @@ fn abi_get_address_category( store_env, arg_offset, |handler, req: GetAddressCategoryRequest| -> Result { - match handler.interface.get_address_category_wasmv1(&req.address) { + let interface = handler.exec_env.get_interface(); + match interface.get_address_category_wasmv1(&req.address) { Ok(res) => { resp_ok!(GetAddressCategoryResult, { category: res.into()}) } @@ -1445,7 +1573,8 @@ fn abi_get_address_version( store_env, arg_offset, |handler, req: GetAddressVersionRequest| -> Result { - match handler.interface.get_address_version_wasmv1(&req.address) { + let interface = handler.exec_env.get_interface(); + match interface.get_address_version_wasmv1(&req.address) { Ok(version) => resp_ok!(GetAddressVersionResult, { version }), Err(e) => resp_err!(e), } @@ -1463,7 +1592,8 @@ fn abi_get_pubkey_version( store_env, arg_offset, |handler, req: GetPubKeyVersionRequest| -> Result { - match handler.interface.get_pubkey_version_wasmv1(&req.pub_key) { + let interface = handler.exec_env.get_interface(); + match interface.get_pubkey_version_wasmv1(&req.pub_key) { Ok(version) => resp_ok!(GetPubKeyVersionResult, { version }), Err(e) => resp_err!(e), } @@ -1481,10 +1611,8 @@ fn abi_get_signature_version( store_env, arg_offset, |handler, req: GetSignatureVersionRequest| -> Result { - match handler - .interface - .get_signature_version_wasmv1(&req.signature) - { + let interface = handler.exec_env.get_interface(); + match interface.get_signature_version_wasmv1(&req.signature) { Ok(version) => resp_ok!(GetSignatureVersionResult, { version }), Err(e) => resp_err!(e), } @@ -1509,10 +1637,8 @@ fn abi_checked_add_native_time( return resp_err!("No time2"); }; - match handler - .interface - .checked_add_native_time_wasmv1(&time1, &time2) - { + let interface = handler.exec_env.get_interface(); + match interface.checked_add_native_time_wasmv1(&time1, &time2) { Ok(res) => { resp_ok!(CheckedAddNativeTimeResult, { sum: Some(res)}) } @@ -1539,10 +1665,8 @@ fn abi_checked_sub_native_time( return resp_err!("No right time"); }; - match handler - .interface - .checked_sub_native_time_wasmv1(&left, &right) - { + let interface = handler.exec_env.get_interface(); + match interface.checked_sub_native_time_wasmv1(&left, &right) { Ok(res) => { resp_ok!(CheckedSubNativeTimeResult, { difference: Some(res)}) } @@ -1566,10 +1690,8 @@ fn abi_checked_mul_native_time( return resp_err!("No time"); }; - match handler - .interface - .checked_mul_native_time_wasmv1(&time, req.coefficient) - { + let interface = handler.exec_env.get_interface(); + match interface.checked_mul_native_time_wasmv1(&time, req.coefficient) { Ok(res) => { resp_ok!(CheckedScalarMulNativeTimeResult, { product: Some(res)}) } @@ -1593,10 +1715,8 @@ fn abi_checked_scalar_div_native_time( return resp_err!("No dividend"); }; - match handler - .interface - .checked_scalar_div_native_time_wasmv1(÷nd, req.divisor) - { + let interface = handler.exec_env.get_interface(); + match interface.checked_scalar_div_native_time_wasmv1(÷nd, req.divisor) { Ok(res) => { resp_ok!(CheckedScalarDivRemNativeTimeResult, { quotient: Some(res.0), remainder: Some(res.1)}) @@ -1624,10 +1744,8 @@ fn abi_checked_div_native_time( return resp_err!("No divisor"); }; - match handler - .interface - .checked_div_native_time_wasmv1(÷nd, &divisor) - { + let interface = handler.exec_env.get_interface(); + match interface.checked_div_native_time_wasmv1(÷nd, &divisor) { Ok(res) => { resp_ok!(CheckedDivRemNativeTimeResult, { quotient: res.0, remainder: Some(res.1)}) @@ -1648,10 +1766,8 @@ pub fn abi_verify_signature( store_env, arg_offset, |handler, req: VerifySigRequest| -> Result { - match handler - .interface - .signature_verify(&req.message, &req.sig, &req.pub_key) - { + let interface = handler.exec_env.get_interface(); + match interface.signature_verify(&req.message, &req.sig, &req.pub_key) { Ok(is_verified) => { resp_ok!(VerifySigResult, { is_verified }) } @@ -1671,7 +1787,8 @@ pub fn abi_chain_id( store_env, arg_offset, |handler, _req: ChainIdRequest| -> Result { - match handler.interface.chain_id() { + let interface = handler.exec_env.get_interface(); + match interface.chain_id() { Ok(chain_id) => { resp_ok!(ChainIdResult, { id: chain_id }) } diff --git a/src/wasmv1_execution/abi/handler.rs b/src/wasmv1_execution/abi/handler.rs index c5993d7b..1d0ae960 100644 --- a/src/wasmv1_execution/abi/handler.rs +++ b/src/wasmv1_execution/abi/handler.rs @@ -1,5 +1,5 @@ use super::super::env::{ABIEnv, ExecutionEnv}; -use crate::{wasmv1_execution::WasmV1Error, GasCosts, Interface}; +use crate::{wasmv1_execution::WasmV1Error, GasCosts}; use std::io::Cursor; use wasmer::FunctionEnvMut; @@ -18,17 +18,15 @@ where { // get environment and interface let env_mutex = store_env.data().clone(); - let env_lock = env_mutex.lock(); - let exec_env = env_lock.as_ref().ok_or_else(|| { + let mut env_lock = env_mutex.lock(); + let exec_env = env_lock.as_mut().ok_or_else(|| { WasmV1Error::InstanciationError("ABIs cannot be called at initialization time.".into()) })?; - let interface = exec_env.get_interface(); // create handler let mut handler = ABIHandler { store_env: &mut store_env, exec_env, - interface, }; // apply gas cost @@ -61,17 +59,16 @@ where { // get environment and interface let env_mutex = store_env.data().clone(); - let env_lock = env_mutex.lock(); - let exec_env = env_lock.as_ref().ok_or_else(|| { + let mut env_lock = env_mutex.lock(); + let exec_env = env_lock.as_mut().ok_or_else(|| { WasmV1Error::InstanciationError("ABIs cannot be called at initialization time.".into()) })?; - let interface = exec_env.get_interface(); + // let interface = exec_env.get_interface_mut(); // create handler let mut handler = ABIHandler { store_env: &mut store_env, exec_env, - interface, }; // apply gas cost @@ -92,9 +89,8 @@ where /// A helper structure to handle ABI calls pub struct ABIHandler<'a, 'b> { - store_env: &'b mut FunctionEnvMut<'a, ABIEnv>, - exec_env: &'b ExecutionEnv, - pub interface: &'b dyn Interface, + pub(crate) store_env: &'b mut FunctionEnvMut<'a, ABIEnv>, + pub(crate) exec_env: &'b mut ExecutionEnv, } impl<'a, 'b> ABIHandler<'a, 'b> { diff --git a/src/wasmv1_execution/env.rs b/src/wasmv1_execution/env.rs index ed7546c9..5cc9f08a 100644 --- a/src/wasmv1_execution/env.rs +++ b/src/wasmv1_execution/env.rs @@ -8,6 +8,9 @@ use wasmer::{AsStoreMut, AsStoreRef, Imports, Instance, InstantiationError, Type use wasmer_middlewares::metering::{self, MeteringPoints}; use wasmer_types::TrapCode; +#[cfg(feature = "execution-trace")] +use crate::AbiTrace; + pub type ABIEnv = Arc>>; /// Execution environment for ABIs. @@ -24,6 +27,8 @@ pub struct ExecutionEnv { ffi: Ffi, /// Gas cost of instance creation init_gas_cost: u64, + #[cfg(feature = "execution-trace")] + pub trace: Vec, } /// ABI environment giving ABIs access to the interface, gas costs and memory. @@ -87,6 +92,8 @@ impl ExecutionEnv { instance, ffi, init_gas_cost, + #[cfg(feature = "execution-trace")] + trace: Default::default(), }) } diff --git a/src/wasmv1_execution/mod.rs b/src/wasmv1_execution/mod.rs index ee61f8f7..b2b297ca 100644 --- a/src/wasmv1_execution/mod.rs +++ b/src/wasmv1_execution/mod.rs @@ -314,6 +314,8 @@ pub(crate) fn exec_wasmv1_module( ret, remaining_gas, init_gas_cost, + #[cfg(feature = "execution-trace")] + trace: execution_env.trace.clone(), }, gc_result, ))