diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index ed10357e59..48b9037bf8 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -82,7 +82,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 236, + spec_version: 237, impl_version: 0, apis: RUNTIME_API_VERSIONS, }; diff --git a/substrate/frame/contracts/COMPLEXITY.md b/substrate/frame/contracts/COMPLEXITY.md index ac0b2535f5..1d8a122aa0 100644 --- a/substrate/frame/contracts/COMPLEXITY.md +++ b/substrate/frame/contracts/COMPLEXITY.md @@ -291,6 +291,21 @@ performed. Moreover, the DB read has to be synchronous and no progress can be ma **complexity**: The memory and computing complexity is proportional to the size of the fetched value. This function performs a DB read. +## ext_transfer + +This function receives the following arguments: + +- `account` buffer of a marshaled `AccountId`, +- `value` buffer of a marshaled `Balance`, + +It consists of the following steps: + +1. Loading `account` buffer from the sandbox memory (see sandboxing memory get) and then decoding it. +2. Loading `value` buffer from the sandbox memory and then decoding it. +4. Invoking the executive function `transfer`. + +Loading of `account` and `value` buffers should be charged. This is because the sizes of buffers are specified by the calling code, even though marshaled representations are, essentially, of constant size. This can be fixed by assigning an upper bound for sizes of `AccountId` and `Balance`. + ## ext_call This function receives the following arguments: diff --git a/substrate/frame/contracts/src/exec.rs b/substrate/frame/contracts/src/exec.rs index 53a6c484fc..19d1d52497 100644 --- a/substrate/frame/contracts/src/exec.rs +++ b/substrate/frame/contracts/src/exec.rs @@ -120,6 +120,14 @@ pub trait Ext { input_data: Vec, ) -> Result<(AccountIdOf, ExecReturnValue), ExecError>; + /// Transfer some amount of funds into the specified account. + fn transfer( + &mut self, + to: &AccountIdOf, + value: BalanceOf, + gas_meter: &mut GasMeter, + ) -> Result<(), DispatchError>; + /// Call (possibly transferring some amount of funds) into the specified account. fn call( &mut self, @@ -331,6 +339,23 @@ where } } + /// Transfer balance to `dest` without calling any contract code. + pub fn transfer( + &mut self, + dest: T::AccountId, + value: BalanceOf, + gas_meter: &mut GasMeter + ) -> Result<(), DispatchError> { + transfer( + gas_meter, + TransferCause::Call, + &self.self_account.clone(), + &dest, + value, + self, + ) + } + /// Make a call to the specified address, optionally transferring some funds. pub fn call( &mut self, @@ -706,6 +731,15 @@ where self.ctx.instantiate(endowment, gas_meter, code_hash, input_data) } + fn transfer( + &mut self, + to: &T::AccountId, + value: BalanceOf, + gas_meter: &mut GasMeter, + ) -> Result<(), DispatchError> { + self.ctx.transfer(to.clone(), value, gas_meter) + } + fn call( &mut self, to: &T::AccountId, diff --git a/substrate/frame/contracts/src/wasm/mod.rs b/substrate/frame/contracts/src/wasm/mod.rs index 1ea2067a8d..1b0401a562 100644 --- a/substrate/frame/contracts/src/wasm/mod.rs +++ b/substrate/frame/contracts/src/wasm/mod.rs @@ -244,6 +244,20 @@ mod tests { Ok((address, ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() })) } + fn transfer( + &mut self, + to: &u64, + value: u64, + gas_meter: &mut GasMeter, + ) -> Result<(), DispatchError> { + self.transfers.push(TransferEntry { + to: *to, + value, + data: Vec::new(), + gas_left: gas_meter.gas_left(), + }); + Ok(()) + } fn call( &mut self, to: &u64, @@ -254,7 +268,7 @@ mod tests { self.transfers.push(TransferEntry { to: *to, value, - data: data.to_vec(), + data: data, gas_left: gas_meter.gas_left(), }); // Assume for now that it was just a plain transfer. @@ -357,6 +371,14 @@ mod tests { ) -> Result<(u64, ExecReturnValue), ExecError> { (**self).instantiate(code, value, gas_meter, input_data) } + fn transfer( + &mut self, + to: &u64, + value: u64, + gas_meter: &mut GasMeter, + ) -> Result<(), DispatchError> { + (**self).transfer(to, value, gas_meter) + } fn call( &mut self, to: &u64, @@ -453,6 +475,59 @@ mod tests { } const CODE_TRANSFER: &str = r#" +(module + ;; ext_transfer( + ;; account_ptr: u32, + ;; account_len: u32, + ;; value_ptr: u32, + ;; value_len: u32, + ;;) -> u32 + (import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call") + (drop + (call $ext_transfer + (i32.const 4) ;; Pointer to "account" address. + (i32.const 8) ;; Length of "account" address. + (i32.const 12) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + ) + ) + ) + (func (export "deploy")) + + ;; Destination AccountId to transfer the funds. + ;; Represented by u64 (8 bytes long) in little endian. + (data (i32.const 4) "\07\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) "\99\00\00\00\00\00\00\00") +) +"#; + + #[test] + fn contract_transfer() { + let mut mock_ext = MockExt::default(); + let _ = execute( + CODE_TRANSFER, + vec![], + &mut mock_ext, + &mut GasMeter::with_limit(50_000, 1), + ).unwrap(); + + assert_eq!( + &mock_ext.transfers, + &[TransferEntry { + to: 7, + value: 153, + data: Vec::new(), + gas_left: 49978, + }] + ); + } + + const CODE_CALL: &str = r#" (module ;; ext_call( ;; callee_ptr: u32, @@ -492,10 +567,10 @@ mod tests { "#; #[test] - fn contract_transfer() { + fn contract_call() { let mut mock_ext = MockExt::default(); let _ = execute( - CODE_TRANSFER, + CODE_CALL, vec![], &mut mock_ext, &mut GasMeter::with_limit(50_000, 1), diff --git a/substrate/frame/contracts/src/wasm/runtime.rs b/substrate/frame/contracts/src/wasm/runtime.rs index 82232dde39..2460944687 100644 --- a/substrate/frame/contracts/src/wasm/runtime.rs +++ b/substrate/frame/contracts/src/wasm/runtime.rs @@ -405,6 +405,36 @@ define_env!(Env, , } }, + // Transfer some value to another account. + // + // If the value transfer was succesful zero is returned. Otherwise one is returned. + // The scratch buffer is not touched. The receiver can be a plain account or + // a contract. + // + // - account_ptr: a pointer to the address of the beneficiary account + // Should be decodable as an `T::AccountId`. Traps otherwise. + // - account_len: length of the address buffer. + // - value_ptr: a pointer to the buffer with value, how much value to send. + // Should be decodable as a `T::Balance`. Traps otherwise. + // - value_len: length of the value buffer. + ext_transfer( + ctx, + account_ptr: u32, + account_len: u32, + value_ptr: u32, + value_len: u32 + ) -> u32 => { + let callee: <::T as frame_system::Trait>::AccountId = + read_sandbox_memory_as(ctx, account_ptr, account_len)?; + let value: BalanceOf<::T> = + read_sandbox_memory_as(ctx, value_ptr, value_len)?; + + match ctx.ext.transfer(&callee, value, ctx.gas_meter) { + Ok(_) => Ok(0), + Err(_) => Ok(1), + } + }, + // Make a call to another contract. // // If the called contract runs to completion, then this returns the status code the callee