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 <nikvolf@gmail.com>

* The define_env! macro does not allow for trailing comma

* Update frame/contracts/src/exec.rs

Co-Authored-By: Nikolay Volf <nikvolf@gmail.com>

* 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 <nikvolf@gmail.com>
This commit is contained in:
Alexander Theißen
2020-03-14 12:49:22 +01:00
committed by GitHub
parent 7e2cd0edee
commit b817763ea9
5 changed files with 158 additions and 4 deletions
+1 -1
View File
@@ -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,
};
+15
View File
@@ -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:
+34
View File
@@ -120,6 +120,14 @@ pub trait Ext {
input_data: Vec<u8>,
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError>;
/// Transfer some amount of funds into the specified account.
fn transfer(
&mut self,
to: &AccountIdOf<Self::T>,
value: BalanceOf<Self::T>,
gas_meter: &mut GasMeter<Self::T>,
) -> 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<T>,
gas_meter: &mut GasMeter<T>
) -> 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<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<(), DispatchError> {
self.ctx.transfer(to.clone(), value, gas_meter)
}
fn call(
&mut self,
to: &T::AccountId,
+78 -3
View File
@@ -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<Test>,
) -> 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<Test>,
) -> 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),
@@ -405,6 +405,36 @@ define_env!(Env, <E: Ext>,
}
},
// 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: <<E as Ext>::T as frame_system::Trait>::AccountId =
read_sandbox_memory_as(ctx, account_ptr, account_len)?;
let value: BalanceOf<<E as Ext>::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