diff --git a/substrate/core/sr-sandbox/src/lib.rs b/substrate/core/sr-sandbox/src/lib.rs index 9cb39236b0..e814a51ace 100755 --- a/substrate/core/sr-sandbox/src/lib.rs +++ b/substrate/core/sr-sandbox/src/lib.rs @@ -53,8 +53,7 @@ mod imp { /// Error that can occur while using this crate. #[cfg_attr(feature = "std", derive(Debug))] pub enum Error { - /// Module is not valid, couldn't be instantiated or it's `start` function trapped - /// when executed. + /// Module is not valid, couldn't be instantiated. Module, /// Access to a memory or table was made with an address or an index which is out of bounds. @@ -62,7 +61,7 @@ pub enum Error { /// Note that if wasm module makes an out-of-bounds access then trap will occur. OutOfBounds, - /// Failed to invoke an exported function for some reason. + /// Failed to invoke the start function or an exported function for some reason. Execution, } diff --git a/substrate/node/executor/src/lib.rs b/substrate/node/executor/src/lib.rs index 167f90feeb..a7dcb879d3 100644 --- a/substrate/node/executor/src/lib.rs +++ b/substrate/node/executor/src/lib.rs @@ -690,7 +690,7 @@ mod tests { ;; ) -> u32 (import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func (export "deploy") ) @@ -705,7 +705,7 @@ mod tests { ) ) - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 0) (i32.const 0) (i32.const 4) diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index 086818c51f..c4c4ca3f8f 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -80,8 +80,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 138, - impl_version: 138, + spec_version: 139, + impl_version: 139, apis: RUNTIME_API_VERSIONS, }; diff --git a/substrate/srml/contracts/COMPLEXITY.md b/substrate/srml/contracts/COMPLEXITY.md index 6ae7b8fb73..c582de4264 100644 --- a/substrate/srml/contracts/COMPLEXITY.md +++ b/substrate/srml/contracts/COMPLEXITY.md @@ -371,7 +371,7 @@ This function returns the size of the scratch buffer. **complexity**: This function is of constant complexity. -## ext_scratch_copy +## ext_scratch_read This function copies slice of data from the scratch buffer to the sandbox memory. The calling code specifies the slice length. Execution of the function consists of the following steps: @@ -379,6 +379,14 @@ This function copies slice of data from the scratch buffer to the sandbox memory **complexity**: The computing complexity of this function is proportional to the length of the slice. No additional memory is required. +## ext_scratch_write + +This function copies slice of data from the sandbox memory to the scratch buffer. The calling code specifies the slice length. Execution of the function consists of the following steps: + +1. Loading a slice from the sandbox memory into the (see sandboxing memory get) + +**complexity**: Complexity is proportional to the length of the slice. + ## ext_set_rent_allowance This function receives the following argument: diff --git a/substrate/srml/contracts/src/exec.rs b/substrate/srml/contracts/src/exec.rs index eda37d2452..5ba02d43a0 100644 --- a/substrate/srml/contracts/src/exec.rs +++ b/substrate/srml/contracts/src/exec.rs @@ -34,15 +34,54 @@ pub type BlockNumberOf = ::BlockNumber; /// A type that represents a topic of an event. At the moment a hash is used. pub type TopicOf = ::Hash; -#[cfg_attr(test, derive(Debug))] -pub struct InstantiateReceipt { - pub address: AccountId, +/// A status code return to the source of a contract call or instantiation indicating success or +/// failure. A code of 0 indicates success and that changes are applied. All other codes indicate +/// failure and that changes are reverted. The particular code in the case of failure is opaque and +/// may be interpreted by the calling contract. +pub type StatusCode = u8; + +/// The status code indicating success. +pub const STATUS_SUCCESS: StatusCode = 0; + +/// Output of a contract call or instantiation which ran to completion. +#[cfg_attr(test, derive(PartialEq, Eq, Debug))] +pub struct ExecReturnValue { + pub status: StatusCode, + pub data: Vec, } +impl ExecReturnValue { + /// Returns whether the call or instantiation exited with a successful status code. + pub fn is_success(&self) -> bool { + self.status == STATUS_SUCCESS + } +} + +/// An error indicating some failure to execute a contract call or instantiation. This can include +/// VM-specific errors during execution (eg. division by 0, OOB access, failure to satisfy some +/// precondition of a system call, etc.) or errors with the orchestration (eg. out-of-gas errors, a +/// non-existent destination contract, etc.). #[cfg_attr(test, derive(Debug))] -pub struct CallReceipt { - /// Output data received as a result of a call. - pub output_data: Vec, +pub struct ExecError { + pub reason: &'static str, + /// This is an allocated buffer that may be reused. The buffer must be cleared explicitly + /// before reuse. + pub buffer: Vec, +} + +pub type ExecResult = Result; + +/// Evaluate an expression of type Result<_, &'static str> and either resolve to the value if Ok or +/// wrap the error string into an ExecutionError with the provided buffer and return from the +/// enclosing function. This macro is used instead of .map_err(..)? in order to avoid taking +/// ownership of buffer unless there is an error. +macro_rules! try_or_exec_error { + ($e:expr, $buffer:expr) => { + match $e { + Ok(val) => val, + Err(reason) => return Err(ExecError { reason, buffer: $buffer }), + } + } } pub type StorageKey = [u8; 32]; @@ -74,8 +113,8 @@ pub trait Ext { code: &CodeHash, value: BalanceOf, gas_meter: &mut GasMeter, - input_data: &[u8], - ) -> Result>, &'static str>; + input_data: Vec, + ) -> Result<(AccountIdOf, ExecReturnValue), ExecError>; /// Call (possibly transferring some amount of funds) into the specified account. fn call( @@ -83,9 +122,8 @@ pub trait Ext { to: &AccountIdOf, value: BalanceOf, gas_meter: &mut GasMeter, - input_data: &[u8], - empty_output_buf: EmptyOutputBuf, - ) -> Result; + input_data: Vec, + ) -> ExecResult; /// Notes a call dispatch. fn note_dispatch_call(&mut self, call: CallOf); @@ -150,66 +188,6 @@ pub trait Loader { fn load_main(&self, code_hash: &CodeHash) -> Result; } -/// An `EmptyOutputBuf` is used as an optimization for reusing empty vectors when -/// available. -/// -/// You can create this structure from a spare vector if you have any and then -/// you can fill it (only once), converting it to `OutputBuf`. -pub struct EmptyOutputBuf(Vec); - -impl EmptyOutputBuf { - /// Create an output buffer from a spare vector which is not longer needed. - /// - /// All contents are discarded, but capacity is preserved. - pub fn from_spare_vec(mut v: Vec) -> Self { - v.clear(); - EmptyOutputBuf(v) - } - - /// Create an output buffer ready for receiving a result. - /// - /// Use this function to create output buffer if you don't have a spare - /// vector. Otherwise, use `from_spare_vec`. - pub fn new() -> Self { - EmptyOutputBuf(Vec::new()) - } - - /// Write to the buffer result of the specified size. - /// - /// Calls closure with the buffer of the requested size. - pub fn fill Result<(), E>>(mut self, size: usize, f: F) -> Result { - assert!(self.0.len() == 0, "the vector is always cleared; it's written only once"); - self.0.resize(size, 0); - f(&mut self.0).map(|()| OutputBuf(self.0)) - } -} - -/// `OutputBuf` is the end result of filling an `EmptyOutputBuf`. -pub struct OutputBuf(Vec); - -#[must_use] -pub enum VmExecResult { - Ok, - Returned(OutputBuf), - /// A program executed some forbidden operation. - /// - /// This can include, e.g.: division by 0, OOB access or failure to satisfy some precondition - /// of a system call. - /// - /// Contains some vm-specific description of an trap. - Trap(&'static str), -} - -impl VmExecResult { - pub fn into_result(self) -> Result, &'static str> { - match self { - VmExecResult::Ok => Ok(Vec::new()), - VmExecResult::Returned(buf) => Ok(buf.0), - VmExecResult::Trap(description) => Err(description), - } - } -} - /// Struct that records a request to deposit an event with a list of topics. #[cfg_attr(any(feature = "std", test), derive(Debug, PartialEq, Eq))] pub struct IndexedEvent { @@ -227,9 +205,6 @@ pub struct IndexedEvent { /// /// Execution of code can end by either implicit termination (that is, reached the end of /// executable), explicit termination via returning a buffer or termination due to a trap. -/// -/// You can optionally provide a vector for collecting output if a spare is available. If you don't have -/// it will be created anyway. pub trait Vm { type Executable; @@ -237,10 +212,9 @@ pub trait Vm { &self, exec: &Self::Executable, ext: E, - input_data: &[u8], - empty_output_buf: EmptyOutputBuf, + input_data: Vec, gas_meter: &mut GasMeter, - ) -> VmExecResult; + ) -> ExecResult; } #[cfg_attr(test, derive(Debug, PartialEq, Eq))] @@ -353,18 +327,23 @@ where dest: T::AccountId, value: BalanceOf, gas_meter: &mut GasMeter, - input_data: &[u8], - empty_output_buf: EmptyOutputBuf, - ) -> Result { + input_data: Vec, + ) -> ExecResult { if self.depth == self.config.max_depth as usize { - return Err("reached maximum depth, cannot make a call"); + return Err(ExecError { + reason: "reached maximum depth, cannot make a call", + buffer: input_data, + }); } if gas_meter .charge(self.config, ExecFeeToken::Call) .is_out_of_gas() { - return Err("not enough gas to pay base call fee"); + return Err(ExecError { + reason: "not enough gas to pay base call fee", + buffer: input_data, + }); } // Assumption: pay_rent doesn't collide with overlay because @@ -374,46 +353,49 @@ where // Calls to dead contracts always fail. if let Some(ContractInfo::Tombstone(_)) = contract_info { - return Err("contract has been evicted"); + return Err(ExecError { + reason: "contract has been evicted", + buffer: input_data, + }); }; let caller = self.self_account.clone(); let dest_trie_id = contract_info.and_then(|i| i.as_alive().map(|i| i.trie_id.clone())); - let output_data = self.with_nested_context(dest.clone(), dest_trie_id, |nested| { + self.with_nested_context(dest.clone(), dest_trie_id, |nested| { if value > BalanceOf::::zero() { - transfer( - gas_meter, - TransferCause::Call, - &caller, - &dest, - value, - nested, - )?; + try_or_exec_error!( + transfer( + gas_meter, + TransferCause::Call, + &caller, + &dest, + value, + nested, + ), + input_data + ); } // If code_hash is not none, then the destination account is a live contract, otherwise // it is a regular account since tombstone accounts have already been rejected. - let output_data = match nested.overlay.get_code_hash(&dest) { + match nested.overlay.get_code_hash(&dest) { Some(dest_code_hash) => { - let executable = nested.loader.load_main(&dest_code_hash)?; + let executable = try_or_exec_error!( + nested.loader.load_main(&dest_code_hash), + input_data + ); nested.vm .execute( &executable, nested.new_call_context(caller, value), input_data, - empty_output_buf, gas_meter, ) - .into_result()? } - None => Vec::new(), - }; - - Ok(output_data) - })?; - - Ok(CallReceipt { output_data }) + None => Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }), + } + }) } pub fn instantiate( @@ -421,53 +403,66 @@ where endowment: BalanceOf, gas_meter: &mut GasMeter, code_hash: &CodeHash, - input_data: &[u8], - ) -> Result, &'static str> { + input_data: Vec, + ) -> Result<(T::AccountId, ExecReturnValue), ExecError> { if self.depth == self.config.max_depth as usize { - return Err("reached maximum depth, cannot create"); + return Err(ExecError { + reason: "reached maximum depth, cannot create", + buffer: input_data, + }); } if gas_meter .charge(self.config, ExecFeeToken::Instantiate) .is_out_of_gas() { - return Err("not enough gas to pay base instantiate fee"); + return Err(ExecError { + reason: "not enough gas to pay base instantiate fee", + buffer: input_data, + }); } let caller = self.self_account.clone(); let dest = T::DetermineContractAddress::contract_address_for( code_hash, - input_data, + &input_data, &caller, ); // TrieId has not been generated yet and storage is empty since contract is new. let dest_trie_id = None; - let _ = self.with_nested_context(dest.clone(), dest_trie_id, |nested| { - nested.overlay.create_contract(&dest, code_hash.clone())?; + let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| { + try_or_exec_error!( + nested.overlay.create_contract(&dest, code_hash.clone()), + input_data + ); // Send funds unconditionally here. If the `endowment` is below existential_deposit // then error will be returned here. - transfer( - gas_meter, - TransferCause::Instantiate, - &caller, - &dest, - endowment, - nested, - )?; + try_or_exec_error!( + transfer( + gas_meter, + TransferCause::Instantiate, + &caller, + &dest, + endowment, + nested, + ), + input_data + ); - let executable = nested.loader.load_init(&code_hash)?; - nested.vm + let executable = try_or_exec_error!( + nested.loader.load_init(&code_hash), + input_data + ); + let output = nested.vm .execute( &executable, nested.new_call_context(caller.clone(), endowment), input_data, - EmptyOutputBuf::new(), gas_meter, - ) - .into_result()?; + )?; // Deposit an instantiation event. nested.deferred.push(DeferredAction::DepositEvent { @@ -475,10 +470,10 @@ where topics: Vec::new(), }); - Ok(Vec::new()) + Ok(output) })?; - Ok(InstantiateReceipt { address: dest }) + Ok((dest, output)) } fn new_call_context<'b>(&'b mut self, caller: T::AccountId, value: BalanceOf) @@ -496,19 +491,21 @@ where } fn with_nested_context(&mut self, dest: T::AccountId, trie_id: Option, func: F) - -> Result, &'static str> - where F: FnOnce(&mut ExecutionContext) -> Result, &'static str> + -> ExecResult + where F: FnOnce(&mut ExecutionContext) -> ExecResult { - let (output_data, change_set, deferred) = { + let (output, change_set, deferred) = { let mut nested = self.nested(dest, trie_id); - let output_data = func(&mut nested)?; - (output_data, nested.overlay.into_change_set(), nested.deferred) + let output = func(&mut nested)?; + (output, nested.overlay.into_change_set(), nested.deferred) }; - self.overlay.commit(change_set); - self.deferred.extend(deferred); + if output.is_success() { + self.overlay.commit(change_set); + self.deferred.extend(deferred); + } - Ok(output_data) + Ok(output) } } @@ -673,8 +670,8 @@ where code_hash: &CodeHash, endowment: BalanceOf, gas_meter: &mut GasMeter, - input_data: &[u8], - ) -> Result>, &'static str> { + input_data: Vec, + ) -> Result<(AccountIdOf, ExecReturnValue), ExecError> { self.ctx.instantiate(endowment, gas_meter, code_hash, input_data) } @@ -683,11 +680,9 @@ where to: &T::AccountId, value: BalanceOf, gas_meter: &mut GasMeter, - input_data: &[u8], - empty_output_buf: EmptyOutputBuf, - ) -> Result { - self.ctx - .call(to.clone(), value, gas_meter, input_data, empty_output_buf) + input_data: Vec, + ) -> ExecResult { + self.ctx.call(to.clone(), value, gas_meter, input_data) } fn note_dispatch_call(&mut self, call: CallOf) { @@ -773,10 +768,11 @@ where #[cfg(test)] mod tests { use super::{ - BalanceOf, ExecFeeToken, ExecutionContext, Ext, Loader, EmptyOutputBuf, TransferFeeKind, TransferFeeToken, - Vm, VmExecResult, InstantiateReceipt, RawEvent, DeferredAction, + BalanceOf, ExecFeeToken, ExecutionContext, Ext, Loader, TransferFeeKind, TransferFeeToken, + Vm, ExecResult, RawEvent, DeferredAction, }; use crate::account_db::AccountDb; + use crate::exec::{ExecReturnValue, ExecError, STATUS_SUCCESS}; use crate::gas::GasMeter; use crate::tests::{ExtBuilder, Test}; use crate::{CodeHash, Config}; @@ -808,16 +804,15 @@ mod tests { struct MockCtx<'a> { ext: &'a mut dyn Ext, - input_data: &'a [u8], - empty_output_buf: Option, + input_data: Vec, gas_meter: &'a mut GasMeter, } #[derive(Clone)] - struct MockExecutable<'a>(Rc VmExecResult + 'a>); + struct MockExecutable<'a>(Rc ExecResult + 'a>); impl<'a> MockExecutable<'a> { - fn new(f: impl Fn(MockCtx) -> VmExecResult + 'a) -> Self { + fn new(f: impl Fn(MockCtx) -> ExecResult + 'a) -> Self { MockExecutable(Rc::new(f)) } } @@ -835,7 +830,7 @@ mod tests { } } - fn insert(&mut self, f: impl Fn(MockCtx) -> VmExecResult + 'a) -> CodeHash { + fn insert(&mut self, f: impl Fn(MockCtx) -> ExecResult + 'a) -> CodeHash { // Generate code hashes as monotonically increasing values. let code_hash = ::Hash::from_low_u64_be(self.counter); @@ -879,19 +874,21 @@ mod tests { &self, exec: &MockExecutable, mut ext: E, - input_data: &[u8], - empty_output_buf: EmptyOutputBuf, + input_data: Vec, gas_meter: &mut GasMeter, - ) -> VmExecResult { + ) -> ExecResult { (exec.0)(MockCtx { ext: &mut ext, input_data, - empty_output_buf: Some(empty_output_buf), gas_meter, }) } } + fn exec_success() -> ExecResult { + Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }) + } + #[test] fn it_works() { let value = Default::default(); @@ -905,7 +902,7 @@ mod tests { let mut loader = MockLoader::empty(); let exec_ch = loader.insert(|_ctx| { test_data.borrow_mut().push(1); - VmExecResult::Ok + exec_success() }); with_externalities(&mut ExtBuilder::default().build(), || { @@ -914,7 +911,7 @@ mod tests { ctx.overlay.create_contract(&BOB, exec_ch).unwrap(); assert_matches!( - ctx.call(BOB, value, &mut gas_meter, &data, EmptyOutputBuf::new()), + ctx.call(BOB, value, &mut gas_meter, data), Ok(_) ); }); @@ -938,7 +935,7 @@ mod tests { let mut gas_meter = GasMeter::::with_limit(1000, 1); - let result = ctx.call(dest, 0, &mut gas_meter, &[], EmptyOutputBuf::new()); + let result = ctx.call(dest, 0, &mut gas_meter, vec![]); assert_matches!(result, Ok(_)); let mut toks = gas_meter.tokens().iter(); @@ -948,7 +945,7 @@ mod tests { // This test verifies that base fee for instantiation is taken. with_externalities(&mut ExtBuilder::default().build(), || { let mut loader = MockLoader::empty(); - let code = loader.insert(|_| VmExecResult::Ok); + let code = loader.insert(|_| exec_success()); let vm = MockVm::new(); let cfg = Config::preload(); @@ -958,7 +955,7 @@ mod tests { let mut gas_meter = GasMeter::::with_limit(1000, 1); - let result = ctx.instantiate(0, &mut gas_meter, &code, &[]); + let result = ctx.instantiate(0, &mut gas_meter, &code, vec![]); assert_matches!(result, Ok(_)); let mut toks = gas_meter.tokens().iter(); @@ -982,19 +979,52 @@ mod tests { ctx.overlay.set_balance(&origin, 100); ctx.overlay.set_balance(&dest, 0); - let result = ctx.call( + let output = ctx.call( dest, 55, &mut GasMeter::::with_limit(1000, 1), - &[], - EmptyOutputBuf::new(), - ); - assert_matches!(result, Ok(_)); + vec![], + ).unwrap(); + + assert!(output.is_success()); assert_eq!(ctx.overlay.get_balance(&origin), 45); assert_eq!(ctx.overlay.get_balance(&dest), 55); }); } + #[test] + fn changes_are_reverted_on_failing_call() { + // This test verifies that a contract is able to transfer + // some funds to another account. + let origin = ALICE; + let dest = BOB; + + let vm = MockVm::new(); + let mut loader = MockLoader::empty(); + let return_ch = loader.insert( + |_| Ok(ExecReturnValue { status: 1, data: Vec::new() }) + ); + + with_externalities(&mut ExtBuilder::default().build(), || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + ctx.overlay.create_contract(&BOB, return_ch).unwrap(); + ctx.overlay.set_balance(&origin, 100); + ctx.overlay.set_balance(&dest, 0); + + let output = ctx.call( + dest, + 55, + &mut GasMeter::::with_limit(1000, 1), + vec![], + ).unwrap(); + + assert!(!output.is_success()); + assert_eq!(ctx.overlay.get_balance(&origin), 100); + assert_eq!(ctx.overlay.get_balance(&dest), 0); + }); + } + #[test] fn transfer_fees() { let origin = ALICE; @@ -1015,7 +1045,7 @@ mod tests { let mut gas_meter = GasMeter::::with_limit(1000, 1); - let result = ctx.call(dest, 50, &mut gas_meter, &[], EmptyOutputBuf::new()); + let result = ctx.call(dest, 50, &mut gas_meter, vec![]); assert_matches!(result, Ok(_)); let mut toks = gas_meter.tokens().iter(); @@ -1044,7 +1074,7 @@ mod tests { let mut gas_meter = GasMeter::::with_limit(1000, 1); - let result = ctx.call(dest, 50, &mut gas_meter, &[], EmptyOutputBuf::new()); + let result = ctx.call(dest, 50, &mut gas_meter, vec![]); assert_matches!(result, Ok(_)); let mut toks = gas_meter.tokens().iter(); @@ -1065,7 +1095,7 @@ mod tests { &mut ExtBuilder::default().existential_deposit(15).build(), || { let mut loader = MockLoader::empty(); - let code = loader.insert(|_| VmExecResult::Ok); + let code = loader.insert(|_| exec_success()); let vm = MockVm::new(); let cfg = Config::preload(); @@ -1076,7 +1106,7 @@ mod tests { let mut gas_meter = GasMeter::::with_limit(1000, 1); - let result = ctx.instantiate(50, &mut gas_meter, &code, &[]); + let result = ctx.instantiate(50, &mut gas_meter, &code, vec![]); assert_matches!(result, Ok(_)); let mut toks = gas_meter.tokens().iter(); @@ -1111,37 +1141,30 @@ mod tests { dest, 100, &mut GasMeter::::with_limit(1000, 1), - &[], - EmptyOutputBuf::new(), + vec![], ); - assert_matches!(result, Err("balance too low to send value")); + assert_matches!( + result, + Err(ExecError { reason: "balance too low to send value", buffer: _ }) + ); assert_eq!(ctx.overlay.get_balance(&origin), 0); assert_eq!(ctx.overlay.get_balance(&dest), 0); }); } #[test] - fn output_is_returned() { - // Verifies that if a contract returns data, this data + fn output_is_returned_on_success() { + // Verifies that if a contract returns data with a successful exit status, this data // is returned from the execution context. let origin = ALICE; let dest = BOB; let vm = MockVm::new(); let mut loader = MockLoader::empty(); - let return_ch = loader.insert(|mut ctx| { - #[derive(Debug)] - enum Void {} - let empty_output_buf = ctx.empty_output_buf.take().unwrap(); - let output_buf = - empty_output_buf.fill::(4, |data| { - data.copy_from_slice(&[1, 2, 3, 4]); - Ok(()) - }) - .expect("Ok is always returned"); - VmExecResult::Returned(output_buf) - }); + let return_ch = loader.insert( + |_| Ok(ExecReturnValue { status: STATUS_SUCCESS, data: vec![1, 2, 3, 4] }) + ); with_externalities(&mut ExtBuilder::default().build(), || { let cfg = Config::preload(); @@ -1152,22 +1175,53 @@ mod tests { dest, 0, &mut GasMeter::::with_limit(1000, 1), - &[], - EmptyOutputBuf::new(), + vec![], ); - let output_data = result.unwrap().output_data; - assert_eq!(&output_data, &[1, 2, 3, 4]); + let output = result.unwrap(); + assert!(output.is_success()); + assert_eq!(output.data, vec![1, 2, 3, 4]); }); } #[test] - fn input_data() { + fn output_is_returned_on_failure() { + // Verifies that if a contract returns data with a failing exit status, this data + // is returned from the execution context. + let origin = ALICE; + let dest = BOB; + + let vm = MockVm::new(); + let mut loader = MockLoader::empty(); + let return_ch = loader.insert( + |_| Ok(ExecReturnValue { status: 1, data: vec![1, 2, 3, 4] }) + ); + + with_externalities(&mut ExtBuilder::default().build(), || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + ctx.overlay.create_contract(&BOB, return_ch).unwrap(); + + let result = ctx.call( + dest, + 0, + &mut GasMeter::::with_limit(1000, 1), + vec![], + ); + + let output = result.unwrap(); + assert!(!output.is_success()); + assert_eq!(output.data, vec![1, 2, 3, 4]); + }); + } + + #[test] + fn input_data_to_call() { let vm = MockVm::new(); let mut loader = MockLoader::empty(); let input_data_ch = loader.insert(|ctx| { assert_eq!(ctx.input_data, &[1, 2, 3, 4]); - VmExecResult::Ok + exec_success() }); // This one tests passing the input data into a contract via call. @@ -1180,11 +1234,20 @@ mod tests { BOB, 0, &mut GasMeter::::with_limit(10000, 1), - &[1, 2, 3, 4], - EmptyOutputBuf::new(), + vec![1, 2, 3, 4], ); assert_matches!(result, Ok(_)); }); + } + + #[test] + fn input_data_to_instantiate() { + let vm = MockVm::new(); + let mut loader = MockLoader::empty(); + let input_data_ch = loader.insert(|ctx| { + assert_eq!(ctx.input_data, &[1, 2, 3, 4]); + exec_success() + }); // This one tests passing the input data into a contract via instantiate. with_externalities(&mut ExtBuilder::default().build(), || { @@ -1195,7 +1258,7 @@ mod tests { 0, &mut GasMeter::::with_limit(10000, 1), &input_data_ch, - &[1, 2, 3, 4], + vec![1, 2, 3, 4], ); assert_matches!(result, Ok(_)); }); @@ -1212,22 +1275,23 @@ mod tests { let mut loader = MockLoader::empty(); let recurse_ch = loader.insert(|ctx| { // Try to call into yourself. - let r = ctx - .ext - .call(&BOB, 0, ctx.gas_meter, &[], EmptyOutputBuf::new()); + let r = ctx.ext.call(&BOB, 0, ctx.gas_meter, vec![]); let mut reached_bottom = reached_bottom.borrow_mut(); if !*reached_bottom { // We are first time here, it means we just reached bottom. // Verify that we've got proper error and set `reached_bottom`. - assert_matches!(r, Err("reached maximum depth, cannot make a call")); + assert_matches!( + r, + Err(ExecError { reason: "reached maximum depth, cannot make a call", buffer: _ }) + ); *reached_bottom = true; } else { // We just unwinding stack here. assert_matches!(r, Ok(_)); } - VmExecResult::Ok + exec_success() }); with_externalities(&mut ExtBuilder::default().build(), || { @@ -1239,8 +1303,7 @@ mod tests { BOB, value, &mut GasMeter::::with_limit(100000, 1), - &[], - EmptyOutputBuf::new(), + vec![], ); assert_matches!(result, Ok(_)); @@ -1264,16 +1327,15 @@ mod tests { // Call into CHARLIE contract. assert_matches!( - ctx.ext - .call(&CHARLIE, 0, ctx.gas_meter, &[], EmptyOutputBuf::new()), + ctx.ext.call(&CHARLIE, 0, ctx.gas_meter, vec![]), Ok(_) ); - VmExecResult::Ok + exec_success() }); let charlie_ch = loader.insert(|ctx| { // Record the caller for charlie. *witnessed_caller_charlie.borrow_mut() = Some(*ctx.ext.caller()); - VmExecResult::Ok + exec_success() }); with_externalities(&mut ExtBuilder::default().build(), || { @@ -1287,8 +1349,7 @@ mod tests { dest, 0, &mut GasMeter::::with_limit(10000, 1), - &[], - EmptyOutputBuf::new(), + vec![], ); assert_matches!(result, Ok(_)); @@ -1309,15 +1370,14 @@ mod tests { // Call into charlie contract. assert_matches!( - ctx.ext - .call(&CHARLIE, 0, ctx.gas_meter, &[], EmptyOutputBuf::new()), + ctx.ext.call(&CHARLIE, 0, ctx.gas_meter, vec![]), Ok(_) ); - VmExecResult::Ok + exec_success() }); let charlie_ch = loader.insert(|ctx| { assert_eq!(*ctx.ext.address(), CHARLIE); - VmExecResult::Ok + exec_success() }); with_externalities(&mut ExtBuilder::default().build(), || { @@ -1330,8 +1390,7 @@ mod tests { BOB, 0, &mut GasMeter::::with_limit(10000, 1), - &[], - EmptyOutputBuf::new(), + vec![], ); assert_matches!(result, Ok(_)); @@ -1343,7 +1402,7 @@ mod tests { let vm = MockVm::new(); let mut loader = MockLoader::empty(); - let dummy_ch = loader.insert(|_| VmExecResult::Ok); + let dummy_ch = loader.insert(|_| exec_success()); with_externalities( &mut ExtBuilder::default().existential_deposit(15).build(), @@ -1356,7 +1415,7 @@ mod tests { 0, // <- zero endowment &mut GasMeter::::with_limit(10000, 1), &dummy_ch, - &[], + vec![], ), Err(_) ); @@ -1365,11 +1424,13 @@ mod tests { } #[test] - fn instantiation() { + fn instantiation_work_with_success_output() { let vm = MockVm::new(); let mut loader = MockLoader::empty(); - let dummy_ch = loader.insert(|_| VmExecResult::Ok); + let dummy_ch = loader.insert( + |_| Ok(ExecReturnValue { status: STATUS_SUCCESS, data: vec![80, 65, 83, 83] }) + ); with_externalities( &mut ExtBuilder::default().existential_deposit(15).build(), @@ -1383,9 +1444,9 @@ mod tests { 100, &mut GasMeter::::with_limit(10000, 1), &dummy_ch, - &[], + vec![], ), - Ok(InstantiateReceipt { address }) => address + Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address ); // Check that the newly created account has the expected code hash and @@ -1405,29 +1466,60 @@ mod tests { ); } + #[test] + fn instantiation_fails_with_failing_output() { + let vm = MockVm::new(); + + let mut loader = MockLoader::empty(); + let dummy_ch = loader.insert( + |_| Ok(ExecReturnValue { status: 1, data: vec![70, 65, 73, 76] }) + ); + + with_externalities( + &mut ExtBuilder::default().existential_deposit(15).build(), + || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + ctx.overlay.set_balance(&ALICE, 1000); + + let created_contract_address = assert_matches!( + ctx.instantiate( + 100, + &mut GasMeter::::with_limit(10000, 1), + &dummy_ch, + vec![], + ), + Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address + ); + + // Check that the account has not been created. + assert!(ctx.overlay.get_code_hash(&created_contract_address).is_none()); + assert!(ctx.events().is_empty()); + } + ); + } + #[test] fn instantiation_from_contract() { let vm = MockVm::new(); let mut loader = MockLoader::empty(); - let dummy_ch = loader.insert(|_| VmExecResult::Ok); + let dummy_ch = loader.insert(|_| exec_success()); let created_contract_address = Rc::new(RefCell::new(None::)); let creator_ch = loader.insert({ let dummy_ch = dummy_ch.clone(); let created_contract_address = Rc::clone(&created_contract_address); move |ctx| { // Instantiate a contract and save it's address in `created_contract_address`. - *created_contract_address.borrow_mut() = - ctx.ext.instantiate( - &dummy_ch, - 15u64, - ctx.gas_meter, - &[] - ) - .unwrap() - .address.into(); + let (address, output) = ctx.ext.instantiate( + &dummy_ch, + 15u64, + ctx.gas_meter, + vec![] + ).unwrap(); - VmExecResult::Ok + *created_contract_address.borrow_mut() = address.into(); + Ok(output) } }); @@ -1440,7 +1532,7 @@ mod tests { ctx.overlay.create_contract(&BOB, creator_ch).unwrap(); assert_matches!( - ctx.call(BOB, 20, &mut GasMeter::::with_limit(1000, 1), &[], EmptyOutputBuf::new()), + ctx.call(BOB, 20, &mut GasMeter::::with_limit(1000, 1), vec![]), Ok(_) ); @@ -1468,11 +1560,13 @@ mod tests { } #[test] - fn instantiation_fails() { + fn instantiation_traps() { let vm = MockVm::new(); let mut loader = MockLoader::empty(); - let dummy_ch = loader.insert(|_| VmExecResult::Trap("It's a trap!")); + let dummy_ch = loader.insert( + |_| Err(ExecError { reason: "It's a trap!", buffer: Vec::new() }) + ); let creator_ch = loader.insert({ let dummy_ch = dummy_ch.clone(); move |ctx| { @@ -1482,12 +1576,12 @@ mod tests { &dummy_ch, 15u64, ctx.gas_meter, - &[] + vec![] ), - Err("It's a trap!") + Err(ExecError { reason: "It's a trap!", buffer: _ }) ); - VmExecResult::Ok + exec_success() } }); @@ -1500,7 +1594,7 @@ mod tests { ctx.overlay.create_contract(&BOB, creator_ch).unwrap(); assert_matches!( - ctx.call(BOB, 20, &mut GasMeter::::with_limit(1000, 1), &[], EmptyOutputBuf::new()), + ctx.call(BOB, 20, &mut GasMeter::::with_limit(1000, 1), vec![]), Ok(_) ); @@ -1524,7 +1618,7 @@ mod tests { assert_eq!(ctx.ext.rent_allowance(), >::max_value()); ctx.ext.set_rent_allowance(10); assert_eq!(ctx.ext.rent_allowance(), 10); - VmExecResult::Ok + exec_success() }); with_externalities(&mut ExtBuilder::default().build(), || { @@ -1535,7 +1629,7 @@ mod tests { 0, &mut GasMeter::::with_limit(10000, 1), &rent_allowance_ch, - &[], + vec![], ); assert_matches!(result, Ok(_)); }); diff --git a/substrate/srml/contracts/src/lib.rs b/substrate/srml/contracts/src/lib.rs index 15e6d9bc67..9f9fae5292 100644 --- a/substrate/srml/contracts/src/lib.rs +++ b/substrate/srml/contracts/src/lib.rs @@ -89,7 +89,7 @@ mod rent; #[cfg(test)] mod tests; -use crate::exec::ExecutionContext; +use crate::exec::{ExecutionContext, ExecResult}; use crate::account_db::{AccountDb, DirectAccountDb}; pub use crate::gas::{Gas, GasMeter}; use crate::wasm::{WasmLoader, WasmVm}; @@ -560,7 +560,7 @@ decl_module! { let dest = T::Lookup::lookup(dest)?; Self::execute_wasm(origin, gas_limit, |ctx, gas_meter| { - ctx.call(dest, value, gas_meter, &data, exec::EmptyOutputBuf::new()).map(|_| ()) + ctx.call(dest, value, gas_meter, data) }) } @@ -584,7 +584,8 @@ decl_module! { let origin = ensure_signed(origin)?; Self::execute_wasm(origin, gas_limit, |ctx, gas_meter| { - ctx.instantiate(endowment, gas_meter, &code_hash, &data).map(|_| ()) + ctx.instantiate(endowment, gas_meter, &code_hash, data) + .map(|(_address, output)| output) }) } @@ -631,7 +632,7 @@ impl Module { fn execute_wasm( origin: T::AccountId, gas_limit: Gas, - func: impl FnOnce(&mut ExecutionContext, &mut GasMeter) -> Result + func: impl FnOnce(&mut ExecutionContext, &mut GasMeter) -> ExecResult ) -> Result { // Pay for the gas upfront. // @@ -646,7 +647,7 @@ impl Module { let result = func(&mut ctx, &mut gas_meter); - if result.is_ok() { + if result.as_ref().map(|output| output.is_success()).unwrap_or(false) { // Commit all changes that made it thus far into the persistent storage. DirectAccountDb.commit(ctx.overlay.into_change_set()); } @@ -688,6 +689,8 @@ impl Module { }); result + .map(|_| ()) + .map_err(|e| e.reason) } fn restore_to( diff --git a/substrate/srml/contracts/src/tests.rs b/substrate/srml/contracts/src/tests.rs index fa11c4f4df..9c338a5abd 100644 --- a/substrate/srml/contracts/src/tests.rs +++ b/substrate/srml/contracts/src/tests.rs @@ -22,7 +22,8 @@ use crate::account_db::{AccountDb, DirectAccountDb, OverlayAccountDb}; use crate::{ BalanceOf, ComputeDispatchFee, ContractAddressFor, ContractInfo, ContractInfoOf, GenesisConfig, - Module, RawAliveContractInfo, RawEvent, Trait, TrieId, TrieIdFromParentCounter, TrieIdGenerator, + Module, RawAliveContractInfo, RawEvent, Trait, TrieId, TrieIdFromParentCounter, + TrieIdGenerator, Schedule, }; use assert_matches::assert_matches; use hex_literal::*; @@ -279,7 +280,10 @@ impl ExtBuilder { vesting: vec![], }.assimilate_storage(&mut t).unwrap(); GenesisConfig:: { - current_schedule: Default::default(), + current_schedule: Schedule { + enable_println: true, + ..Default::default() + }, gas_price: self.gas_price, }.assimilate_storage(&mut t).unwrap(); runtime_io::TestExternalities::new(t) @@ -698,7 +702,7 @@ const CODE_SET_RENT: &str = r#" (import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32))) (import "env" "ext_set_rent_allowance" (func $ext_set_rent_allowance (param i32 i32))) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) ;; insert a value of 4 bytes into storage @@ -781,7 +785,7 @@ const CODE_SET_RENT: &str = r#" (i32.const 0) (i32.const 4) ) - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 0) (i32.const 0) (get_local $input_size) @@ -1173,7 +1177,7 @@ const CODE_CHECK_DEFAULT_RENT_ALLOWANCE: &str = r#" (module (import "env" "ext_rent_allowance" (func $ext_rent_allowance)) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func $assert (param i32) @@ -1200,7 +1204,7 @@ const CODE_CHECK_DEFAULT_RENT_ALLOWANCE: &str = r#" ) ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 8) ;; Pointer in memory to the place where to copy. (i32.const 0) ;; Offset from the start of the scratch buffer. (i32.const 8) ;; Count of bytes to copy. @@ -1304,10 +1308,10 @@ const CODE_RESTORATION: &str = r#" ;; Address of bob (data (i32.const 256) "\02\00\00\00\00\00\00\00") - ;; Code hash of SET_CODE + ;; Code hash of SET_RENT (data (i32.const 264) - "\69\ae\df\b4\f6\c1\c3\98\e9\7f\8a\52\04\de\0f\95" - "\ad\5e\7d\c3\54\09\60\be\ab\11\a8\6c\56\9f\bf\cf" + "\14\eb\65\3c\86\98\d6\b2\3d\8d\3c\4a\54\c6\c4\71" + "\b9\fc\19\36\df\ca\a0\a1\f2\dc\ad\9d\e5\36\0b\25" ) ;; Rent allowance @@ -1445,6 +1449,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: } else { // Here we expect that the restoration is succeeded. Check that the restoration // contract `DJANGO` ceased to exist and that `BOB` returned back. + println!("{:?}", ContractInfoOf::::get(BOB)); let bob_contract = ContractInfoOf::::get(BOB).unwrap() .get_alive().unwrap(); assert_eq!(bob_contract.rent_allowance, 50); @@ -1462,7 +1467,7 @@ const CODE_STORAGE_SIZE: &str = r#" (import "env" "ext_get_storage" (func $ext_get_storage (param i32) (result i32))) (import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32))) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "memory" (memory 16 16)) (func $assert (param i32) @@ -1484,7 +1489,7 @@ const CODE_STORAGE_SIZE: &str = r#" ) ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 32) ;; Pointer in memory to the place where to copy. (i32.const 0) ;; Offset from the start of the scratch buffer. (i32.const 4) ;; Count of bytes to copy. @@ -1566,3 +1571,357 @@ fn storage_max_value_limit() { } ); } + +const CODE_RETURN_WITH_DATA: &str = r#" +(module + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; Deploy routine is the same as call. + (func (export "deploy") (result i32) + (call $call) + ) + + ;; Call reads the first 4 bytes (LE) as the exit status and returns the rest as output data. + (func $call (export "call") (result i32) + (local $buf_size i32) + (local $exit_status i32) + + ;; Find out the size of the scratch buffer + (set_local $buf_size (call $ext_scratch_size)) + + ;; Copy scratch buffer into this contract memory. + (call $ext_scratch_read + (i32.const 0) ;; The pointer where to store the scratch buffer contents, + (i32.const 0) ;; Offset from the start of the scratch buffer. + (get_local $buf_size) ;; Count of bytes to copy. + ) + + ;; Copy all but the first 4 bytes of the input data as the output data. + (call $ext_scratch_write + (i32.const 4) ;; Offset from the start of the scratch buffer. + (i32.sub ;; Count of bytes to copy. + (get_local $buf_size) + (i32.const 4) + ) + ) + + ;; Return the first 4 bytes of the input data as the exit status. + (i32.load (i32.const 0)) + ) +) +"#; + +const CODE_CALLER_CONTRACT: &str = r#" +(module + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "ext_balance" (func $ext_balance)) + (import "env" "ext_call" (func $ext_call (param i32 i32 i64 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" "ext_println" (func $ext_println (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func $current_balance (param $sp i32) (result i64) + (call $ext_balance) + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 8)) + ) + (call $ext_scratch_read + (i32.sub (get_local $sp) (i32.const 8)) + (i32.const 0) + (i32.const 8) + ) + (i64.load (i32.sub (get_local $sp) (i32.const 8))) + ) + + (func (export "deploy")) + + (func (export "call") + (local $sp i32) + (local $exit_code i32) + (local $balance i64) + + ;; Input data is the code hash of the contract to be deployed. + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 32) + ) + ) + + ;; Copy code hash from scratch buffer into this contract's memory. + (call $ext_scratch_read + (i32.const 24) ;; The pointer where to store the scratch buffer contents, + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 32) ;; Count of bytes to copy. + ) + + ;; Read current balance into local variable. + (set_local $sp (i32.const 1024)) + (set_local $balance + (call $current_balance (get_local $sp)) + ) + + ;; Fail to deploy the contract since it returns a non-zero exit status. + (set_local $exit_code + (call $ext_create + (i32.const 24) ;; Pointer to the code hash. + (i32.const 32) ;; Length of the code hash. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 9) ;; Pointer to input data buffer address + (i32.const 7) ;; Length of input data buffer + ) + ) + + ;; Check non-zero exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x11)) + ) + + ;; Check that scratch buffer is empty since contract instantiation failed. + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 0)) + ) + + ;; Check that balance has not changed. + (call $assert + (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + ) + + ;; Fail to deploy the contract due to insufficient gas. + (set_local $exit_code + (call $ext_create + (i32.const 24) ;; Pointer to the code hash. + (i32.const 32) ;; Length of the code hash. + (i64.const 200) ;; How much gas to devote for the execution. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + ) + ) + + ;; Check for special trap exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x0100)) + ) + + ;; Check that scratch buffer is empty since contract instantiation failed. + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 0)) + ) + + ;; Check that balance has not changed. + (call $assert + (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + ) + + ;; Deploy the contract successfully. + (set_local $exit_code + (call $ext_create + (i32.const 24) ;; Pointer to the code hash. + (i32.const 32) ;; Length of the code hash. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + ) + ) + + ;; Check for success exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x00)) + ) + + ;; Check that scratch buffer contains the address of the new contract. + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 8)) + ) + + ;; Copy contract address from scratch buffer into this contract's memory. + (call $ext_scratch_read + (i32.const 16) ;; The pointer where to store the scratch buffer contents, + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 8) ;; Count of bytes to copy. + ) + + ;; Check that balance has been deducted. + (set_local $balance + (i64.sub (get_local $balance) (i64.load (i32.const 0))) + ) + (call $assert + (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + ) + + ;; Call the new contract and expect it to return failing exit code. + (set_local $exit_code + (call $ext_call + (i32.const 16) ;; 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 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 9) ;; Pointer to input data buffer address + (i32.const 7) ;; Length of input data buffer + ) + ) + + ;; Check non-zero exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x11)) + ) + + ;; Check that scratch buffer contains the expected return data. + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 3)) + ) + (i32.store + (i32.sub (get_local $sp) (i32.const 4)) + (i32.const 0) + ) + (call $ext_scratch_read + (i32.sub (get_local $sp) (i32.const 4)) + (i32.const 0) + (i32.const 3) + ) + (call $assert + (i32.eq + (i32.load (i32.sub (get_local $sp) (i32.const 4))) + (i32.const 0x00776655) + ) + ) + + ;; Check that balance has not changed. + (call $assert + (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + ) + + ;; Fail to call the contract due to insufficient gas. + (set_local $exit_code + (call $ext_call + (i32.const 16) ;; Pointer to "callee" address. + (i32.const 8) ;; Length of "callee" address. + (i64.const 100) ;; How much gas to devote for the execution. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + ) + ) + + ;; Check for special trap exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x0100)) + ) + + ;; Check that scratch buffer is empty since call trapped. + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 0)) + ) + + ;; Check that balance has not changed. + (call $assert + (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + ) + + ;; Call the contract successfully. + (set_local $exit_code + (call $ext_call + (i32.const 16) ;; 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 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + ) + ) + + ;; Check for success exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x00)) + ) + + ;; Check that scratch buffer contains the expected return data. + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 4)) + ) + (i32.store + (i32.sub (get_local $sp) (i32.const 4)) + (i32.const 0) + ) + (call $ext_scratch_read + (i32.sub (get_local $sp) (i32.const 4)) + (i32.const 0) + (i32.const 4) + ) + (call $assert + (i32.eq + (i32.load (i32.sub (get_local $sp) (i32.const 4))) + (i32.const 0x77665544) + ) + ) + + ;; Check that balance has been deducted. + (set_local $balance + (i64.sub (get_local $balance) (i64.load (i32.const 0))) + ) + (call $assert + (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + ) + ) + + (data (i32.const 0) "\00\80") ;; The value to transfer on instantiation and calls. + ;; Chosen to be greater than existential deposit. + (data (i32.const 8) "\00\11\22\33\44\55\66\77") ;; The input data to instantiations and calls. +) +"#; + +#[test] +fn deploy_and_call_other_contract() { + let (callee_wasm, callee_code_hash) = compile_module::(CODE_RETURN_WITH_DATA).unwrap(); + let (caller_wasm, caller_code_hash) = compile_module::(CODE_CALLER_CONTRACT).unwrap(); + + with_externalities( + &mut ExtBuilder::default().existential_deposit(50).build(), + || { + // Create + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, callee_wasm)); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, caller_wasm)); + + assert_ok!(Contract::create( + Origin::signed(ALICE), + 100_000, + 100_000, + caller_code_hash.into(), + vec![], + )); + + // Call BOB contract, which attempts to instantiate and call the callee contract and + // makes various assertions on the results from those calls. + assert_ok!(Contract::call( + Origin::signed(ALICE), + BOB, + 0, + 200_000, + callee_code_hash.as_ref().to_vec(), + )); + } + ); +} diff --git a/substrate/srml/contracts/src/wasm/mod.rs b/substrate/srml/contracts/src/wasm/mod.rs index f1db63870b..99578fee27 100644 --- a/substrate/srml/contracts/src/wasm/mod.rs +++ b/substrate/srml/contracts/src/wasm/mod.rs @@ -19,7 +19,7 @@ use crate::{CodeHash, Schedule, Trait}; use crate::wasm::env_def::FunctionImplProvider; -use crate::exec::{Ext, EmptyOutputBuf, VmExecResult}; +use crate::exec::{Ext, ExecResult}; use crate::gas::GasMeter; use rstd::prelude::*; @@ -110,10 +110,9 @@ impl<'a, T: Trait> crate::exec::Vm for WasmVm<'a> { &self, exec: &WasmExecutable, mut ext: E, - input_data: &[u8], - empty_output_buf: EmptyOutputBuf, + input_data: Vec, gas_meter: &mut GasMeter, - ) -> VmExecResult { + ) -> ExecResult { let memory = sandbox::Memory::new(exec.prefab_module.initial, Some(exec.prefab_module.maximum)) .unwrap_or_else(|_| { @@ -134,38 +133,17 @@ impl<'a, T: Trait> crate::exec::Vm for WasmVm<'a> { let mut runtime = Runtime::new( &mut ext, - input_data.to_vec(), - empty_output_buf, + input_data, &self.schedule, memory, gas_meter, ); - // Instantiate the instance from the instrumented module code. - match sandbox::Instance::new(&exec.prefab_module.code, &imports, &mut runtime) { - // No errors or traps were generated on instantiation! That - // means we can now invoke the contract entrypoint. - Ok(mut instance) => { - let err = instance - .invoke(exec.entrypoint_name, &[], &mut runtime) - .err(); - to_execution_result(runtime, err) - } - // `start` function trapped. Treat it in the same manner as an execution error. - Err(err @ sandbox::Error::Execution) => to_execution_result(runtime, Some(err)), - Err(_err @ sandbox::Error::Module) => { - // `Error::Module` is returned only if instantiation or linking failed (i.e. - // wasm binary tried to import a function that is not provided by the host). - // This shouldn't happen because validation process ought to reject such binaries. - // - // Because panics are really undesirable in the runtime code, we treat this as - // a trap for now. Eventually, we might want to revisit this. - return VmExecResult::Trap("validation error"); - } - // Other instantiation errors. - // Return without executing anything. - Err(_) => return VmExecResult::Trap("during start function"), - } + // Instantiate the instance from the instrumented module code and invoke the contract + // entrypoint. + let result = sandbox::Instance::new(&exec.prefab_module.code, &imports, &mut runtime) + .and_then(|mut instance| instance.invoke(exec.entrypoint_name, &[], &mut runtime)); + to_execution_result(runtime, result) } } @@ -174,16 +152,18 @@ mod tests { use super::*; use std::collections::HashMap; use primitives::H256; - use crate::exec::{CallReceipt, Ext, InstantiateReceipt, EmptyOutputBuf, StorageKey}; + use crate::exec::{Ext, StorageKey, ExecError, ExecReturnValue, STATUS_SUCCESS}; use crate::gas::{Gas, GasMeter}; use crate::tests::{Test, Call}; use crate::wasm::prepare::prepare_contract; use crate::CodeHash; use wabt; use hex_literal::hex; + use assert_matches::assert_matches; #[derive(Debug, PartialEq, Eq)] struct DispatchEntry(Call); + #[derive(Debug, PartialEq, Eq)] struct RestoreEntry { dest: u64, @@ -191,6 +171,7 @@ mod tests { rent_allowance: u64, delta: Vec, } + #[derive(Debug, PartialEq, Eq)] struct CreateEntry { code_hash: H256, @@ -198,6 +179,7 @@ mod tests { data: Vec, gas_left: u64, } + #[derive(Debug, PartialEq, Eq)] struct TransferEntry { to: u64, @@ -205,6 +187,7 @@ mod tests { data: Vec, gas_left: u64, } + #[derive(Default)] pub struct MockExt { storage: HashMap>, @@ -217,6 +200,7 @@ mod tests { events: Vec<(Vec, Vec)>, next_account_id: u64, } + impl Ext for MockExt { type T = Test; @@ -234,8 +218,8 @@ mod tests { code_hash: &CodeHash, endowment: u64, gas_meter: &mut GasMeter, - data: &[u8], - ) -> Result, &'static str> { + data: Vec, + ) -> Result<(u64, ExecReturnValue), ExecError> { self.creates.push(CreateEntry { code_hash: code_hash.clone(), endowment, @@ -245,16 +229,15 @@ mod tests { let address = self.next_account_id; self.next_account_id += 1; - Ok(InstantiateReceipt { address }) + Ok((address, ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() })) } fn call( &mut self, to: &u64, value: u64, gas_meter: &mut GasMeter, - data: &[u8], - _output_data: EmptyOutputBuf, - ) -> Result { + data: Vec, + ) -> ExecResult { self.transfers.push(TransferEntry { to: *to, value, @@ -263,9 +246,7 @@ mod tests { }); // Assume for now that it was just a plain transfer. // TODO: Add tests for different call outcomes. - Ok(CallReceipt { - output_data: Vec::new(), - }) + Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }) } fn note_dispatch_call(&mut self, call: Call) { self.dispatches.push(DispatchEntry(call)); @@ -321,6 +302,7 @@ mod tests { fn max_value_size(&self) -> u32 { 16_384 } } + impl Ext for &mut MockExt { type T = ::T; @@ -337,8 +319,8 @@ mod tests { code: &CodeHash, value: u64, gas_meter: &mut GasMeter, - input_data: &[u8] - ) -> Result, &'static str> { + input_data: Vec, + ) -> Result<(u64, ExecReturnValue), ExecError> { (**self).instantiate(code, value, gas_meter, input_data) } fn call( @@ -346,10 +328,9 @@ mod tests { to: &u64, value: u64, gas_meter: &mut GasMeter, - input_data: &[u8], - empty_output_buf: EmptyOutputBuf - ) -> Result { - (**self).call(to, value, gas_meter, input_data, empty_output_buf) + input_data: Vec, + ) -> ExecResult { + (**self).call(to, value, gas_meter, input_data) } fn note_dispatch_call(&mut self, call: Call) { (**self).note_dispatch_call(call) @@ -405,11 +386,10 @@ mod tests { fn execute( wat: &str, - input_data: &[u8], - output_data: &mut Vec, + input_data: Vec, ext: E, gas_meter: &mut GasMeter, - ) -> Result<(), &'static str> { + ) -> ExecResult { use crate::exec::Vm; let wasm = wabt::wat2wasm(wat).unwrap(); @@ -426,11 +406,7 @@ mod tests { let cfg = Default::default(); let vm = WasmVm::new(&cfg); - *output_data = vm - .execute(&exec, ext, input_data, EmptyOutputBuf::new(), gas_meter) - .into_result()?; - - Ok(()) + vm.execute(&exec, ext, input_data, gas_meter) } const CODE_TRANSFER: &str = r#" @@ -475,14 +451,12 @@ mod tests { #[test] fn contract_transfer() { let mut mock_ext = MockExt::default(); - execute( + let _ = execute( CODE_TRANSFER, - &[], - &mut Vec::new(), + vec![], &mut mock_ext, &mut GasMeter::with_limit(50_000, 1), - ) - .unwrap(); + ).unwrap(); assert_eq!( &mock_ext.transfers, @@ -539,14 +513,12 @@ mod tests { #[test] fn contract_create() { let mut mock_ext = MockExt::default(); - execute( + let _ = execute( CODE_CREATE, - &[], - &mut Vec::new(), + vec![], &mut mock_ext, &mut GasMeter::with_limit(50_000, 1), - ) - .unwrap(); + ).unwrap(); assert_eq!( &mock_ext.creates, @@ -601,14 +573,12 @@ mod tests { #[test] fn contract_call_limited_gas() { let mut mock_ext = MockExt::default(); - execute( + let _ = execute( &CODE_TRANSFER_LIMITED_GAS, - &[], - &mut Vec::new(), + vec![], &mut mock_ext, &mut GasMeter::with_limit(50_000, 1), - ) - .unwrap(); + ).unwrap(); assert_eq!( &mock_ext.transfers, @@ -625,7 +595,7 @@ mod tests { (module (import "env" "ext_get_storage" (func $ext_get_storage (param i32) (result i32))) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "ext_return" (func $ext_return (param i32 i32))) (import "env" "memory" (memory 1 1)) @@ -661,7 +631,7 @@ mod tests { ) ;; Copy scratch buffer into this contract memory. - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 36) ;; The pointer where to store the scratch buffer contents, ;; 36 = 4 + 32 (i32.const 0) ;; Offset from the start of the scratch buffer. @@ -696,17 +666,14 @@ mod tests { .storage .insert([0x11; 32], [0x22; 32].to_vec()); - let mut return_buf = Vec::new(); - execute( + let output = execute( CODE_GET_STORAGE, - &[], - &mut return_buf, + vec![], mock_ext, &mut GasMeter::with_limit(50_000, 1), - ) - .unwrap(); + ).unwrap(); - assert_eq!(return_buf, [0x22; 32].to_vec()); + assert_eq!(output, ExecReturnValue { status: STATUS_SUCCESS, data: [0x22; 32].to_vec() }); } /// calls `ext_caller`, loads the address from the scratch buffer and @@ -715,7 +682,7 @@ mod tests { (module (import "env" "ext_caller" (func $ext_caller)) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func $assert (param i32) @@ -740,7 +707,7 @@ mod tests { ) ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 8) ;; Pointer in memory to the place where to copy. (i32.const 0) ;; Offset from the start of the scratch buffer. (i32.const 8) ;; Count of bytes to copy. @@ -763,14 +730,12 @@ mod tests { #[test] fn caller() { - execute( + let _ = execute( CODE_CALLER, - &[], - &mut Vec::new(), + vec![], MockExt::default(), &mut GasMeter::with_limit(50_000, 1), - ) - .unwrap(); + ).unwrap(); } /// calls `ext_address`, loads the address from the scratch buffer and @@ -779,7 +744,7 @@ mod tests { (module (import "env" "ext_address" (func $ext_address)) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func $assert (param i32) @@ -804,7 +769,7 @@ mod tests { ) ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 8) ;; Pointer in memory to the place where to copy. (i32.const 0) ;; Offset from the start of the scratch buffer. (i32.const 8) ;; Count of bytes to copy. @@ -827,21 +792,19 @@ mod tests { #[test] fn address() { - execute( + let _ = execute( CODE_ADDRESS, - &[], - &mut Vec::new(), + vec![], MockExt::default(), &mut GasMeter::with_limit(50_000, 1), - ) - .unwrap(); + ).unwrap(); } const CODE_BALANCE: &str = r#" (module (import "env" "ext_balance" (func $ext_balance)) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func $assert (param i32) @@ -866,7 +829,7 @@ mod tests { ) ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 8) ;; Pointer in memory to the place where to copy. (i32.const 0) ;; Offset from the start of the scratch buffer. (i32.const 8) ;; Count of bytes to copy. @@ -889,21 +852,19 @@ mod tests { #[test] fn balance() { let mut gas_meter = GasMeter::with_limit(50_000, 1); - execute( + let _ = execute( CODE_BALANCE, - &[], - &mut Vec::new(), + vec![], MockExt::default(), &mut gas_meter, - ) - .unwrap(); + ).unwrap(); } const CODE_GAS_PRICE: &str = r#" (module (import "env" "ext_gas_price" (func $ext_gas_price)) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func $assert (param i32) @@ -928,7 +889,7 @@ mod tests { ) ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 8) ;; Pointer in memory to the place where to copy. (i32.const 0) ;; Offset from the start of the scratch buffer. (i32.const 8) ;; Count of bytes to copy. @@ -951,21 +912,19 @@ mod tests { #[test] fn gas_price() { let mut gas_meter = GasMeter::with_limit(50_000, 1312); - execute( + let _ = execute( CODE_GAS_PRICE, - &[], - &mut Vec::new(), + vec![], MockExt::default(), &mut gas_meter, - ) - .unwrap(); + ).unwrap(); } const CODE_GAS_LEFT: &str = r#" (module (import "env" "ext_gas_left" (func $ext_gas_left)) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "ext_return" (func $ext_return (param i32 i32))) (import "env" "memory" (memory 1 1)) @@ -991,7 +950,7 @@ mod tests { ) ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 8) ;; Pointer in memory to the place where to copy. (i32.const 0) ;; Offset from the start of the scratch buffer. (i32.const 8) ;; Count of bytes to copy. @@ -1012,17 +971,14 @@ mod tests { fn gas_left() { let mut gas_meter = GasMeter::with_limit(50_000, 1312); - let mut return_buf = Vec::new(); - execute( + let output = execute( CODE_GAS_LEFT, - &[], - &mut return_buf, + vec![], MockExt::default(), &mut gas_meter, - ) - .unwrap(); + ).unwrap(); - let gas_left = Gas::decode(&mut &return_buf[..]).unwrap(); + let gas_left = Gas::decode(&mut output.data.as_slice()).unwrap(); assert!(gas_left < 50_000, "gas_left must be less than initial"); assert!(gas_left > gas_meter.gas_left(), "gas_left must be greater than final"); } @@ -1031,7 +987,7 @@ mod tests { (module (import "env" "ext_value_transferred" (func $ext_value_transferred)) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func $assert (param i32) @@ -1056,7 +1012,7 @@ mod tests { ) ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 8) ;; Pointer in memory to the place where to copy. (i32.const 0) ;; Offset from the start of the scratch buffer. (i32.const 8) ;; Count of bytes to copy. @@ -1079,14 +1035,12 @@ mod tests { #[test] fn value_transferred() { let mut gas_meter = GasMeter::with_limit(50_000, 1); - execute( + let _ = execute( CODE_VALUE_TRANSFERRED, - &[], - &mut Vec::new(), + vec![], MockExt::default(), &mut gas_meter, - ) - .unwrap(); + ).unwrap(); } const CODE_DISPATCH_CALL: &str = r#" @@ -1112,14 +1066,12 @@ mod tests { // let's rewrite so as we use this module controlled call or we serialize it in runtime. let mut mock_ext = MockExt::default(); - execute( + let _ = execute( CODE_DISPATCH_CALL, - &[], - &mut Vec::new(), + vec![], &mut mock_ext, &mut GasMeter::with_limit(50_000, 1), - ) - .unwrap(); + ).unwrap(); assert_eq!( &mock_ext.dispatches, @@ -1154,24 +1106,21 @@ mod tests { #[test] fn return_from_start_fn() { - let mut output_data = Vec::new(); - execute( + let output = execute( CODE_RETURN_FROM_START_FN, - &[], - &mut output_data, + vec![], MockExt::default(), &mut GasMeter::with_limit(50_000, 1), - ) - .unwrap(); + ).unwrap(); - assert_eq!(output_data, vec![1, 2, 3, 4]); + assert_eq!(output, ExecReturnValue { status: STATUS_SUCCESS, data: vec![1, 2, 3, 4] }); } const CODE_TIMESTAMP_NOW: &str = r#" (module (import "env" "ext_now" (func $ext_now)) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func $assert (param i32) @@ -1196,7 +1145,7 @@ mod tests { ) ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 8) ;; Pointer in memory to the place where to copy. (i32.const 0) ;; Offset from the start of the scratch buffer. (i32.const 8) ;; Count of bytes to copy. @@ -1219,21 +1168,19 @@ mod tests { #[test] fn now() { let mut gas_meter = GasMeter::with_limit(50_000, 1); - execute( + let _ = execute( CODE_TIMESTAMP_NOW, - &[], - &mut Vec::new(), + vec![], MockExt::default(), &mut gas_meter, - ) - .unwrap(); + ).unwrap(); } const CODE_RANDOM: &str = r#" (module (import "env" "ext_random" (func $ext_random (param i32 i32))) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "ext_return" (func $ext_return (param i32 i32))) (import "env" "memory" (memory 1 1)) @@ -1262,7 +1209,7 @@ mod tests { ) ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 8) ;; Pointer in memory to the place where to copy. (i32.const 0) ;; Offset from the start of the scratch buffer. (i32.const 32) ;; Count of bytes to copy. @@ -1290,20 +1237,20 @@ mod tests { fn random() { let mut gas_meter = GasMeter::with_limit(50_000, 1); - let mut return_buf = Vec::new(); - execute( + let output = execute( CODE_RANDOM, - &[], - &mut return_buf, + vec![], MockExt::default(), &mut gas_meter, - ) - .unwrap(); + ).unwrap(); // The mock ext just returns the same data that was passed as the subject. assert_eq!( - &return_buf, - &hex!("000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F") + output, + ExecReturnValue { + status: STATUS_SUCCESS, + data: hex!("000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F").to_vec(), + }, ); } @@ -1334,14 +1281,12 @@ mod tests { fn deposit_event() { let mut mock_ext = MockExt::default(); let mut gas_meter = GasMeter::with_limit(50_000, 1); - execute( + let _ = execute( CODE_DEPOSIT_EVENT, - &[], - &mut Vec::new(), + vec![], &mut mock_ext, &mut gas_meter - ) - .unwrap(); + ).unwrap(); assert_eq!(mock_ext.events, vec![ (vec![H256::repeat_byte(0x33)], @@ -1387,15 +1332,14 @@ mod tests { // Checks that the runtime traps if there are more than `max_topic_events` topics. let mut gas_meter = GasMeter::with_limit(50_000, 1); - assert_eq!( + assert_matches!( execute( CODE_DEPOSIT_EVENT_MAX_TOPICS, - &[], - &mut Vec::new(), + vec![], MockExt::default(), &mut gas_meter ), - Err("during execution"), + Err(ExecError { reason: "during execution", buffer: _ }) ); } @@ -1430,15 +1374,14 @@ mod tests { // Checks that the runtime traps if there are duplicates. let mut gas_meter = GasMeter::with_limit(50_000, 1); - assert_eq!( + assert_matches!( execute( CODE_DEPOSIT_EVENT_DUPLICATES, - &[], - &mut Vec::new(), + vec![], MockExt::default(), &mut gas_meter ), - Err("during execution"), + Err(ExecError { reason: "during execution", buffer: _ }) ); } @@ -1448,7 +1391,7 @@ mod tests { (module (import "env" "ext_block_number" (func $ext_block_number)) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func $assert (param i32) @@ -1473,7 +1416,7 @@ mod tests { ) ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_copy + (call $ext_scratch_read (i32.const 8) ;; Pointer in memory to the place where to copy. (i32.const 0) ;; Offset from the start of the scratch buffer. (i32.const 8) ;; Count of bytes to copy. @@ -1496,14 +1439,137 @@ mod tests { #[test] fn block_number() { - execute( + let _ = execute( CODE_BLOCK_NUMBER, - &[], - &mut Vec::new(), + vec![], MockExt::default(), &mut GasMeter::with_limit(50_000, 1), - ) - .unwrap(); + ).unwrap(); } + // asserts that the size of the input data is 4. + const CODE_SIMPLE_ASSERT: &str = r#" +(module + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "deploy")) + + (func (export "call") + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 4) + ) + ) + ) +) +"#; + + #[test] + fn output_buffer_capacity_preserved_on_success() { + let mut input_data = Vec::with_capacity(1_234); + input_data.extend_from_slice(&[1, 2, 3, 4][..]); + + let output = execute( + CODE_SIMPLE_ASSERT, + input_data, + MockExt::default(), + &mut GasMeter::with_limit(50_000, 1), + ).unwrap(); + + assert_eq!(output.data.len(), 0); + assert_eq!(output.data.capacity(), 1_234); + } + + #[test] + fn output_buffer_capacity_preserved_on_failure() { + let mut input_data = Vec::with_capacity(1_234); + input_data.extend_from_slice(&[1, 2, 3, 4, 5][..]); + + let error = execute( + CODE_SIMPLE_ASSERT, + input_data, + MockExt::default(), + &mut GasMeter::with_limit(50_000, 1), + ).err().unwrap(); + + assert_eq!(error.buffer.capacity(), 1_234); + } + + const CODE_RETURN_WITH_DATA: &str = r#" +(module + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; Deploy routine is the same as call. + (func (export "deploy") (result i32) + (call $call) + ) + + ;; Call reads the first 4 bytes (LE) as the exit status and returns the rest as output data. + (func $call (export "call") (result i32) + (local $buf_size i32) + (local $exit_status i32) + + ;; Find out the size of the scratch buffer + (set_local $buf_size (call $ext_scratch_size)) + + ;; Copy scratch buffer into this contract memory. + (call $ext_scratch_read + (i32.const 0) ;; The pointer where to store the scratch buffer contents, + (i32.const 0) ;; Offset from the start of the scratch buffer. + (get_local $buf_size) ;; Count of bytes to copy. + ) + + ;; Copy all but the first 4 bytes of the input data as the output data. + (call $ext_scratch_write + (i32.const 4) ;; Offset from the start of the scratch buffer. + (i32.sub ;; Count of bytes to copy. + (get_local $buf_size) + (i32.const 4) + ) + ) + + ;; Return the first 4 bytes of the input data as the exit status. + (i32.load (i32.const 0)) + ) +) +"#; + + #[test] + fn return_with_success_status() { + let output = execute( + CODE_RETURN_WITH_DATA, + hex!("00112233445566778899").to_vec(), + MockExt::default(), + &mut GasMeter::with_limit(50_000, 1), + ).unwrap(); + + assert_eq!(output, ExecReturnValue { status: 0, data: hex!("445566778899").to_vec() }); + assert!(output.is_success()); + } + + #[test] + fn return_with_failure_status() { + let output = execute( + CODE_RETURN_WITH_DATA, + hex!("112233445566778899").to_vec(), + MockExt::default(), + &mut GasMeter::with_limit(50_000, 1), + ).unwrap(); + + assert_eq!(output, ExecReturnValue { status: 17, data: hex!("5566778899").to_vec() }); + assert!(!output.is_success()); + } } diff --git a/substrate/srml/contracts/src/wasm/prepare.rs b/substrate/srml/contracts/src/wasm/prepare.rs index 8dc137fc01..41269772a8 100644 --- a/substrate/srml/contracts/src/wasm/prepare.rs +++ b/substrate/srml/contracts/src/wasm/prepare.rs @@ -227,14 +227,20 @@ impl<'a> ContractModule<'a> { }; // Then check the signature. - // Both "call" and "deploy" has a () -> () function type. + // Both "call" and "deploy" has a [] -> [] or [] -> [i32] function type. + // + // The [] -> [] signature predates the [] -> [i32] signature and is supported for + // backwards compatibility. This will likely be removed once ink! is updated to + // generate modules with the new function signatures. let func_ty_idx = func_entries.get(fn_idx as usize) .ok_or_else(|| "export refers to non-existent function")? .type_ref(); let Type::Function(ref func_ty) = types .get(func_ty_idx as usize) .ok_or_else(|| "function has a non-existent type")?; - if !(func_ty.params().is_empty() && func_ty.return_type().is_none()) { + if !func_ty.params().is_empty() || + !(func_ty.return_type().is_none() || + func_ty.return_type() == Some(ValueType::I32)) { return Err("entry point has wrong signature"); } } diff --git a/substrate/srml/contracts/src/wasm/runtime.rs b/substrate/srml/contracts/src/wasm/runtime.rs index 1fdc1a2c1a..ecc4dfc7fb 100644 --- a/substrate/srml/contracts/src/wasm/runtime.rs +++ b/substrate/srml/contracts/src/wasm/runtime.rs @@ -18,32 +18,34 @@ use crate::{Schedule, Trait, CodeHash, ComputeDispatchFee, BalanceOf}; use crate::exec::{ - Ext, VmExecResult, OutputBuf, EmptyOutputBuf, CallReceipt, InstantiateReceipt, StorageKey, - TopicOf, + Ext, ExecResult, ExecError, ExecReturnValue, StorageKey, TopicOf, STATUS_SUCCESS, }; use crate::gas::{Gas, GasMeter, Token, GasMeterResult, approx_gas_for_balance}; use sandbox; use system; use rstd::prelude::*; +use rstd::convert::TryInto; use rstd::mem; use codec::{Decode, Encode}; use sr_primitives::traits::{Bounded, SaturatedConversion}; +/// The value returned from ext_call and ext_create contract external functions if the call or +/// instantiation traps. This value is chosen as if the execution does not trap, the return value +/// will always be an 8-bit integer, so 0x0100 is the smallest value that could not be returned. +const TRAP_RETURN_CODE: u32 = 0x0100; + /// Enumerates all possible *special* trap conditions. /// /// In this runtime traps used not only for signaling about errors but also /// to just terminate quickly in some cases. enum SpecialTrap { /// Signals that trap was generated in response to call `ext_return` host function. - Return(OutputBuf), + Return(Vec), } /// Can only be used for one call. pub(crate) struct Runtime<'a, E: Ext + 'a> { ext: &'a mut E, - // A VM can return a result only once and only by value. So - // we wrap output buffer to make it possible to take the buffer out. - empty_output_buf: Option, scratch_buf: Vec, schedule: &'a Schedule, memory: sandbox::Memory, @@ -54,14 +56,12 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { pub(crate) fn new( ext: &'a mut E, input_data: Vec, - empty_output_buf: EmptyOutputBuf, schedule: &'a Schedule, memory: sandbox::Memory, gas_meter: &'a mut GasMeter, ) -> Self { Runtime { ext, - empty_output_buf: Some(empty_output_buf), // Put the input data into the scratch buffer immediately. scratch_buf: input_data, schedule, @@ -78,20 +78,42 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { pub(crate) fn to_execution_result( runtime: Runtime, - sandbox_err: Option, -) -> VmExecResult { - // Check the exact type of the error. It could be plain trap or - // special runtime trap the we must recognize. - match (sandbox_err, runtime.special_trap) { + sandbox_result: Result, +) -> ExecResult { + // Special case. The trap was the result of the execution `return` host function. + if let Some(SpecialTrap::Return(data)) = runtime.special_trap { + return Ok(ExecReturnValue { status: STATUS_SUCCESS, data }); + } + + // Check the exact type of the error. + match sandbox_result { // No traps were generated. Proceed normally. - (None, None) => VmExecResult::Ok, - // Special case. The trap was the result of the execution `return` host function. - (Some(sandbox::Error::Execution), Some(SpecialTrap::Return(buf))) => VmExecResult::Returned(buf), + Ok(sandbox::ReturnValue::Unit) => { + let mut buffer = runtime.scratch_buf; + buffer.clear(); + Ok(ExecReturnValue { status: STATUS_SUCCESS, data: buffer }) + } + Ok(sandbox::ReturnValue::Value(sandbox::TypedValue::I32(exit_code))) => { + let status = (exit_code & 0xFF).try_into() + .expect("exit_code is masked into the range of a u8; qed"); + Ok(ExecReturnValue { status, data: runtime.scratch_buf }) + } + // This should never happen as the return type of exported functions should have been + // validated by the code preparation process. However, because panics are really + // undesirable in the runtime code, we treat this as a trap for now. Eventually, we might + // want to revisit this. + Ok(_) => Err(ExecError { reason: "return type error", buffer: runtime.scratch_buf }), + // `Error::Module` is returned only if instantiation or linking failed (i.e. + // wasm binary tried to import a function that is not provided by the host). + // This shouldn't happen because validation process ought to reject such binaries. + // + // Because panics are really undesirable in the runtime code, we treat this as + // a trap for now. Eventually, we might want to revisit this. + Err(sandbox::Error::Module) => + Err(ExecError { reason: "validation error", buffer: runtime.scratch_buf }), // Any other kind of a trap should result in a failure. - (Some(_), _) => VmExecResult::Trap("during execution"), - // Any other case (such as special trap flag without actual trap) signifies - // a logic error. - _ => unreachable!(), + Err(sandbox::Error::Execution) | Err(sandbox::Error::OutOfBounds) => + Err(ExecError { reason: "during execution", buffer: runtime.scratch_buf }), } } @@ -186,14 +208,31 @@ fn read_sandbox_memory( ) -> Result, sandbox::HostError> { charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(len))?; - let mut buf = Vec::new(); - buf.resize(len as usize, 0); - - ctx.memory().get(ptr, &mut buf)?; - + let mut buf = vec![0u8; len as usize]; + ctx.memory.get(ptr, buf.as_mut_slice()).map_err(|_| sandbox::HostError)?; Ok(buf) } +/// Read designated chunk from the sandbox memory into the scratch buffer, consuming an +/// appropriate amount of gas. Resizes the scratch buffer to the specified length on success. +/// +/// Returns `Err` if one of the following conditions occurs: +/// +/// - calculating the gas cost resulted in overflow. +/// - out of gas +/// - requested buffer is not within the bounds of the sandbox memory. +fn read_sandbox_memory_into_scratch( + ctx: &mut Runtime, + ptr: u32, + len: u32, +) -> Result<(), sandbox::HostError> { + charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(len))?; + + ctx.scratch_buf.resize(len as usize, 0); + ctx.memory.get(ptr, ctx.scratch_buf.as_mut_slice()).map_err(|_| sandbox::HostError)?; + Ok(()) +} + /// Read designated chunk from the sandbox memory into the supplied buffer, consuming /// an appropriate amount of gas. /// @@ -209,7 +248,7 @@ fn read_sandbox_memory_into_buf( ) -> Result<(), sandbox::HostError> { charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(buf.len() as u32))?; - ctx.memory().get(ptr, buf).map_err(Into::into) + ctx.memory.get(ptr, buf).map_err(Into::into) } /// Read designated chunk from the sandbox memory, consuming an appropriate amount of @@ -318,8 +357,15 @@ define_env!(Env, , // Make a call to another contract. // - // Returns 0 on the successful execution and puts the result data returned by the callee into - // the scratch buffer. Otherwise, returns a non-zero value and clears the scratch buffer. + // If the called contract runs to completion, then this returns the status code the callee + // returns on exit in the bottom 8 bits of the return value. The top 24 bits are 0s. A status + // code of 0 indicates success, and any other code indicates a failure. On failure, any state + // changes made by the called contract are reverted. The scratch buffer is filled with the + // output data returned by the called contract, even in the case of a failure status. + // + // If the contract traps during execution or otherwise fails to complete successfully, then + // this function clears the scratch buffer and returns 0x0100. As with a failure status, any + // state changes made by the called contract are reverted. // // - callee_ptr: a pointer to the address of the callee contract. // Should be decodable as an `T::AccountId`. Traps otherwise. @@ -344,12 +390,10 @@ define_env!(Env, , read_sandbox_memory_as(ctx, callee_ptr, callee_len)?; let value: BalanceOf<::T> = read_sandbox_memory_as(ctx, value_ptr, value_len)?; - let input_data = read_sandbox_memory(ctx, input_data_ptr, input_data_len)?; - // Grab the scratch buffer and put in its' place an empty one. - // We will use it for creating `EmptyOutputBuf` container for the call. - let scratch_buf = mem::replace(&mut ctx.scratch_buf, Vec::new()); - let empty_output_buf = EmptyOutputBuf::from_spare_vec(scratch_buf); + // Read input data into the scratch buffer, then take ownership of it. + read_sandbox_memory_into_scratch(ctx, input_data_ptr, input_data_len)?; + let input_data = mem::replace(&mut ctx.scratch_buf, Vec::new()); let nested_gas_limit = if gas == 0 { ctx.gas_meter.gas_left() @@ -364,22 +408,25 @@ define_env!(Env, , &callee, value, nested_meter, - &input_data, - empty_output_buf + input_data, ) - .map_err(|_| ()) + .map_err(|err| err.buffer) } // there is not enough gas to allocate for the nested call. - None => Err(()), + None => Err(input_data), } }); match call_outcome { - Ok(CallReceipt { output_data }) => { - ctx.scratch_buf = output_data; - Ok(0) + Ok(output) => { + ctx.scratch_buf = output.data; + Ok(output.status.into()) + }, + Err(buffer) => { + ctx.scratch_buf = buffer; + ctx.scratch_buf.clear(); + Ok(TRAP_RETURN_CODE) }, - Err(_) => Ok(1), } }, @@ -388,6 +435,20 @@ define_env!(Env, , // This function creates an account and executes the constructor defined in the code specified // by the code hash. // + // If the constructor runs to completion, then this returns the status code that the newly + // created contract returns on exit in the bottom 8 bits of the return value. The top 24 bits + // are 0s. A status code of 0 indicates success, and any other code indicates a failure. On + // failure, any state changes made by the called contract are reverted and the contract is not + // instantiated. On a success status, the scratch buffer is filled with the encoded address of + // the newly created contract. In the case of a failure status, the scratch buffer is cleared. + // + // If the contract traps during execution or otherwise fails to complete successfully, then + // this function clears the scratch buffer and returns 0x0100. As with a failure status, any + // state changes made by the called contract are reverted. + + // This function creates an account and executes initializer code. After the execution, + // the returned buffer is saved as the code of the created account. + // // Returns 0 on the successful contract creation and puts the address of the created contract // into the scratch buffer. Otherwise, returns non-zero value and clears the scratch buffer. // @@ -413,10 +474,10 @@ define_env!(Env, , read_sandbox_memory_as(ctx, code_hash_ptr, code_hash_len)?; let value: BalanceOf<::T> = read_sandbox_memory_as(ctx, value_ptr, value_len)?; - let input_data = read_sandbox_memory(ctx, input_data_ptr, input_data_len)?; - // Clear the scratch buffer in any case. - ctx.scratch_buf.clear(); + // Read input data into the scratch buffer, then take ownership of it. + read_sandbox_memory_into_scratch(ctx, input_data_ptr, input_data_len)?; + let input_data = mem::replace(&mut ctx.scratch_buf, Vec::new()); let nested_gas_limit = if gas == 0 { ctx.gas_meter.gas_left() @@ -431,21 +492,30 @@ define_env!(Env, , &code_hash, value, nested_meter, - &input_data + input_data ) - .map_err(|_| ()) + .map_err(|err| err.buffer) } // there is not enough gas to allocate for the nested call. - None => Err(()), + None => Err(input_data), } }); match instantiate_outcome { - Ok(InstantiateReceipt { address }) => { - // Write the address to the scratch buffer. - address.encode_to(&mut ctx.scratch_buf); - Ok(0) + Ok((address, output)) => { + let is_success = output.is_success(); + ctx.scratch_buf = output.data; + ctx.scratch_buf.clear(); + if is_success { + // Write the address to the scratch buffer. + address.encode_to(&mut ctx.scratch_buf); + } + Ok(output.status.into()) + }, + Err(buffer) => { + ctx.scratch_buf = buffer; + ctx.scratch_buf.clear(); + Ok(TRAP_RETURN_CODE) }, - Err(_) => Ok(1), } }, @@ -454,33 +524,11 @@ define_env!(Env, , // // This is the only way to return a data buffer to the caller. ext_return(ctx, data_ptr: u32, data_len: u32) => { - match ctx - .gas_meter - .charge( - ctx.schedule, - RuntimeToken::ReturnData(data_len) - ) - { - GasMeterResult::Proceed => (), - GasMeterResult::OutOfGas => return Err(sandbox::HostError), - } + charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReturnData(data_len))?; + + read_sandbox_memory_into_scratch(ctx, data_ptr, data_len)?; + let output_buf = mem::replace(&mut ctx.scratch_buf, Vec::new()); - let empty_output_buf = ctx - .empty_output_buf - .take() - .expect( - "`empty_output_buf` is taken only here; - `ext_return` traps; - `Runtime` can only be used only for one execution; - qed" - ); - let output_buf = empty_output_buf.fill( - data_len as usize, - |slice_mut| { - // Read the memory at the specified pointer to the provided slice. - ctx.memory.get(data_ptr, slice_mut) - } - )?; ctx.special_trap = Some(SpecialTrap::Return(output_buf)); // The trap mechanism is used to immediately terminate the execution. @@ -658,7 +706,7 @@ define_env!(Env, , // Returns the size of the scratch buffer. // - // For more details on the scratch buffer see `ext_scratch_copy`. + // For more details on the scratch buffer see `ext_scratch_read`. ext_scratch_size(ctx) -> u32 => { Ok(ctx.scratch_buf.len() as u32) }, @@ -669,7 +717,7 @@ define_env!(Env, , // In order to get size of the scratch buffer use `ext_scratch_size`. At the start of contract // execution, the scratch buffer is filled with the input data. Whenever a contract calls // function that uses the scratch buffer the contents of the scratch buffer are overwritten. - ext_scratch_copy(ctx, dest_ptr: u32, offset: u32, len: u32) => { + ext_scratch_read(ctx, dest_ptr: u32, offset: u32, len: u32) => { let offset = offset as usize; if offset > ctx.scratch_buf.len() { // Offset can't be larger than scratch buffer length. @@ -694,6 +742,15 @@ define_env!(Env, , Ok(()) }, + // Copy data from contract memory starting from `src_ptr` with length `len` into the scratch + // buffer. This overwrites the entire scratch buffer and resizes to `len`. Specifying a `len` + // of zero clears the scratch buffer. + // + // This should be used before exiting a call or instantiation in order to set the return data. + ext_scratch_write(ctx, src_ptr: u32, len: u32) => { + read_sandbox_memory_into_scratch(ctx, src_ptr, len) + }, + // Deposit a contract event with the data buffer and optional list of topics. There is a limit // on the maximum number of topics specified by `max_event_topics`. // @@ -720,16 +777,11 @@ define_env!(Env, , let event_data = read_sandbox_memory(ctx, data_ptr, data_len)?; - match ctx - .gas_meter - .charge( - ctx.schedule, - RuntimeToken::DepositEvent(topics.len() as u32, data_len) - ) - { - GasMeterResult::Proceed => (), - GasMeterResult::OutOfGas => return Err(sandbox::HostError), - } + charge_gas( + ctx.gas_meter, + ctx.schedule, + RuntimeToken::DepositEvent(topics.len() as u32, data_len) + )?; ctx.ext.deposit_event(topics, event_data); Ok(())