From b817763ea97cb7589095df65b881ed29c569d192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Sat, 14 Mar 2020 12:49:22 +0100 Subject: [PATCH] Add ext_transfer call (#5169) * contracts: Add ext_transfer call This call allows contracts to send balance to any account contract or not. Previously, the only way to do that was though ext_call. * Apply suggestions from code review Co-Authored-By: Nikolay Volf * The define_env! macro does not allow for trailing comma * Update frame/contracts/src/exec.rs Co-Authored-By: Nikolay Volf * Bump spec version * Do not use nested gas meter * Use explicit 0 or 1 as return value * Remove superflous intermediate binding Co-authored-by: Nikolay Volf --- substrate/bin/node/runtime/src/lib.rs | 2 +- substrate/frame/contracts/COMPLEXITY.md | 15 ++++ substrate/frame/contracts/src/exec.rs | 34 ++++++++ substrate/frame/contracts/src/wasm/mod.rs | 81 ++++++++++++++++++- substrate/frame/contracts/src/wasm/runtime.rs | 30 +++++++ 5 files changed, 158 insertions(+), 4 deletions(-) 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