diff --git a/substrate/runtime/contract/src/exec.rs b/substrate/runtime/contract/src/exec.rs index 4779e959c17f4..f7673d49b0fcf 100644 --- a/substrate/runtime/contract/src/exec.rs +++ b/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/runtime/contract/src/lib.rs b/substrate/runtime/contract/src/lib.rs index 9f77b07fb8816..ec78584f2b12a 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 d12b137d41e82..191536cba8027 100644 --- a/substrate/runtime/contract/src/tests.rs +++ b/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/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 1222c23290428..eb6c16ca8a07e 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, + } } } @@ -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/runtime/contract/src/vm/mod.rs b/substrate/runtime/contract/src/vm/mod.rs index 5b3318e6fcc8a..0ca9f9e694b5b 100644 --- a/substrate/runtime/contract/src/vm/mod.rs +++ b/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: 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.transfers, &[TransferEntry { to: 2, value: 6 }]); + 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, + }] + ); + } }