From 73ad673404a85325d2c0e91d182bba5613594390 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Sat, 1 Sep 2018 13:02:10 +0300 Subject: [PATCH] Contract runtime polishing (#601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rename ext_transfer → ext_call There are several tests where gas was changed. This is due to shrinking the name and consequently a size of contracts. * Fix macros Specifically, make fixes that allows to use a function without arguments (apart from ctx). For example: ```rust ext_return_42(ctx) -> u32 => { Ok(42) } ``` Also, add impl ConvertibleToWasm for u64. * Implement passing input data buffer from top-level This allows the transaction initiator to pass some input data that can be accessed from the executed contract. However, this only works for the top-level contract. The contract still cannot pass input data to the callee contract. The reason for that is that API doesn't support this presently. This will be addressed in the following commits. * Input data passing thru `ext_call` `ext_call` now takes a buffer (or more specifically, pointer to the start of and size of a buffer). This allows contracts to pass input data while calling into other contracts. * Input data passing thru `ext_create` Same as previous commit but for `ext_create`. * Trap on decoding error instead of panicking * Don't cascade failure in ext_call Instead, if callee doesn't complete successfuly, return a non-zero status code. This will prevent cascading traps up to the top-level. Due to this some tests were altered so that they now expect successful transfer instead of a failure. * Don't cascade failure in ext_create Same as previous commit but for ext_create. * Specify how much gas to allot for a call * Specify how much gas to allot for a create * Update tests.rs --- .../substrate/runtime/contract/src/exec.rs | 6 +- .../substrate/runtime/contract/src/lib.rs | 1 - .../substrate/runtime/contract/src/tests.rs | 170 +++++++++++---- .../runtime/contract/src/vm/env_def/macros.rs | 10 +- .../runtime/contract/src/vm/env_def/mod.rs | 117 ++++++++--- .../substrate/runtime/contract/src/vm/mod.rs | 198 ++++++++++++++++-- 6 files changed, 408 insertions(+), 94 deletions(-) diff --git a/substrate/substrate/runtime/contract/src/exec.rs b/substrate/substrate/runtime/contract/src/exec.rs index 4779e959c1..f7673d49b0 100644 --- a/substrate/substrate/runtime/contract/src/exec.rs +++ b/substrate/substrate/runtime/contract/src/exec.rs @@ -47,7 +47,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { dest: T::AccountId, value: T::Balance, gas_meter: &mut GasMeter, - _data: &[u8], + data: &[u8], ) -> Result { if self.depth == >::get() as usize { return Err("reached maximum depth, cannot make a call"); @@ -82,6 +82,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { let exec_result = if !dest_code.is_empty() { vm::execute( &dest_code, + data, &mut CallContext { ctx: &mut nested, _caller: caller, @@ -111,7 +112,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { endowment: T::Balance, gas_meter: &mut GasMeter, ctor: &[u8], - _data: &[u8], + data: &[u8], ) -> Result, &'static str> { if self.depth == >::get() as usize { return Err("reached maximum depth, cannot create"); @@ -150,6 +151,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { let exec_result = { vm::execute( ctor, + data, &mut CallContext { ctx: &mut nested, _caller: caller, diff --git a/substrate/substrate/runtime/contract/src/lib.rs b/substrate/substrate/runtime/contract/src/lib.rs index 9f77b07fb8..ec78584f2b 100644 --- a/substrate/substrate/runtime/contract/src/lib.rs +++ b/substrate/substrate/runtime/contract/src/lib.rs @@ -31,7 +31,6 @@ //! //! Failures are typically not cascading. That, for example, means that if contract A calls B and B errors //! somehow, then A can decide if it should proceed or error. -//! TODO: That is not the case now, since call/create externalities traps on any error now. //! //! # Interaction with the system //! diff --git a/substrate/substrate/runtime/contract/src/tests.rs b/substrate/substrate/runtime/contract/src/tests.rs index d12b137d41..191536cba8 100644 --- a/substrate/substrate/runtime/contract/src/tests.rs +++ b/substrate/substrate/runtime/contract/src/tests.rs @@ -125,15 +125,28 @@ impl ExtBuilder { const CODE_TRANSFER: &str = r#" (module - ;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) - (import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32))) + ;; ext_call( + ;; callee_ptr: u32, + ;; callee_len: u32, + ;; gas: u64, + ;; value_ptr: u32, + ;; value_len: u32, + ;; input_data_ptr: u32, + ;; input_data_len: u32 + ;; ) -> u32 + (import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) (func (export "call") - (call $ext_transfer - (i32.const 4) ;; Pointer to "Transfer to" address. - (i32.const 8) ;; Length of "Transfer to" address. - (i32.const 12) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. + (drop + (call $ext_call + (i32.const 4) ;; Pointer to "callee" address. + (i32.const 8) ;; Length of "callee" address. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 12) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 0) ;; Length of input data buffer + ) ) ) ;; Destination AccountId to transfer the funds. @@ -165,10 +178,10 @@ fn contract_transfer() { assert_eq!( Balances::free_balance(&0), // 3 - value sent with the transaction - // 2 * 6 - gas used by the contract (6) multiplied by gas price (2) + // 2 * 10 - gas used by the contract (10) multiplied by gas price (2) // 2 * 135 - base gas fee for call (by transaction) // 2 * 135 - base gas fee for call (by the contract) - 100_000_000 - 3 - (2 * 6) - (2 * 135) - (2 * 135), + 100_000_000 - 3 - (2 * 10) - (2 * 135) - (2 * 135), ); assert_eq!( Balances::free_balance(&1), @@ -195,20 +208,20 @@ fn contract_transfer_oog() { Balances::set_free_balance(&1, 11); Balances::increase_total_stake_by(11); - assert_err!( - Contract::call(&0, 1, 3, 276, Vec::new()), - "vm execute returned error while call" - ); + assert_ok!(Contract::call(&0, 1, 3, 135 + 135 + 7, Vec::new())); assert_eq!( Balances::free_balance(&0), // 3 - value sent with the transaction - // 2 * 6 - gas used by the contract (6) multiplied by gas price (2) + // 2 * 7 - gas used by the contract (7) multiplied by gas price (2) // 2 * 135 - base gas fee for call (by transaction) // 2 * 135 - base gas fee for call (by contract) - 100_000_000 - (2 * 6) - (2 * 135) - (2 * 135), + 100_000_000 - 3 - (2 * 7) - (2 * 135) - (2 * 135), ); - assert_eq!(Balances::free_balance(&1), 11); + + // Transaction level transfer should succeed. + assert_eq!(Balances::free_balance(&1), 14); + // But `ext_call` should not. assert_eq!(Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 0); }); } @@ -227,20 +240,17 @@ fn contract_transfer_max_depth() { Balances::set_free_balance(&CONTRACT_SHOULD_TRANSFER_TO, 11); Balances::increase_total_stake_by(11); - assert_err!( - Contract::call(&0, CONTRACT_SHOULD_TRANSFER_TO, 3, 100_000, Vec::new()), - "vm execute returned error while call" - ); + assert_ok!(Contract::call(&0, CONTRACT_SHOULD_TRANSFER_TO, 3, 100_000, Vec::new())); assert_eq!( Balances::free_balance(&0), // 3 - value sent with the transaction - // 2 * 6 * 100 - gas used by the contract (6) multiplied by gas price (2) - // multiplied by max depth (100). + // 2 * 10 * 100 - gas used by the contract (10) multiplied by gas price (2) + // multiplied by max depth (100). // 2 * 135 * 100 - base gas fee for call (by transaction) multiplied by max depth (100). - 100_000_000 - (2 * 135 * 100) - (2 * 6 * 100), + 100_000_000 - 3 - (2 * 10 * 100) - (2 * 135 * 100), ); - assert_eq!(Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 11); + assert_eq!(Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 14); }); } @@ -290,15 +300,28 @@ fn code_create(constructor: &[u8]) -> String { format!( r#" (module - ;; ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32) - (import "env" "ext_create" (func $ext_create (param i32 i32 i32 i32))) + ;; ext_create( + ;; code_ptr: u32, + ;; code_len: u32, + ;; gas: u64, + ;; value_ptr: u32, + ;; value_len: u32, + ;; input_data_ptr: u32, + ;; input_data_len: u32, + ;; ) -> u32 + (import "env" "ext_create" (func $ext_create (param i32 i32 i64 i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) (func (export "call") - (call $ext_create - (i32.const 12) ;; Pointer to `code` - (i32.const {code_len}) ;; Length of `code` - (i32.const 4) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer + (drop + (call $ext_create + (i32.const 12) ;; Pointer to `code` + (i32.const {code_len}) ;; Length of `code` + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 4) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 0) ;; Length of input data buffer + ) ) ) ;; Amount of value to transfer. @@ -337,12 +360,12 @@ fn contract_create() { ); // 11 - value sent with the transaction - // 2 * 128 - gas spent by the deployer contract (128) multiplied by gas price (2) + // 2 * 139 - gas spent by the deployer contract (139) multiplied by gas price (2) // 2 * 135 - base gas fee for call (top level) // 2 * 175 - base gas fee for create (by contract) // ((21 / 2) * 2) - price per account creation let expected_gas_after_create = - 100_000_000 - 11 - (2 * 128) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); + 100_000_000 - 11 - (2 * 139) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); assert_eq!(Balances::free_balance(&0), expected_gas_after_create); assert_eq!(Balances::free_balance(&1), 8); assert_eq!(Balances::free_balance(&derived_address), 3); @@ -353,10 +376,10 @@ fn contract_create() { assert_eq!( Balances::free_balance(&0), // 22 - value sent with the transaction - // (2 * 6) - gas used by the contract + // (2 * 10) - gas used by the contract // (2 * 135) - base gas fee for call (top level) // (2 * 135) - base gas fee for call (by transfer contract) - expected_gas_after_create - 22 - (2 * 6) - (2 * 135) - (2 * 135), + expected_gas_after_create - 22 - (2 * 10) - (2 * 135) - (2 * 135), ); assert_eq!(Balances::free_balance(&derived_address), 22 - 3); assert_eq!(Balances::free_balance(&9), 36); @@ -388,12 +411,12 @@ fn top_level_create() { )); // 11 - value sent with the transaction - // (3 * 122) - gas spent by the ctor + // (3 * 129) - gas spent by the ctor // (3 * 175) - base gas fee for create (175) (top level) multipled by gas price (3) // ((21 / 3) * 3) - price for contract creation assert_eq!( Balances::free_balance(&0), - 100_000_000 - 11 - (3 * 122) - (3 * 175) - ((21 / 3) * 3) + 100_000_000 - 11 - (3 * 129) - (3 * 175) - ((21 / 3) * 3) ); assert_eq!(Balances::free_balance(&derived_address), 30 + 11); @@ -569,3 +592,76 @@ fn block_gas_limit() { }, ); } + +const CODE_INPUT_DATA: &'static str = r#" +(module + (import "env" "ext_input_size" (func $ext_input_size (result i32))) + (import "env" "ext_input_copy" (func $ext_input_copy (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func (export "call") + (block $fail + ;; fail if ext_input_size != 4 + (br_if $fail + (i32.ne + (i32.const 4) + (call $ext_input_size) + ) + ) + + (call $ext_input_copy + (i32.const 0) + (i32.const 0) + (i32.const 4) + ) + + + (br_if $fail + (i32.ne + (i32.load8_u (i32.const 0)) + (i32.const 0) + ) + ) + (br_if $fail + (i32.ne + (i32.load8_u (i32.const 1)) + (i32.const 1) + ) + ) + (br_if $fail + (i32.ne + (i32.load8_u (i32.const 2)) + (i32.const 2) + ) + ) + (br_if $fail + (i32.ne + (i32.load8_u (i32.const 3)) + (i32.const 3) + ) + ) + + (return) + ) + unreachable + ) +) +"#; + +#[test] +fn input_data() { + let code_input_data = wabt::wat2wasm(CODE_INPUT_DATA).unwrap(); + with_externalities( + &mut ExtBuilder::default().build(), + || { + >::insert(1, code_input_data.to_vec()); + + Balances::set_free_balance(&0, 100_000_000); + Balances::increase_total_stake_by(100_000_000); + + assert_ok!(Contract::call(&0, 1, 0, 50_000, vec![0, 1, 2, 3])); + + // all asserts are made within contract code itself. + }, + ); +} diff --git a/substrate/substrate/runtime/contract/src/vm/env_def/macros.rs b/substrate/substrate/runtime/contract/src/vm/env_def/macros.rs index 1472b407d4..b6b2cdb2ba 100644 --- a/substrate/substrate/runtime/contract/src/vm/env_def/macros.rs +++ b/substrate/substrate/runtime/contract/src/vm/env_def/macros.rs @@ -21,7 +21,7 @@ #[macro_export] macro_rules! convert_args { - () => ([]); + () => (vec![]); ( $( $t:ty ),* ) => ( vec![ $( { use $crate::vm::env_def::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] ); } @@ -90,7 +90,7 @@ macro_rules! unmarshall_then_body_then_marshall { unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*) }); let r = body()?; - return Ok(ReturnValue::Value({ use $crate::vm::env_def::ConvertibleToWasm; r.to_typed_value() })) + return Ok($crate::sandbox::ReturnValue::Value({ use $crate::vm::env_def::ConvertibleToWasm; r.to_typed_value() })) }); ( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({ let body = $crate::vm::env_def::macros::constrain_closure::<(), _>(|| { @@ -103,7 +103,7 @@ macro_rules! unmarshall_then_body_then_marshall { #[macro_export] macro_rules! define_func { - ( < E: $ext_ty:tt > $name:ident ( $ctx: ident, $($names:ident : $params:ty),*) $(-> $returns:ty)* => $body:tt ) => { + ( < E: $ext_ty:tt > $name:ident ( $ctx: ident $(, $names:ident : $params:ty)*) $(-> $returns:ty)* => $body:tt ) => { fn $name< E: $ext_ty >( $ctx: &mut $crate::vm::Runtime, args: &[$crate::sandbox::TypedValue], @@ -129,7 +129,7 @@ macro_rules! define_func { /// and reject the code if any imported function has a mismached signature. macro_rules! define_env { ( $init_name:ident , < E: $ext_ty:tt > , - $( $name:ident ( $ctx:ident, $( $names:ident : $params:ty ),* ) + $( $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* ) $( -> $returns:ty )* => $body:tt , )* ) => { pub(crate) fn $init_name() -> HostFunctionSet { @@ -142,7 +142,7 @@ macro_rules! define_env { gen_signature!( ( $( $params ),* ) $( -> $returns )* ), { define_func!( - < E: $ext_ty > $name ( $ctx, $( $names : $params ),* ) $( -> $returns )* => $body + < E: $ext_ty > $name ( $ctx $(, $names : $params )* ) $( -> $returns )* => $body ); $name:: }, diff --git a/substrate/substrate/runtime/contract/src/vm/env_def/mod.rs b/substrate/substrate/runtime/contract/src/vm/env_def/mod.rs index 1222c23290..eb6c16ca8a 100644 --- a/substrate/substrate/runtime/contract/src/vm/env_def/mod.rs +++ b/substrate/substrate/runtime/contract/src/vm/env_def/mod.rs @@ -49,7 +49,23 @@ impl ConvertibleToWasm for u32 { TypedValue::I32(self as i32) } fn from_typed_value(v: TypedValue) -> Option { - v.as_i32().map(|v| v as u32) + match v { + TypedValue::I32(v) => Some(v as u32), + _ => None, + } + } +} +impl ConvertibleToWasm for u64 { + type NativeType = u64; + const VALUE_TYPE: ValueType = ValueType::I64; + fn to_typed_value(self) -> TypedValue { + TypedValue::I64(self as i64) + } + fn from_typed_value(v: TypedValue) -> Option { + match v { + TypedValue::I64(v) => Some(v as u64), + _ => None, + } } } @@ -166,29 +182,34 @@ define_env!(init_env, , Ok(()) }, - // TODO: Rename ext_transfer to ext_call. - // ext_transfer(transfer_to_ptr: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) - ext_transfer(ctx, transfer_to_ptr: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) => { - let mut transfer_to = Vec::new(); - transfer_to.resize(transfer_to_len as usize, 0); - ctx.memory().get(transfer_to_ptr, &mut transfer_to)?; - let transfer_to = - <::T as system::Trait>::AccountId::decode(&mut &transfer_to[..]).unwrap(); + // ext_call(transfer_to_ptr: u32, transfer_to_len: u32, gas: u64, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) + ext_call(ctx, callee_ptr: u32, callee_len: u32, gas: u64, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) -> u32 => { + let mut callee = Vec::new(); + callee.resize(callee_len as usize, 0); + ctx.memory().get(callee_ptr, &mut callee)?; + let callee = + <::T as system::Trait>::AccountId::decode(&mut &callee[..]) + .ok_or_else(|| sandbox::HostError)?; let mut value_buf = Vec::new(); value_buf.resize(value_len as usize, 0); ctx.memory().get(value_ptr, &mut value_buf)?; - let value = BalanceOf::<::T>::decode(&mut &value_buf[..]).unwrap(); + let value = BalanceOf::<::T>::decode(&mut &value_buf[..]) + .ok_or_else(|| sandbox::HostError)?; - // TODO: Read input data from memory. - let input_data = Vec::new(); + let mut input_data = Vec::new(); + input_data.resize(input_data_len as usize, 0u8); + ctx.memory().get(input_data_ptr, &mut input_data)?; - // TODO: Let user to choose how much gas to allocate for the execution. - let nested_gas_limit = ctx.gas_meter.gas_left(); + let nested_gas_limit = if gas == 0 { + ctx.gas_meter.gas_left() + } else { + <<::T as Trait>::Gas as As>::sa(gas) + }; let ext = &mut ctx.ext; let call_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| { match nested_meter { - Some(nested_meter) => ext.call(&transfer_to, value, nested_meter, &input_data), + Some(nested_meter) => ext.call(&callee, value, nested_meter, &input_data), // there is not enough gas to allocate for the nested call. None => Err(()), } @@ -196,28 +217,40 @@ define_env!(init_env, , match call_outcome { // TODO: Find a way how to pass return_data back to the this sandbox. - Ok(CallReceipt { .. }) => Ok(()), - // TODO: Return a status code value that can be handled by the caller instead of a trap. - Err(_) => Err(sandbox::HostError), + Ok(CallReceipt { .. }) => Ok(0), + Err(_) => Ok(1), } }, - // ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32) - ext_create(ctx, code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32) => { + // ext_create(code_ptr: u32, code_len: u32, gas: u64, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) -> u32 + ext_create( + ctx, code_ptr: u32, + code_len: u32, + gas: u64, + value_ptr: u32, + value_len: u32, + input_data_ptr: u32, + input_data_len: u32 + ) -> u32 => { let mut value_buf = Vec::new(); value_buf.resize(value_len as usize, 0); ctx.memory().get(value_ptr, &mut value_buf)?; - let value = BalanceOf::<::T>::decode(&mut &value_buf[..]).unwrap(); + let value = BalanceOf::<::T>::decode(&mut &value_buf[..]) + .ok_or_else(|| sandbox::HostError)?; let mut code = Vec::new(); code.resize(code_len as usize, 0u8); ctx.memory().get(code_ptr, &mut code)?; - // TODO: Read input data from the sandbox. - let input_data = Vec::new(); + let mut input_data = Vec::new(); + input_data.resize(input_data_len as usize, 0u8); + ctx.memory().get(input_data_ptr, &mut input_data)?; - // TODO: Let user to choose how much gas to allocate for the execution. - let nested_gas_limit = ctx.gas_meter.gas_left(); + let nested_gas_limit = if gas == 0 { + ctx.gas_meter.gas_left() + } else { + <<::T as Trait>::Gas as As>::sa(gas) + }; let ext = &mut ctx.ext; let create_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| { match nested_meter { @@ -229,9 +262,8 @@ define_env!(init_env, , match create_outcome { // TODO: Copy an address of the created contract in the sandbox. - Ok(CreateReceipt { .. }) => Ok(()), - // TODO: Return a status code value that can be handled by the caller instead of a trap. - Err(_) => Err(sandbox::HostError), + Ok(CreateReceipt { .. }) => Ok(0), + Err(_) => Ok(1), } }, @@ -249,4 +281,33 @@ define_env!(init_env, , // to the user of this crate. Err(sandbox::HostError) }, + + // ext_input_size() -> u32 + // + // Returns size of an input buffer. + ext_input_size(ctx) -> u32 => { + Ok(ctx.input_data.len() as u32) + }, + + // ext_input_copy(dest_ptr: u32, offset: u32, len: u32) + // + // Copy data from an input buffer starting from `offset` with length `len` into the contract memory. + // The region at which the data should be put is specified by `dest_ptr`. + ext_input_copy(ctx, dest_ptr: u32, offset: u32, len: u32) => { + let offset = offset as usize; + if offset > ctx.input_data.len() { + // Offset can't be larger than input buffer length. + return Err(sandbox::HostError); + } + + // This can't panic since `offset <= ctx.input_data.len()`. + let src = &ctx.input_data[offset..]; + if src.len() != len as usize { + return Err(sandbox::HostError); + } + + ctx.memory().set(dest_ptr, src)?; + + Ok(()) + }, ); diff --git a/substrate/substrate/runtime/contract/src/vm/mod.rs b/substrate/substrate/runtime/contract/src/vm/mod.rs index 5b3318e6fc..0ca9f9e694 100644 --- a/substrate/substrate/runtime/contract/src/vm/mod.rs +++ b/substrate/substrate/runtime/contract/src/vm/mod.rs @@ -121,14 +121,15 @@ enum SpecialTrap { Return(Vec), } -pub(crate) struct Runtime<'a, E: Ext + 'a> { +pub(crate) struct Runtime<'a, 'data, E: Ext + 'a> { ext: &'a mut E, + input_data: &'data [u8], config: &'a Config, memory: sandbox::Memory, gas_meter: &'a mut GasMeter, special_trap: Option, } -impl<'a, E: Ext + 'a> Runtime<'a, E> { +impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> { fn memory(&self) -> &sandbox::Memory { &self.memory } @@ -190,11 +191,10 @@ pub struct ExecutionResult { /// Execute the given code as a contract. pub fn execute<'a, E: Ext>( code: &[u8], + input_data: &[u8], ext: &'a mut E, gas_meter: &mut GasMeter, ) -> Result { - // TODO: Receive data as an argument - let config = Config::default(); let env = env_def::init_env(); @@ -211,6 +211,7 @@ pub fn execute<'a, E: Ext>( let mut runtime = Runtime { ext, + input_data, config: &config, memory, gas_meter, @@ -273,11 +274,14 @@ mod tests { code: Vec, endowment: u64, data: Vec, + gas_left: u64, } #[derive(Debug, PartialEq, Eq)] struct TransferEntry { to: u64, value: u64, + data: Vec, + gas_left: u64, } #[derive(Default)] pub struct MockExt { @@ -299,13 +303,14 @@ mod tests { &mut self, code: &[u8], endowment: u64, - _gas_meter: &mut GasMeter, + gas_meter: &mut GasMeter, data: &[u8], ) -> Result, ()> { self.creates.push(CreateEntry { code: code.to_vec(), endowment, data: data.to_vec(), + gas_left: gas_meter.gas_left(), }); let address = self.next_account_id; self.next_account_id += 1; @@ -316,10 +321,15 @@ mod tests { &mut self, to: &u64, value: u64, - _gas_meter: &mut GasMeter, - _data: &[u8], + gas_meter: &mut GasMeter, + data: &[u8], ) -> Result { - self.transfers.push(TransferEntry { to: *to, value }); + self.transfers.push(TransferEntry { + to: *to, + value, + data: data.to_vec(), + gas_left: gas_meter.gas_left(), + }); // Assume for now that it was just a plain transfer. // TODO: Add tests for different call outcomes. Ok(CallReceipt { @@ -330,27 +340,38 @@ mod tests { const CODE_TRANSFER: &str = r#" (module - ;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) - (import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32))) - + ;; ext_call( + ;; callee_ptr: u32, + ;; callee_len: u32, + ;; gas: u64, + ;; value_ptr: u32, + ;; value_len: u32, + ;; input_data_ptr: u32, + ;; input_data_len: u32 + ;;) -> u32 + (import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) - (func (export "call") - (call $ext_transfer - (i32.const 4) ;; Pointer to "Transfer to" address. - (i32.const 8) ;; Length of "Transfer to" address. - (i32.const 12) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. + (drop + (call $ext_call + (i32.const 4) ;; Pointer to "callee" address. + (i32.const 8) ;; Length of "callee" address. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 12) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 20) ;; Pointer to input data buffer address + (i32.const 4) ;; Length of input data buffer + ) ) ) - ;; Destination AccountId to transfer the funds. ;; Represented by u64 (8 bytes long) in little endian. - (data (i32.const 4) "\02\00\00\00\00\00\00\00") - + (data (i32.const 4) "\09\00\00\00\00\00\00\00") ;; Amount of value to transfer. ;; Represented by u64 (8 bytes long) in little endian. (data (i32.const 12) "\06\00\00\00\00\00\00\00") + + (data (i32.const 20) "\01\02\03\04") ) "#; @@ -361,11 +382,83 @@ mod tests { let mut mock_ext = MockExt::default(); execute( &code_transfer, + &[], &mut mock_ext, &mut GasMeter::with_limit(50_000, 1), ).unwrap(); - assert_eq!(&mock_ext.transfers, &[TransferEntry { to: 2, value: 6 }]); + assert_eq!( + &mock_ext.transfers, + &[TransferEntry { + to: 9, + value: 6, + data: vec![ + 1, 2, 3, 4, + ], + gas_left: 49990, + }] + ); + } + + const CODE_CREATE: &str = r#" +(module + ;; ext_create( + ;; code_ptr: u32, + ;; code_len: u32, + ;; gas: u64, + ;; value_ptr: u32, + ;; value_len: u32, + ;; input_data_ptr: u32, + ;; input_data_len: u32, + ;; ) -> u32 + (import "env" "ext_create" (func $ext_create (param i32 i32 i64 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call") + (drop + (call $ext_create + (i32.const 12) ;; Pointer to `code` + (i32.const 8) ;; Length of `code` + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 4) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer + (i32.const 20) ;; Pointer to input data buffer address + (i32.const 4) ;; Length of input data buffer + ) + ) + ) + ;; Amount of value to transfer. + ;; Represented by u64 (8 bytes long) in little endian. + (data (i32.const 4) "\03\00\00\00\00\00\00\00") + ;; Embedded wasm code. + (data (i32.const 12) "\00\61\73\6d\01\00\00\00") + ;; Input data to pass to the contract being created. + (data (i32.const 20) "\01\02\03\04") +) +"#; + + #[test] + fn contract_create() { + let code_create = wabt::wat2wasm(CODE_CREATE).unwrap(); + + let mut mock_ext = MockExt::default(); + execute( + &code_create, + &[], + &mut mock_ext, + &mut GasMeter::with_limit(50_000, 1), + ).unwrap(); + + assert_eq!( + &mock_ext.creates, + &[CreateEntry { + code: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], + endowment: 3, + data: vec![ + 1, 2, 3, 4, + ], + gas_left: 49990, + }] + ); } const CODE_MEM: &str = r#" @@ -388,10 +481,73 @@ mod tests { assert_matches!( execute( &code_mem, + &[], &mut mock_ext, &mut GasMeter::with_limit(100_000, 1) ), Err(_) ); } + + const CODE_TRANSFER_LIMITED_GAS: &str = r#" +(module + ;; ext_call( + ;; callee_ptr: u32, + ;; callee_len: u32, + ;; gas: u64, + ;; value_ptr: u32, + ;; value_len: u32, + ;; input_data_ptr: u32, + ;; input_data_len: u32 + ;;) -> u32 + (import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call") + (drop + (call $ext_call + (i32.const 4) ;; Pointer to "callee" address. + (i32.const 8) ;; Length of "callee" address. + (i64.const 228) ;; How much gas to devote for the execution. + (i32.const 12) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 20) ;; Pointer to input data buffer address + (i32.const 4) ;; Length of input data buffer + ) + ) + ) + ;; Destination AccountId to transfer the funds. + ;; Represented by u64 (8 bytes long) in little endian. + (data (i32.const 4) "\09\00\00\00\00\00\00\00") + ;; Amount of value to transfer. + ;; Represented by u64 (8 bytes long) in little endian. + (data (i32.const 12) "\06\00\00\00\00\00\00\00") + + (data (i32.const 20) "\01\02\03\04") +) +"#; + + #[test] + fn contract_call_limited_gas() { + let code_transfer = wabt::wat2wasm(CODE_TRANSFER_LIMITED_GAS).unwrap(); + + let mut mock_ext = MockExt::default(); + execute( + &code_transfer, + &[], + &mut mock_ext, + &mut GasMeter::with_limit(50_000, 1), + ).unwrap(); + + assert_eq!( + &mock_ext.transfers, + &[TransferEntry { + to: 9, + value: 6, + data: vec![ + 1, 2, 3, 4, + ], + gas_left: 228, + }] + ); + } }