From 74896232c9871dc30939c795716773d1a81ff225 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Mon, 13 Aug 2018 14:37:27 +0200 Subject: [PATCH 01/11] =?UTF-8?q?Rename=20ext=5Ftransfer=20=E2=86=92=20ext?= =?UTF-8?q?=5Fcall?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are several tests where gas was changed. This is due to shrinking the name and consequently a size of contracts. --- substrate/runtime/contract/src/tests.rs | 14 +++++++------- .../runtime/contract/src/vm/env_def/mod.rs | 17 ++++++++--------- substrate/runtime/contract/src/vm/mod.rs | 6 +++--- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/substrate/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs index 5f1014243a046..f456cbbe96a92 100644 --- a/substrate/runtime/contract/src/tests.rs +++ b/substrate/runtime/contract/src/tests.rs @@ -165,11 +165,11 @@ 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, value_ptr: u32, value_len: u32) + (import "env" "ext_call" (func $ext_call (param i32 i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func (export "call") - (call $ext_transfer + (call $ext_call (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 @@ -377,12 +377,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 * 124 - gas spent by the deployer contract (124) 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 * 124) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); assert_eq!(Staking::free_balance(&0), expected_gas_after_create); assert_eq!(Staking::free_balance(&1), 8); assert_eq!(Staking::free_balance(&derived_address), 3); @@ -428,12 +428,12 @@ fn top_level_create() { )); // 11 - value sent with the transaction - // (3 * 122) - gas spent by the ctor + // (3 * 118) - 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!( Staking::free_balance(&0), - 100_000_000 - 11 - (3 * 122) - (3 * 175) - ((21 / 3) * 3) + 100_000_000 - 11 - (3 * 118) - (3 * 175) - ((21 / 3) * 3) ); assert_eq!(Staking::free_balance(&derived_address), 30 + 11); diff --git a/substrate/runtime/contract/src/vm/env_def/mod.rs b/substrate/runtime/contract/src/vm/env_def/mod.rs index 1222c23290428..5502419d5bac0 100644 --- a/substrate/runtime/contract/src/vm/env_def/mod.rs +++ b/substrate/runtime/contract/src/vm/env_def/mod.rs @@ -166,14 +166,13 @@ 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, value_ptr: u32, value_len: u32) + ext_call(ctx, callee_ptr: u32, callee_len: u32, value_ptr: u32, value_len: 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[..]).unwrap(); let mut value_buf = Vec::new(); value_buf.resize(value_len as usize, 0); @@ -188,7 +187,7 @@ define_env!(init_env, , 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(()), } diff --git a/substrate/runtime/contract/src/vm/mod.rs b/substrate/runtime/contract/src/vm/mod.rs index ec26fb2daabfb..968e3203b9eaf 100644 --- a/substrate/runtime/contract/src/vm/mod.rs +++ b/substrate/runtime/contract/src/vm/mod.rs @@ -332,13 +332,13 @@ 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, value_ptr: u32, value_len: u32) + (import "env" "ext_call" (func $ext_call (param i32 i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func (export "call") - (call $ext_transfer + (call $ext_call (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 From c6e7c54ffe9d91851b5b452ae9e20b05cfff3194 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Mon, 13 Aug 2018 15:16:02 +0200 Subject: [PATCH 02/11] 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. --- .../runtime/contract/src/vm/env_def/macros.rs | 10 +++++----- .../runtime/contract/src/vm/env_def/mod.rs | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/substrate/runtime/contract/src/vm/env_def/macros.rs b/substrate/runtime/contract/src/vm/env_def/macros.rs index 1472b407d4b37..b6b2cdb2ba344 100644 --- a/substrate/runtime/contract/src/vm/env_def/macros.rs +++ b/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/runtime/contract/src/vm/env_def/mod.rs b/substrate/runtime/contract/src/vm/env_def/mod.rs index 5502419d5bac0..9d948a891c884 100644 --- a/substrate/runtime/contract/src/vm/env_def/mod.rs +++ b/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, + } } } From 23c02a72ebcdf0e2abdd3c0fe5dc1ee114c11b52 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Mon, 13 Aug 2018 17:48:13 +0200 Subject: [PATCH 03/11] 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. --- substrate/runtime/contract/src/exec.rs | 6 +- substrate/runtime/contract/src/tests.rs | 73 +++++++++++++++++++ .../runtime/contract/src/vm/env_def/mod.rs | 29 ++++++++ substrate/runtime/contract/src/vm/mod.rs | 11 ++- 4 files changed, 113 insertions(+), 6 deletions(-) diff --git a/substrate/runtime/contract/src/exec.rs b/substrate/runtime/contract/src/exec.rs index a62e2ce97f972..3e46c375429b3 100644 --- a/substrate/runtime/contract/src/exec.rs +++ b/substrate/runtime/contract/src/exec.rs @@ -48,7 +48,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"); @@ -83,6 +83,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, @@ -112,7 +113,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"); @@ -151,6 +152,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/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs index f456cbbe96a92..dc0f3799fafe6 100644 --- a/substrate/runtime/contract/src/tests.rs +++ b/substrate/runtime/contract/src/tests.rs @@ -609,3 +609,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()); + + Staking::set_free_balance(&0, 100_000_000); + Staking::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/runtime/contract/src/vm/env_def/mod.rs b/substrate/runtime/contract/src/vm/env_def/mod.rs index 9d948a891c884..e18458fc2a207 100644 --- a/substrate/runtime/contract/src/vm/env_def/mod.rs +++ b/substrate/runtime/contract/src/vm/env_def/mod.rs @@ -264,4 +264,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/runtime/contract/src/vm/mod.rs b/substrate/runtime/contract/src/vm/mod.rs index 968e3203b9eaf..4f4e576213ee3 100644 --- a/substrate/runtime/contract/src/vm/mod.rs +++ b/substrate/runtime/contract/src/vm/mod.rs @@ -123,14 +123,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 } @@ -192,11 +193,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(); @@ -213,6 +213,7 @@ pub fn execute<'a, E: Ext>( let mut runtime = Runtime { ext, + input_data, config: &config, memory, gas_meter, @@ -363,6 +364,7 @@ mod tests { let mut mock_ext = MockExt::default(); execute( &code_transfer, + &[], &mut mock_ext, &mut GasMeter::with_limit(50_000, 1), ).unwrap(); @@ -390,6 +392,7 @@ mod tests { assert_matches!( execute( &code_mem, + &[], &mut mock_ext, &mut GasMeter::with_limit(100_000, 1) ), From 8b4fc53be790dddeb6a197fcd15127c367c2862a Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Mon, 20 Aug 2018 14:58:17 +0300 Subject: [PATCH 04/11] 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. --- substrate/runtime/contract/src/tests.rs | 35 +++++++++------ .../runtime/contract/src/vm/env_def/mod.rs | 9 ++-- substrate/runtime/contract/src/vm/mod.rs | 45 ++++++++++++++----- 3 files changed, 60 insertions(+), 29 deletions(-) diff --git a/substrate/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs index dc0f3799fafe6..562fb1390e937 100644 --- a/substrate/runtime/contract/src/tests.rs +++ b/substrate/runtime/contract/src/tests.rs @@ -165,15 +165,24 @@ impl ExtBuilder { const CODE_TRANSFER: &str = r#" (module - ;; ext_call(callee_ptr: u32, callee_len: u32, value_ptr: u32, value_len: u32) - (import "env" "ext_call" (func $ext_call (param i32 i32 i32 i32))) + ;; ext_call( + ;; callee_ptr: u32, + ;; callee_len: u32, + ;; value_ptr: u32, + ;; value_len: u32, + ;; input_data_ptr: u32, + ;; input_data_len: u32 + ;;) + (import "env" "ext_call" (func $ext_call (param i32 i32 i32 i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func (export "call") (call $ext_call - (i32.const 4) ;; Pointer to "Transfer to" address. - (i32.const 8) ;; Length of "Transfer to" address. + (i32.const 4) ;; Pointer to "callee" address. + (i32.const 8) ;; Length of "callee" address. (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. @@ -205,10 +214,10 @@ fn contract_transfer() { assert_eq!( Staking::free_balance(&0), // 3 - value sent with the transaction - // 2 * 6 - gas used by the contract (6) multiplied by gas price (2) + // 2 * 8 - gas used by the contract (8) 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 * 8) - (2 * 135) - (2 * 135), ); assert_eq!( Staking::free_balance(&1), @@ -278,7 +287,7 @@ fn contract_transfer_max_depth() { // 2 * 6 * 100 - gas used by the contract (6) 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 - (2 * 135 * 100) - (2 * 8 * 100), ); assert_eq!(Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 11); }); @@ -377,12 +386,12 @@ fn contract_create() { ); // 11 - value sent with the transaction - // 2 * 124 - gas spent by the deployer contract (124) multiplied by gas price (2) + // 2 * 130 - gas spent by the deployer contract (130) 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 * 124) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); + 100_000_000 - 11 - (2 * 130) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); assert_eq!(Staking::free_balance(&0), expected_gas_after_create); assert_eq!(Staking::free_balance(&1), 8); assert_eq!(Staking::free_balance(&derived_address), 3); @@ -393,10 +402,10 @@ fn contract_create() { assert_eq!( Staking::free_balance(&0), // 22 - value sent with the transaction - // (2 * 6) - gas used by the contract + // (2 * 8) - 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 * 8) - (2 * 135) - (2 * 135), ); assert_eq!(Staking::free_balance(&derived_address), 22 - 3); assert_eq!(Staking::free_balance(&9), 36); @@ -428,12 +437,12 @@ fn top_level_create() { )); // 11 - value sent with the transaction - // (3 * 118) - gas spent by the ctor + // (3 * 124) - 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!( Staking::free_balance(&0), - 100_000_000 - 11 - (3 * 118) - (3 * 175) - ((21 / 3) * 3) + 100_000_000 - 11 - (3 * 124) - (3 * 175) - ((21 / 3) * 3) ); assert_eq!(Staking::free_balance(&derived_address), 30 + 11); diff --git a/substrate/runtime/contract/src/vm/env_def/mod.rs b/substrate/runtime/contract/src/vm/env_def/mod.rs index e18458fc2a207..5654dfa28ef72 100644 --- a/substrate/runtime/contract/src/vm/env_def/mod.rs +++ b/substrate/runtime/contract/src/vm/env_def/mod.rs @@ -182,8 +182,8 @@ define_env!(init_env, , Ok(()) }, - // ext_call(transfer_to_ptr: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) - ext_call(ctx, callee_ptr: u32, callee_len: u32, value_ptr: u32, value_len: u32) => { + // ext_call(transfer_to_ptr: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) + ext_call(ctx, callee_ptr: u32, callee_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) => { let mut callee = Vec::new(); callee.resize(callee_len as usize, 0); ctx.memory().get(callee_ptr, &mut callee)?; @@ -195,8 +195,9 @@ define_env!(init_env, , ctx.memory().get(value_ptr, &mut value_buf)?; let value = BalanceOf::<::T>::decode(&mut &value_buf[..]).unwrap(); - // 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(); diff --git a/substrate/runtime/contract/src/vm/mod.rs b/substrate/runtime/contract/src/vm/mod.rs index 4f4e576213ee3..6b748122200a6 100644 --- a/substrate/runtime/contract/src/vm/mod.rs +++ b/substrate/runtime/contract/src/vm/mod.rs @@ -281,6 +281,7 @@ mod tests { struct TransferEntry { to: u64, value: u64, + data: Vec, } #[derive(Default)] pub struct MockExt { @@ -320,9 +321,13 @@ mod tests { to: &u64, value: u64, _gas_meter: &mut GasMeter, - _data: &[u8], + data: &[u8], ) -> Result { - self.transfers.push(TransferEntry { to: *to, value }); + self.transfers.push(TransferEntry { + to: *to, + value, + data: data.to_vec(), + }); // Assume for now that it was just a plain transfer. // TODO: Add tests for different call outcomes. Ok(CallReceipt { @@ -333,27 +338,34 @@ mod tests { const CODE_TRANSFER: &str = r#" (module - ;; ext_call(callee_ptr: u32, callee_len: u32, value_ptr: u32, value_len: u32) - (import "env" "ext_call" (func $ext_call (param i32 i32 i32 i32))) - + ;; ext_call( + ;; callee_ptr: u32, + ;; callee_len: u32, + ;; value_ptr: u32, + ;; value_len: u32, + ;; input_data_ptr: u32, + ;; input_data_len: u32 + ;;) + (import "env" "ext_call" (func $ext_call (param i32 i32 i32 i32 i32 i32))) (import "env" "memory" (memory 1 1)) - (func (export "call") (call $ext_call - (i32.const 4) ;; Pointer to "Transfer to" address. - (i32.const 8) ;; Length of "Transfer to" address. + (i32.const 4) ;; Pointer to "callee" address. + (i32.const 8) ;; Length of "callee" address. (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") ) "#; @@ -369,7 +381,16 @@ mod tests { &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, + ], + }] + ); } const CODE_MEM: &str = r#" From 8e2228fa3ba85b3b48658c6b60b55c026e64225d Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Mon, 20 Aug 2018 16:48:36 +0300 Subject: [PATCH 05/11] Input data passing thru `ext_create` Same as previous commit but for `ext_create`. --- substrate/runtime/contract/src/tests.rs | 10 ++-- .../runtime/contract/src/vm/env_def/mod.rs | 9 ++-- substrate/runtime/contract/src/vm/mod.rs | 49 +++++++++++++++++++ 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/substrate/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs index 562fb1390e937..0fff9094647b5 100644 --- a/substrate/runtime/contract/src/tests.rs +++ b/substrate/runtime/contract/src/tests.rs @@ -339,8 +339,8 @@ 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, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) + (import "env" "ext_create" (func $ext_create (param i32 i32 i32 i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func (export "call") (call $ext_create @@ -348,6 +348,8 @@ fn code_create(constructor: &[u8]) -> String { (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 + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 0) ;; Length of input data buffer ) ) ;; Amount of value to transfer. @@ -386,12 +388,12 @@ fn contract_create() { ); // 11 - value sent with the transaction - // 2 * 130 - gas spent by the deployer contract (130) multiplied by gas price (2) + // 2 * 132 - gas spent by the deployer contract (130) 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 * 130) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); + 100_000_000 - 11 - (2 * 132) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); assert_eq!(Staking::free_balance(&0), expected_gas_after_create); assert_eq!(Staking::free_balance(&1), 8); assert_eq!(Staking::free_balance(&derived_address), 3); diff --git a/substrate/runtime/contract/src/vm/env_def/mod.rs b/substrate/runtime/contract/src/vm/env_def/mod.rs index 5654dfa28ef72..4a0b59c36e820 100644 --- a/substrate/runtime/contract/src/vm/env_def/mod.rs +++ b/substrate/runtime/contract/src/vm/env_def/mod.rs @@ -218,8 +218,8 @@ define_env!(init_env, , } }, - // 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, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) + ext_create(ctx, code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) => { let mut value_buf = Vec::new(); value_buf.resize(value_len as usize, 0); ctx.memory().get(value_ptr, &mut value_buf)?; @@ -229,8 +229,9 @@ define_env!(init_env, , 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(); diff --git a/substrate/runtime/contract/src/vm/mod.rs b/substrate/runtime/contract/src/vm/mod.rs index 6b748122200a6..0fbf26b04da3a 100644 --- a/substrate/runtime/contract/src/vm/mod.rs +++ b/substrate/runtime/contract/src/vm/mod.rs @@ -393,6 +393,55 @@ mod tests { ); } + const CODE_CREATE: &str = r#" +(module + ;; ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) + (import "env" "ext_create" (func $ext_create (param i32 i32 i32 i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call") + (call $ext_create + (i32.const 12) ;; Pointer to `code` + (i32.const 8) ;; 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 + (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, + ], + }] + ); + } + const CODE_MEM: &str = r#" (module ;; Internal memory is not allowed. From ca330719109b0a96ff21fe139116306ac0e8272b Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Mon, 20 Aug 2018 17:07:44 +0300 Subject: [PATCH 06/11] Trap on decoding error instead of panicking --- substrate/runtime/contract/src/vm/env_def/mod.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/substrate/runtime/contract/src/vm/env_def/mod.rs b/substrate/runtime/contract/src/vm/env_def/mod.rs index 4a0b59c36e820..15da37f29b7fa 100644 --- a/substrate/runtime/contract/src/vm/env_def/mod.rs +++ b/substrate/runtime/contract/src/vm/env_def/mod.rs @@ -188,12 +188,14 @@ define_env!(init_env, , callee.resize(callee_len as usize, 0); ctx.memory().get(callee_ptr, &mut callee)?; let callee = - <::T as system::Trait>::AccountId::decode(&mut &callee[..]).unwrap(); + <::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)?; let mut input_data = Vec::new(); input_data.resize(input_data_len as usize, 0u8); @@ -223,7 +225,8 @@ define_env!(init_env, , 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); From f4f944814dccc844c5ba1fc4719e3324f79609ec Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Tue, 21 Aug 2018 12:50:02 +0300 Subject: [PATCH 07/11] 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. --- substrate/runtime/contract/src/lib.rs | 1 - substrate/runtime/contract/src/tests.rs | 61 +++++++++---------- .../runtime/contract/src/vm/env_def/mod.rs | 7 +-- substrate/runtime/contract/src/vm/mod.rs | 20 +++--- 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/substrate/runtime/contract/src/lib.rs b/substrate/runtime/contract/src/lib.rs index 7479686ddc453..589041790a0b4 100644 --- a/substrate/runtime/contract/src/lib.rs +++ b/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/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs index 0fff9094647b5..f6c42f0a0a2cb 100644 --- a/substrate/runtime/contract/src/tests.rs +++ b/substrate/runtime/contract/src/tests.rs @@ -172,17 +172,19 @@ const CODE_TRANSFER: &str = r#" ;; value_len: u32, ;; input_data_ptr: u32, ;; input_data_len: u32 - ;;) - (import "env" "ext_call" (func $ext_call (param i32 i32 i32 i32 i32 i32))) + ;; ) -> u32 + (import "env" "ext_call" (func $ext_call (param i32 i32 i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) (func (export "call") - (call $ext_call - (i32.const 4) ;; Pointer to "callee" address. - (i32.const 8) ;; Length of "callee" address. - (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 + (drop + (call $ext_call + (i32.const 4) ;; Pointer to "callee" address. + (i32.const 8) ;; Length of "callee" address. + (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. @@ -214,10 +216,10 @@ fn contract_transfer() { assert_eq!( Staking::free_balance(&0), // 3 - value sent with the transaction - // 2 * 8 - gas used by the contract (8) multiplied by gas price (2) + // 2 * 9 - gas used by the contract (8) 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 * 8) - (2 * 135) - (2 * 135), + 100_000_000 - 3 - (2 * 9) - (2 * 135) - (2 * 135), ); assert_eq!( Staking::free_balance(&1), @@ -244,20 +246,20 @@ fn contract_transfer_oog() { Staking::set_free_balance(&1, 11); Staking::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!( Staking::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!(Staking::free_balance(&1), 11); + + // Transaction level transfer should succeed. + assert_eq!(Staking::free_balance(&1), 14); + // But `ext_call` should not. assert_eq!(Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 0); }); } @@ -276,20 +278,17 @@ fn contract_transfer_max_depth() { Staking::set_free_balance(&CONTRACT_SHOULD_TRANSFER_TO, 11); Staking::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!( Staking::free_balance(&0), // 3 - value sent with the transaction - // 2 * 6 * 100 - gas used by the contract (6) multiplied by gas price (2) + // 2 * 9 * 100 - gas used by the contract (9) 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 * 8 * 100), + 100_000_000 - 3 - (2 * 135 * 100) - (2 * 9 * 100), ); - assert_eq!(Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 11); + assert_eq!(Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 14); }); } @@ -388,12 +387,12 @@ fn contract_create() { ); // 11 - value sent with the transaction - // 2 * 132 - gas spent by the deployer contract (130) multiplied by gas price (2) + // 2 * 134 - gas spent by the deployer contract (134) 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 * 132) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); + 100_000_000 - 11 - (2 * 134) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); assert_eq!(Staking::free_balance(&0), expected_gas_after_create); assert_eq!(Staking::free_balance(&1), 8); assert_eq!(Staking::free_balance(&derived_address), 3); @@ -404,10 +403,10 @@ fn contract_create() { assert_eq!( Staking::free_balance(&0), // 22 - value sent with the transaction - // (2 * 8) - gas used by the contract + // (2 * 9) - 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 * 8) - (2 * 135) - (2 * 135), + expected_gas_after_create - 22 - (2 * 9) - (2 * 135) - (2 * 135), ); assert_eq!(Staking::free_balance(&derived_address), 22 - 3); assert_eq!(Staking::free_balance(&9), 36); @@ -439,12 +438,12 @@ fn top_level_create() { )); // 11 - value sent with the transaction - // (3 * 124) - gas spent by the ctor + // (3 * 126) - 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!( Staking::free_balance(&0), - 100_000_000 - 11 - (3 * 124) - (3 * 175) - ((21 / 3) * 3) + 100_000_000 - 11 - (3 * 126) - (3 * 175) - ((21 / 3) * 3) ); assert_eq!(Staking::free_balance(&derived_address), 30 + 11); diff --git a/substrate/runtime/contract/src/vm/env_def/mod.rs b/substrate/runtime/contract/src/vm/env_def/mod.rs index 15da37f29b7fa..31a52fe2cdf87 100644 --- a/substrate/runtime/contract/src/vm/env_def/mod.rs +++ b/substrate/runtime/contract/src/vm/env_def/mod.rs @@ -183,7 +183,7 @@ define_env!(init_env, , }, // ext_call(transfer_to_ptr: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) - ext_call(ctx, callee_ptr: u32, callee_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) => { + ext_call(ctx, callee_ptr: u32, callee_len: u32, 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)?; @@ -214,9 +214,8 @@ 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), } }, diff --git a/substrate/runtime/contract/src/vm/mod.rs b/substrate/runtime/contract/src/vm/mod.rs index 0fbf26b04da3a..6fd8120fc979f 100644 --- a/substrate/runtime/contract/src/vm/mod.rs +++ b/substrate/runtime/contract/src/vm/mod.rs @@ -345,17 +345,19 @@ mod tests { ;; value_len: u32, ;; input_data_ptr: u32, ;; input_data_len: u32 - ;;) - (import "env" "ext_call" (func $ext_call (param i32 i32 i32 i32 i32 i32))) + ;;) -> u32 + (import "env" "ext_call" (func $ext_call (param i32 i32 i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) (func (export "call") - (call $ext_call - (i32.const 4) ;; Pointer to "callee" address. - (i32.const 8) ;; Length of "callee" address. - (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 + (drop + (call $ext_call + (i32.const 4) ;; Pointer to "callee" address. + (i32.const 8) ;; Length of "callee" address. + (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. From 39aef46e401b86b22d1369559472f72ac85a712e Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Tue, 21 Aug 2018 13:42:06 +0300 Subject: [PATCH 08/11] Don't cascade failure in ext_create Same as previous commit but for ext_create. --- substrate/runtime/contract/src/tests.rs | 31 ++++++++++++------- .../runtime/contract/src/vm/env_def/mod.rs | 16 +++++++--- substrate/runtime/contract/src/vm/mod.rs | 27 ++++++++++------ 3 files changed, 49 insertions(+), 25 deletions(-) diff --git a/substrate/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs index f6c42f0a0a2cb..42a869b305de3 100644 --- a/substrate/runtime/contract/src/tests.rs +++ b/substrate/runtime/contract/src/tests.rs @@ -338,17 +338,26 @@ fn code_create(constructor: &[u8]) -> String { format!( r#" (module - ;; ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) - (import "env" "ext_create" (func $ext_create (param i32 i32 i32 i32 i32 i32))) + ;; ext_create( + ;; code_ptr: u32, + ;; code_len: u32, + ;; 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 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 - (i32.const 0) ;; Pointer to input data buffer address - (i32.const 0) ;; Length of input data buffer + (drop + (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 + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 0) ;; Length of input data buffer + ) ) ) ;; Amount of value to transfer. @@ -387,12 +396,12 @@ fn contract_create() { ); // 11 - value sent with the transaction - // 2 * 134 - gas spent by the deployer contract (134) multiplied by gas price (2) + // 2 * 135 - gas spent by the deployer contract (135) 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 * 134) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); + 100_000_000 - 11 - (2 * 135) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); assert_eq!(Staking::free_balance(&0), expected_gas_after_create); assert_eq!(Staking::free_balance(&1), 8); assert_eq!(Staking::free_balance(&derived_address), 3); diff --git a/substrate/runtime/contract/src/vm/env_def/mod.rs b/substrate/runtime/contract/src/vm/env_def/mod.rs index 31a52fe2cdf87..1d338b4ee01a5 100644 --- a/substrate/runtime/contract/src/vm/env_def/mod.rs +++ b/substrate/runtime/contract/src/vm/env_def/mod.rs @@ -219,8 +219,15 @@ define_env!(init_env, , } }, - // ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) - ext_create(ctx, code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) => { + // ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) -> u32 + ext_create( + ctx, code_ptr: u32, + code_len: u32, + 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)?; @@ -248,9 +255,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), } }, diff --git a/substrate/runtime/contract/src/vm/mod.rs b/substrate/runtime/contract/src/vm/mod.rs index 6fd8120fc979f..b2ce2cd94fdcf 100644 --- a/substrate/runtime/contract/src/vm/mod.rs +++ b/substrate/runtime/contract/src/vm/mod.rs @@ -397,17 +397,26 @@ mod tests { const CODE_CREATE: &str = r#" (module - ;; ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) - (import "env" "ext_create" (func $ext_create (param i32 i32 i32 i32 i32 i32))) + ;; ext_create( + ;; code_ptr: u32, + ;; code_len: u32, + ;; 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 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 8) ;; 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 - (i32.const 20) ;; Pointer to input data buffer address - (i32.const 4) ;; Length of input data buffer + (drop + (call $ext_create + (i32.const 12) ;; Pointer to `code` + (i32.const 8) ;; 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 + (i32.const 20) ;; Pointer to input data buffer address + (i32.const 4) ;; Length of input data buffer + ) ) ) ;; Amount of value to transfer. From 19e893b5423e0505a47d6e33f31da4b283f0e0f0 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Wed, 22 Aug 2018 17:40:40 +0300 Subject: [PATCH 09/11] Specify how much gas to allot for a call --- substrate/runtime/contract/src/tests.rs | 26 +++---- .../runtime/contract/src/vm/env_def/mod.rs | 11 +-- substrate/runtime/contract/src/vm/mod.rs | 71 ++++++++++++++++++- 3 files changed, 90 insertions(+), 18 deletions(-) diff --git a/substrate/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs index 42a869b305de3..a362dc250d728 100644 --- a/substrate/runtime/contract/src/tests.rs +++ b/substrate/runtime/contract/src/tests.rs @@ -168,18 +168,20 @@ const CODE_TRANSFER: &str = r#" ;; 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 i32 i32 i32 i32) (result i32))) + (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 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 @@ -216,10 +218,10 @@ fn contract_transfer() { assert_eq!( Staking::free_balance(&0), // 3 - value sent with the transaction - // 2 * 9 - gas used by the contract (8) 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 * 9) - (2 * 135) - (2 * 135), + 100_000_000 - 3 - (2 * 10) - (2 * 135) - (2 * 135), ); assert_eq!( Staking::free_balance(&1), @@ -283,10 +285,10 @@ fn contract_transfer_max_depth() { assert_eq!( Staking::free_balance(&0), // 3 - value sent with the transaction - // 2 * 9 * 100 - gas used by the contract (9) 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 - 3 - (2 * 135 * 100) - (2 * 9 * 100), + 100_000_000 - 3 - (2 * 10 * 100) - (2 * 135 * 100), ); assert_eq!(Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 14); }); @@ -396,12 +398,12 @@ fn contract_create() { ); // 11 - value sent with the transaction - // 2 * 135 - gas spent by the deployer contract (135) multiplied by gas price (2) + // 2 * 138 - gas spent by the deployer contract (138) 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 * 135) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); + 100_000_000 - 11 - (2 * 138) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); assert_eq!(Staking::free_balance(&0), expected_gas_after_create); assert_eq!(Staking::free_balance(&1), 8); assert_eq!(Staking::free_balance(&derived_address), 3); @@ -412,10 +414,10 @@ fn contract_create() { assert_eq!( Staking::free_balance(&0), // 22 - value sent with the transaction - // (2 * 9) - 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 * 9) - (2 * 135) - (2 * 135), + expected_gas_after_create - 22 - (2 * 10) - (2 * 135) - (2 * 135), ); assert_eq!(Staking::free_balance(&derived_address), 22 - 3); assert_eq!(Staking::free_balance(&9), 36); @@ -447,12 +449,12 @@ fn top_level_create() { )); // 11 - value sent with the transaction - // (3 * 126) - 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!( Staking::free_balance(&0), - 100_000_000 - 11 - (3 * 126) - (3 * 175) - ((21 / 3) * 3) + 100_000_000 - 11 - (3 * 129) - (3 * 175) - ((21 / 3) * 3) ); assert_eq!(Staking::free_balance(&derived_address), 30 + 11); diff --git a/substrate/runtime/contract/src/vm/env_def/mod.rs b/substrate/runtime/contract/src/vm/env_def/mod.rs index 1d338b4ee01a5..23f0cd092ddf2 100644 --- a/substrate/runtime/contract/src/vm/env_def/mod.rs +++ b/substrate/runtime/contract/src/vm/env_def/mod.rs @@ -182,8 +182,8 @@ define_env!(init_env, , Ok(()) }, - // ext_call(transfer_to_ptr: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) - ext_call(ctx, callee_ptr: u32, callee_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) -> u32 => { + // 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)?; @@ -201,8 +201,11 @@ define_env!(init_env, , 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 { diff --git a/substrate/runtime/contract/src/vm/mod.rs b/substrate/runtime/contract/src/vm/mod.rs index b2ce2cd94fdcf..34b30608ffa62 100644 --- a/substrate/runtime/contract/src/vm/mod.rs +++ b/substrate/runtime/contract/src/vm/mod.rs @@ -282,6 +282,7 @@ mod tests { to: u64, value: u64, data: Vec, + gas_left: u64, } #[derive(Default)] pub struct MockExt { @@ -320,13 +321,14 @@ mod tests { &mut self, to: &u64, value: u64, - _gas_meter: &mut GasMeter, + gas_meter: &mut GasMeter, data: &[u8], ) -> Result { 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. @@ -341,18 +343,20 @@ mod tests { ;; 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 i32 i32 i32 i32) (result i32))) + (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 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 @@ -391,6 +395,7 @@ mod tests { data: vec![ 1, 2, 3, 4, ], + gas_left: 49990, }] ); } @@ -480,4 +485,66 @@ mod tests { 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, + }] + ); + } } From 758f59b95822eeadb106193ef595309aed719a34 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Fri, 24 Aug 2018 17:26:21 +0300 Subject: [PATCH 10/11] Specify how much gas to allot for a create --- substrate/runtime/contract/src/tests.rs | 8 +++++--- substrate/runtime/contract/src/vm/env_def/mod.rs | 10 +++++++--- substrate/runtime/contract/src/vm/mod.rs | 9 +++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/substrate/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs index a362dc250d728..9b65c7bdb7ca7 100644 --- a/substrate/runtime/contract/src/tests.rs +++ b/substrate/runtime/contract/src/tests.rs @@ -343,18 +343,20 @@ fn code_create(constructor: &[u8]) -> String { ;; 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 i32 i32 i32 i32) (result i32))) + (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 {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 @@ -398,12 +400,12 @@ fn contract_create() { ); // 11 - value sent with the transaction - // 2 * 138 - gas spent by the deployer contract (138) 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 * 138) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); + 100_000_000 - 11 - (2 * 139) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); assert_eq!(Staking::free_balance(&0), expected_gas_after_create); assert_eq!(Staking::free_balance(&1), 8); assert_eq!(Staking::free_balance(&derived_address), 3); diff --git a/substrate/runtime/contract/src/vm/env_def/mod.rs b/substrate/runtime/contract/src/vm/env_def/mod.rs index 23f0cd092ddf2..eb6c16ca8a07e 100644 --- a/substrate/runtime/contract/src/vm/env_def/mod.rs +++ b/substrate/runtime/contract/src/vm/env_def/mod.rs @@ -222,10 +222,11 @@ define_env!(init_env, , } }, - // ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) -> 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, @@ -245,8 +246,11 @@ define_env!(init_env, , 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 { diff --git a/substrate/runtime/contract/src/vm/mod.rs b/substrate/runtime/contract/src/vm/mod.rs index 34b30608ffa62..f7dd9934bb24f 100644 --- a/substrate/runtime/contract/src/vm/mod.rs +++ b/substrate/runtime/contract/src/vm/mod.rs @@ -276,6 +276,7 @@ mod tests { code: Vec, endowment: u64, data: Vec, + gas_left: u64, } #[derive(Debug, PartialEq, Eq)] struct TransferEntry { @@ -304,13 +305,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; @@ -405,18 +407,20 @@ mod tests { ;; 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 i32 i32 i32 i32) (result i32))) + (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 @@ -454,6 +458,7 @@ mod tests { data: vec![ 1, 2, 3, 4, ], + gas_left: 49990, }] ); } From a33eab34305e5bd9e4d8a7eeb761eff9a51c2bee Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 1 Sep 2018 09:14:34 +0200 Subject: [PATCH 11/11] Update tests.rs --- substrate/runtime/contract/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs index 7d2c2acf65f48..191536cba8027 100644 --- a/substrate/runtime/contract/src/tests.rs +++ b/substrate/runtime/contract/src/tests.rs @@ -656,8 +656,8 @@ fn input_data() { || { >::insert(1, code_input_data.to_vec()); - Staking::set_free_balance(&0, 100_000_000); - Staking::increase_total_stake_by(100_000_000); + 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]));