mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 18:41:05 +00:00
Contract runtime polishing (#601)
* Rename ext_transfer → ext_call
There are several tests where gas was changed. This is due to shrinking
the name and consequently a size of contracts.
* Fix macros
Specifically, make fixes that allows to use a function without arguments (apart from ctx).
For example:
```rust
ext_return_42(ctx) -> u32 => {
Ok(42)
}
```
Also, add impl ConvertibleToWasm for u64.
* Implement passing input data buffer from top-level
This allows the transaction initiator to pass some input data that can be accessed from the executed contract.
However, this only works for the top-level contract. The contract still cannot pass input data to the callee contract. The reason for that is that API doesn't support this presently.
This will be addressed in the following commits.
* Input data passing thru `ext_call`
`ext_call` now takes a buffer (or more specifically, pointer to the start of and size of a buffer).
This allows contracts to pass input data while calling into other contracts.
* Input data passing thru `ext_create`
Same as previous commit but for `ext_create`.
* Trap on decoding error instead of panicking
* Don't cascade failure in ext_call
Instead, if callee doesn't complete successfuly, return a non-zero status code.
This will prevent cascading traps up to the top-level. Due to this some tests were altered so that they now expect successful transfer instead of a failure.
* Don't cascade failure in ext_create
Same as previous commit but for ext_create.
* Specify how much gas to allot for a call
* Specify how much gas to allot for a create
* Update tests.rs
This commit is contained in:
committed by
Gav Wood
parent
beee3d4092
commit
73ad673404
@@ -47,7 +47,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> {
|
||||
dest: T::AccountId,
|
||||
value: T::Balance,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
_data: &[u8],
|
||||
data: &[u8],
|
||||
) -> Result<CallReceipt, &'static str> {
|
||||
if self.depth == <MaxDepth<T>>::get() as usize {
|
||||
return Err("reached maximum depth, cannot make a call");
|
||||
@@ -82,6 +82,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> {
|
||||
let exec_result = if !dest_code.is_empty() {
|
||||
vm::execute(
|
||||
&dest_code,
|
||||
data,
|
||||
&mut CallContext {
|
||||
ctx: &mut nested,
|
||||
_caller: caller,
|
||||
@@ -111,7 +112,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> {
|
||||
endowment: T::Balance,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
ctor: &[u8],
|
||||
_data: &[u8],
|
||||
data: &[u8],
|
||||
) -> Result<CreateReceipt<T>, &'static str> {
|
||||
if self.depth == <MaxDepth<T>>::get() as usize {
|
||||
return Err("reached maximum depth, cannot create");
|
||||
@@ -150,6 +151,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> {
|
||||
let exec_result = {
|
||||
vm::execute(
|
||||
ctor,
|
||||
data,
|
||||
&mut CallContext {
|
||||
ctx: &mut nested,
|
||||
_caller: caller,
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
//!
|
||||
//! Failures are typically not cascading. That, for example, means that if contract A calls B and B errors
|
||||
//! somehow, then A can decide if it should proceed or error.
|
||||
//! TODO: That is not the case now, since call/create externalities traps on any error now.
|
||||
//!
|
||||
//! # Interaction with the system
|
||||
//!
|
||||
|
||||
@@ -125,15 +125,28 @@ impl ExtBuilder {
|
||||
|
||||
const CODE_TRANSFER: &str = r#"
|
||||
(module
|
||||
;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32)
|
||||
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32)))
|
||||
;; ext_call(
|
||||
;; callee_ptr: u32,
|
||||
;; callee_len: u32,
|
||||
;; gas: u64,
|
||||
;; value_ptr: u32,
|
||||
;; value_len: u32,
|
||||
;; input_data_ptr: u32,
|
||||
;; input_data_len: u32
|
||||
;; ) -> u32
|
||||
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(func (export "call")
|
||||
(call $ext_transfer
|
||||
(i32.const 4) ;; Pointer to "Transfer to" address.
|
||||
(i32.const 8) ;; Length of "Transfer to" address.
|
||||
(i32.const 12) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||
(drop
|
||||
(call $ext_call
|
||||
(i32.const 4) ;; Pointer to "callee" address.
|
||||
(i32.const 8) ;; Length of "callee" address.
|
||||
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||
(i32.const 12) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||
(i32.const 0) ;; Pointer to input data buffer address
|
||||
(i32.const 0) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
)
|
||||
;; Destination AccountId to transfer the funds.
|
||||
@@ -165,10 +178,10 @@ fn contract_transfer() {
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
// 3 - value sent with the transaction
|
||||
// 2 * 6 - gas used by the contract (6) multiplied by gas price (2)
|
||||
// 2 * 10 - gas used by the contract (10) multiplied by gas price (2)
|
||||
// 2 * 135 - base gas fee for call (by transaction)
|
||||
// 2 * 135 - base gas fee for call (by the contract)
|
||||
100_000_000 - 3 - (2 * 6) - (2 * 135) - (2 * 135),
|
||||
100_000_000 - 3 - (2 * 10) - (2 * 135) - (2 * 135),
|
||||
);
|
||||
assert_eq!(
|
||||
Balances::free_balance(&1),
|
||||
@@ -195,20 +208,20 @@ fn contract_transfer_oog() {
|
||||
Balances::set_free_balance(&1, 11);
|
||||
Balances::increase_total_stake_by(11);
|
||||
|
||||
assert_err!(
|
||||
Contract::call(&0, 1, 3, 276, Vec::new()),
|
||||
"vm execute returned error while call"
|
||||
);
|
||||
assert_ok!(Contract::call(&0, 1, 3, 135 + 135 + 7, Vec::new()));
|
||||
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
// 3 - value sent with the transaction
|
||||
// 2 * 6 - gas used by the contract (6) multiplied by gas price (2)
|
||||
// 2 * 7 - gas used by the contract (7) multiplied by gas price (2)
|
||||
// 2 * 135 - base gas fee for call (by transaction)
|
||||
// 2 * 135 - base gas fee for call (by contract)
|
||||
100_000_000 - (2 * 6) - (2 * 135) - (2 * 135),
|
||||
100_000_000 - 3 - (2 * 7) - (2 * 135) - (2 * 135),
|
||||
);
|
||||
assert_eq!(Balances::free_balance(&1), 11);
|
||||
|
||||
// Transaction level transfer should succeed.
|
||||
assert_eq!(Balances::free_balance(&1), 14);
|
||||
// But `ext_call` should not.
|
||||
assert_eq!(Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 0);
|
||||
});
|
||||
}
|
||||
@@ -227,20 +240,17 @@ fn contract_transfer_max_depth() {
|
||||
Balances::set_free_balance(&CONTRACT_SHOULD_TRANSFER_TO, 11);
|
||||
Balances::increase_total_stake_by(11);
|
||||
|
||||
assert_err!(
|
||||
Contract::call(&0, CONTRACT_SHOULD_TRANSFER_TO, 3, 100_000, Vec::new()),
|
||||
"vm execute returned error while call"
|
||||
);
|
||||
assert_ok!(Contract::call(&0, CONTRACT_SHOULD_TRANSFER_TO, 3, 100_000, Vec::new()));
|
||||
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
// 3 - value sent with the transaction
|
||||
// 2 * 6 * 100 - gas used by the contract (6) multiplied by gas price (2)
|
||||
// multiplied by max depth (100).
|
||||
// 2 * 10 * 100 - gas used by the contract (10) multiplied by gas price (2)
|
||||
// multiplied by max depth (100).
|
||||
// 2 * 135 * 100 - base gas fee for call (by transaction) multiplied by max depth (100).
|
||||
100_000_000 - (2 * 135 * 100) - (2 * 6 * 100),
|
||||
100_000_000 - 3 - (2 * 10 * 100) - (2 * 135 * 100),
|
||||
);
|
||||
assert_eq!(Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 11);
|
||||
assert_eq!(Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 14);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -290,15 +300,28 @@ fn code_create(constructor: &[u8]) -> String {
|
||||
format!(
|
||||
r#"
|
||||
(module
|
||||
;; ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32)
|
||||
(import "env" "ext_create" (func $ext_create (param i32 i32 i32 i32)))
|
||||
;; ext_create(
|
||||
;; code_ptr: u32,
|
||||
;; code_len: u32,
|
||||
;; gas: u64,
|
||||
;; value_ptr: u32,
|
||||
;; value_len: u32,
|
||||
;; input_data_ptr: u32,
|
||||
;; input_data_len: u32,
|
||||
;; ) -> u32
|
||||
(import "env" "ext_create" (func $ext_create (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(func (export "call")
|
||||
(call $ext_create
|
||||
(i32.const 12) ;; Pointer to `code`
|
||||
(i32.const {code_len}) ;; Length of `code`
|
||||
(i32.const 4) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer
|
||||
(drop
|
||||
(call $ext_create
|
||||
(i32.const 12) ;; Pointer to `code`
|
||||
(i32.const {code_len}) ;; Length of `code`
|
||||
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||
(i32.const 4) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer
|
||||
(i32.const 0) ;; Pointer to input data buffer address
|
||||
(i32.const 0) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
)
|
||||
;; Amount of value to transfer.
|
||||
@@ -337,12 +360,12 @@ fn contract_create() {
|
||||
);
|
||||
|
||||
// 11 - value sent with the transaction
|
||||
// 2 * 128 - gas spent by the deployer contract (128) multiplied by gas price (2)
|
||||
// 2 * 139 - gas spent by the deployer contract (139) multiplied by gas price (2)
|
||||
// 2 * 135 - base gas fee for call (top level)
|
||||
// 2 * 175 - base gas fee for create (by contract)
|
||||
// ((21 / 2) * 2) - price per account creation
|
||||
let expected_gas_after_create =
|
||||
100_000_000 - 11 - (2 * 128) - (2 * 135) - (2 * 175) - ((21 / 2) * 2);
|
||||
100_000_000 - 11 - (2 * 139) - (2 * 135) - (2 * 175) - ((21 / 2) * 2);
|
||||
assert_eq!(Balances::free_balance(&0), expected_gas_after_create);
|
||||
assert_eq!(Balances::free_balance(&1), 8);
|
||||
assert_eq!(Balances::free_balance(&derived_address), 3);
|
||||
@@ -353,10 +376,10 @@ fn contract_create() {
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
// 22 - value sent with the transaction
|
||||
// (2 * 6) - gas used by the contract
|
||||
// (2 * 10) - gas used by the contract
|
||||
// (2 * 135) - base gas fee for call (top level)
|
||||
// (2 * 135) - base gas fee for call (by transfer contract)
|
||||
expected_gas_after_create - 22 - (2 * 6) - (2 * 135) - (2 * 135),
|
||||
expected_gas_after_create - 22 - (2 * 10) - (2 * 135) - (2 * 135),
|
||||
);
|
||||
assert_eq!(Balances::free_balance(&derived_address), 22 - 3);
|
||||
assert_eq!(Balances::free_balance(&9), 36);
|
||||
@@ -388,12 +411,12 @@ fn top_level_create() {
|
||||
));
|
||||
|
||||
// 11 - value sent with the transaction
|
||||
// (3 * 122) - gas spent by the ctor
|
||||
// (3 * 129) - gas spent by the ctor
|
||||
// (3 * 175) - base gas fee for create (175) (top level) multipled by gas price (3)
|
||||
// ((21 / 3) * 3) - price for contract creation
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
100_000_000 - 11 - (3 * 122) - (3 * 175) - ((21 / 3) * 3)
|
||||
100_000_000 - 11 - (3 * 129) - (3 * 175) - ((21 / 3) * 3)
|
||||
);
|
||||
assert_eq!(Balances::free_balance(&derived_address), 30 + 11);
|
||||
|
||||
@@ -569,3 +592,76 @@ fn block_gas_limit() {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const CODE_INPUT_DATA: &'static str = r#"
|
||||
(module
|
||||
(import "env" "ext_input_size" (func $ext_input_size (result i32)))
|
||||
(import "env" "ext_input_copy" (func $ext_input_copy (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func (export "call")
|
||||
(block $fail
|
||||
;; fail if ext_input_size != 4
|
||||
(br_if $fail
|
||||
(i32.ne
|
||||
(i32.const 4)
|
||||
(call $ext_input_size)
|
||||
)
|
||||
)
|
||||
|
||||
(call $ext_input_copy
|
||||
(i32.const 0)
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
)
|
||||
|
||||
|
||||
(br_if $fail
|
||||
(i32.ne
|
||||
(i32.load8_u (i32.const 0))
|
||||
(i32.const 0)
|
||||
)
|
||||
)
|
||||
(br_if $fail
|
||||
(i32.ne
|
||||
(i32.load8_u (i32.const 1))
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
(br_if $fail
|
||||
(i32.ne
|
||||
(i32.load8_u (i32.const 2))
|
||||
(i32.const 2)
|
||||
)
|
||||
)
|
||||
(br_if $fail
|
||||
(i32.ne
|
||||
(i32.load8_u (i32.const 3))
|
||||
(i32.const 3)
|
||||
)
|
||||
)
|
||||
|
||||
(return)
|
||||
)
|
||||
unreachable
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn input_data() {
|
||||
let code_input_data = wabt::wat2wasm(CODE_INPUT_DATA).unwrap();
|
||||
with_externalities(
|
||||
&mut ExtBuilder::default().build(),
|
||||
|| {
|
||||
<CodeOf<Test>>::insert(1, code_input_data.to_vec());
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
|
||||
assert_ok!(Contract::call(&0, 1, 0, 50_000, vec![0, 1, 2, 3]));
|
||||
|
||||
// all asserts are made within contract code itself.
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! convert_args {
|
||||
() => ([]);
|
||||
() => (vec![]);
|
||||
( $( $t:ty ),* ) => ( vec![ $( { use $crate::vm::env_def::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] );
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ macro_rules! unmarshall_then_body_then_marshall {
|
||||
unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*)
|
||||
});
|
||||
let r = body()?;
|
||||
return Ok(ReturnValue::Value({ use $crate::vm::env_def::ConvertibleToWasm; r.to_typed_value() }))
|
||||
return Ok($crate::sandbox::ReturnValue::Value({ use $crate::vm::env_def::ConvertibleToWasm; r.to_typed_value() }))
|
||||
});
|
||||
( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({
|
||||
let body = $crate::vm::env_def::macros::constrain_closure::<(), _>(|| {
|
||||
@@ -103,7 +103,7 @@ macro_rules! unmarshall_then_body_then_marshall {
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! define_func {
|
||||
( < E: $ext_ty:tt > $name:ident ( $ctx: ident, $($names:ident : $params:ty),*) $(-> $returns:ty)* => $body:tt ) => {
|
||||
( < E: $ext_ty:tt > $name:ident ( $ctx: ident $(, $names:ident : $params:ty)*) $(-> $returns:ty)* => $body:tt ) => {
|
||||
fn $name< E: $ext_ty >(
|
||||
$ctx: &mut $crate::vm::Runtime<E>,
|
||||
args: &[$crate::sandbox::TypedValue],
|
||||
@@ -129,7 +129,7 @@ macro_rules! define_func {
|
||||
/// and reject the code if any imported function has a mismached signature.
|
||||
macro_rules! define_env {
|
||||
( $init_name:ident , < E: $ext_ty:tt > ,
|
||||
$( $name:ident ( $ctx:ident, $( $names:ident : $params:ty ),* )
|
||||
$( $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
|
||||
$( -> $returns:ty )* => $body:tt , )*
|
||||
) => {
|
||||
pub(crate) fn $init_name<E: Ext>() -> HostFunctionSet<E> {
|
||||
@@ -142,7 +142,7 @@ macro_rules! define_env {
|
||||
gen_signature!( ( $( $params ),* ) $( -> $returns )* ),
|
||||
{
|
||||
define_func!(
|
||||
< E: $ext_ty > $name ( $ctx, $( $names : $params ),* ) $( -> $returns )* => $body
|
||||
< E: $ext_ty > $name ( $ctx $(, $names : $params )* ) $( -> $returns )* => $body
|
||||
);
|
||||
$name::<E>
|
||||
},
|
||||
|
||||
@@ -49,7 +49,23 @@ impl ConvertibleToWasm for u32 {
|
||||
TypedValue::I32(self as i32)
|
||||
}
|
||||
fn from_typed_value(v: TypedValue) -> Option<Self> {
|
||||
v.as_i32().map(|v| v as u32)
|
||||
match v {
|
||||
TypedValue::I32(v) => Some(v as u32),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ConvertibleToWasm for u64 {
|
||||
type NativeType = u64;
|
||||
const VALUE_TYPE: ValueType = ValueType::I64;
|
||||
fn to_typed_value(self) -> TypedValue {
|
||||
TypedValue::I64(self as i64)
|
||||
}
|
||||
fn from_typed_value(v: TypedValue) -> Option<Self> {
|
||||
match v {
|
||||
TypedValue::I64(v) => Some(v as u64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,29 +182,34 @@ define_env!(init_env, <E: Ext>,
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// TODO: Rename ext_transfer to ext_call.
|
||||
// ext_transfer(transfer_to_ptr: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32)
|
||||
ext_transfer(ctx, transfer_to_ptr: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) => {
|
||||
let mut transfer_to = Vec::new();
|
||||
transfer_to.resize(transfer_to_len as usize, 0);
|
||||
ctx.memory().get(transfer_to_ptr, &mut transfer_to)?;
|
||||
let transfer_to =
|
||||
<<E as Ext>::T as system::Trait>::AccountId::decode(&mut &transfer_to[..]).unwrap();
|
||||
// ext_call(transfer_to_ptr: u32, transfer_to_len: u32, gas: u64, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32)
|
||||
ext_call(ctx, callee_ptr: u32, callee_len: u32, gas: u64, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) -> u32 => {
|
||||
let mut callee = Vec::new();
|
||||
callee.resize(callee_len as usize, 0);
|
||||
ctx.memory().get(callee_ptr, &mut callee)?;
|
||||
let callee =
|
||||
<<E as Ext>::T as system::Trait>::AccountId::decode(&mut &callee[..])
|
||||
.ok_or_else(|| sandbox::HostError)?;
|
||||
|
||||
let mut value_buf = Vec::new();
|
||||
value_buf.resize(value_len as usize, 0);
|
||||
ctx.memory().get(value_ptr, &mut value_buf)?;
|
||||
let value = BalanceOf::<<E as Ext>::T>::decode(&mut &value_buf[..]).unwrap();
|
||||
let value = BalanceOf::<<E as Ext>::T>::decode(&mut &value_buf[..])
|
||||
.ok_or_else(|| sandbox::HostError)?;
|
||||
|
||||
// TODO: Read input data from memory.
|
||||
let input_data = Vec::new();
|
||||
let mut input_data = Vec::new();
|
||||
input_data.resize(input_data_len as usize, 0u8);
|
||||
ctx.memory().get(input_data_ptr, &mut input_data)?;
|
||||
|
||||
// TODO: Let user to choose how much gas to allocate for the execution.
|
||||
let nested_gas_limit = ctx.gas_meter.gas_left();
|
||||
let nested_gas_limit = if gas == 0 {
|
||||
ctx.gas_meter.gas_left()
|
||||
} else {
|
||||
<<<E as Ext>::T as Trait>::Gas as As<u64>>::sa(gas)
|
||||
};
|
||||
let ext = &mut ctx.ext;
|
||||
let call_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| {
|
||||
match nested_meter {
|
||||
Some(nested_meter) => ext.call(&transfer_to, value, nested_meter, &input_data),
|
||||
Some(nested_meter) => ext.call(&callee, value, nested_meter, &input_data),
|
||||
// there is not enough gas to allocate for the nested call.
|
||||
None => Err(()),
|
||||
}
|
||||
@@ -196,28 +217,40 @@ define_env!(init_env, <E: Ext>,
|
||||
|
||||
match call_outcome {
|
||||
// TODO: Find a way how to pass return_data back to the this sandbox.
|
||||
Ok(CallReceipt { .. }) => Ok(()),
|
||||
// TODO: Return a status code value that can be handled by the caller instead of a trap.
|
||||
Err(_) => Err(sandbox::HostError),
|
||||
Ok(CallReceipt { .. }) => Ok(0),
|
||||
Err(_) => Ok(1),
|
||||
}
|
||||
},
|
||||
|
||||
// ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32)
|
||||
ext_create(ctx, code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32) => {
|
||||
// ext_create(code_ptr: u32, code_len: u32, gas: u64, value_ptr: u32, value_len: u32, input_data_ptr: u32, input_data_len: u32) -> u32
|
||||
ext_create(
|
||||
ctx, code_ptr: u32,
|
||||
code_len: u32,
|
||||
gas: u64,
|
||||
value_ptr: u32,
|
||||
value_len: u32,
|
||||
input_data_ptr: u32,
|
||||
input_data_len: u32
|
||||
) -> u32 => {
|
||||
let mut value_buf = Vec::new();
|
||||
value_buf.resize(value_len as usize, 0);
|
||||
ctx.memory().get(value_ptr, &mut value_buf)?;
|
||||
let value = BalanceOf::<<E as Ext>::T>::decode(&mut &value_buf[..]).unwrap();
|
||||
let value = BalanceOf::<<E as Ext>::T>::decode(&mut &value_buf[..])
|
||||
.ok_or_else(|| sandbox::HostError)?;
|
||||
|
||||
let mut code = Vec::new();
|
||||
code.resize(code_len as usize, 0u8);
|
||||
ctx.memory().get(code_ptr, &mut code)?;
|
||||
|
||||
// TODO: Read input data from the sandbox.
|
||||
let input_data = Vec::new();
|
||||
let mut input_data = Vec::new();
|
||||
input_data.resize(input_data_len as usize, 0u8);
|
||||
ctx.memory().get(input_data_ptr, &mut input_data)?;
|
||||
|
||||
// TODO: Let user to choose how much gas to allocate for the execution.
|
||||
let nested_gas_limit = ctx.gas_meter.gas_left();
|
||||
let nested_gas_limit = if gas == 0 {
|
||||
ctx.gas_meter.gas_left()
|
||||
} else {
|
||||
<<<E as Ext>::T as Trait>::Gas as As<u64>>::sa(gas)
|
||||
};
|
||||
let ext = &mut ctx.ext;
|
||||
let create_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| {
|
||||
match nested_meter {
|
||||
@@ -229,9 +262,8 @@ define_env!(init_env, <E: Ext>,
|
||||
|
||||
match create_outcome {
|
||||
// TODO: Copy an address of the created contract in the sandbox.
|
||||
Ok(CreateReceipt { .. }) => Ok(()),
|
||||
// TODO: Return a status code value that can be handled by the caller instead of a trap.
|
||||
Err(_) => Err(sandbox::HostError),
|
||||
Ok(CreateReceipt { .. }) => Ok(0),
|
||||
Err(_) => Ok(1),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -249,4 +281,33 @@ define_env!(init_env, <E: Ext>,
|
||||
// to the user of this crate.
|
||||
Err(sandbox::HostError)
|
||||
},
|
||||
|
||||
// ext_input_size() -> u32
|
||||
//
|
||||
// Returns size of an input buffer.
|
||||
ext_input_size(ctx) -> u32 => {
|
||||
Ok(ctx.input_data.len() as u32)
|
||||
},
|
||||
|
||||
// ext_input_copy(dest_ptr: u32, offset: u32, len: u32)
|
||||
//
|
||||
// Copy data from an input buffer starting from `offset` with length `len` into the contract memory.
|
||||
// The region at which the data should be put is specified by `dest_ptr`.
|
||||
ext_input_copy(ctx, dest_ptr: u32, offset: u32, len: u32) => {
|
||||
let offset = offset as usize;
|
||||
if offset > ctx.input_data.len() {
|
||||
// Offset can't be larger than input buffer length.
|
||||
return Err(sandbox::HostError);
|
||||
}
|
||||
|
||||
// This can't panic since `offset <= ctx.input_data.len()`.
|
||||
let src = &ctx.input_data[offset..];
|
||||
if src.len() != len as usize {
|
||||
return Err(sandbox::HostError);
|
||||
}
|
||||
|
||||
ctx.memory().set(dest_ptr, src)?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
@@ -121,14 +121,15 @@ enum SpecialTrap {
|
||||
Return(Vec<u8>),
|
||||
}
|
||||
|
||||
pub(crate) struct Runtime<'a, E: Ext + 'a> {
|
||||
pub(crate) struct Runtime<'a, 'data, E: Ext + 'a> {
|
||||
ext: &'a mut E,
|
||||
input_data: &'data [u8],
|
||||
config: &'a Config<E::T>,
|
||||
memory: sandbox::Memory,
|
||||
gas_meter: &'a mut GasMeter<E::T>,
|
||||
special_trap: Option<SpecialTrap>,
|
||||
}
|
||||
impl<'a, E: Ext + 'a> Runtime<'a, E> {
|
||||
impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> {
|
||||
fn memory(&self) -> &sandbox::Memory {
|
||||
&self.memory
|
||||
}
|
||||
@@ -190,11 +191,10 @@ pub struct ExecutionResult {
|
||||
/// Execute the given code as a contract.
|
||||
pub fn execute<'a, E: Ext>(
|
||||
code: &[u8],
|
||||
input_data: &[u8],
|
||||
ext: &'a mut E,
|
||||
gas_meter: &mut GasMeter<E::T>,
|
||||
) -> Result<ExecutionResult, Error> {
|
||||
// TODO: Receive data as an argument
|
||||
|
||||
let config = Config::default();
|
||||
let env = env_def::init_env();
|
||||
|
||||
@@ -211,6 +211,7 @@ pub fn execute<'a, E: Ext>(
|
||||
|
||||
let mut runtime = Runtime {
|
||||
ext,
|
||||
input_data,
|
||||
config: &config,
|
||||
memory,
|
||||
gas_meter,
|
||||
@@ -273,11 +274,14 @@ mod tests {
|
||||
code: Vec<u8>,
|
||||
endowment: u64,
|
||||
data: Vec<u8>,
|
||||
gas_left: u64,
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct TransferEntry {
|
||||
to: u64,
|
||||
value: u64,
|
||||
data: Vec<u8>,
|
||||
gas_left: u64,
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct MockExt {
|
||||
@@ -299,13 +303,14 @@ mod tests {
|
||||
&mut self,
|
||||
code: &[u8],
|
||||
endowment: u64,
|
||||
_gas_meter: &mut GasMeter<Test>,
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
data: &[u8],
|
||||
) -> Result<CreateReceipt<Test>, ()> {
|
||||
self.creates.push(CreateEntry {
|
||||
code: code.to_vec(),
|
||||
endowment,
|
||||
data: data.to_vec(),
|
||||
gas_left: gas_meter.gas_left(),
|
||||
});
|
||||
let address = self.next_account_id;
|
||||
self.next_account_id += 1;
|
||||
@@ -316,10 +321,15 @@ mod tests {
|
||||
&mut self,
|
||||
to: &u64,
|
||||
value: u64,
|
||||
_gas_meter: &mut GasMeter<Test>,
|
||||
_data: &[u8],
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
data: &[u8],
|
||||
) -> Result<CallReceipt, ()> {
|
||||
self.transfers.push(TransferEntry { to: *to, value });
|
||||
self.transfers.push(TransferEntry {
|
||||
to: *to,
|
||||
value,
|
||||
data: data.to_vec(),
|
||||
gas_left: gas_meter.gas_left(),
|
||||
});
|
||||
// Assume for now that it was just a plain transfer.
|
||||
// TODO: Add tests for different call outcomes.
|
||||
Ok(CallReceipt {
|
||||
@@ -330,27 +340,38 @@ mod tests {
|
||||
|
||||
const CODE_TRANSFER: &str = r#"
|
||||
(module
|
||||
;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32)
|
||||
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32)))
|
||||
|
||||
;; ext_call(
|
||||
;; callee_ptr: u32,
|
||||
;; callee_len: u32,
|
||||
;; gas: u64,
|
||||
;; value_ptr: u32,
|
||||
;; value_len: u32,
|
||||
;; input_data_ptr: u32,
|
||||
;; input_data_len: u32
|
||||
;;) -> u32
|
||||
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func (export "call")
|
||||
(call $ext_transfer
|
||||
(i32.const 4) ;; Pointer to "Transfer to" address.
|
||||
(i32.const 8) ;; Length of "Transfer to" address.
|
||||
(i32.const 12) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||
(drop
|
||||
(call $ext_call
|
||||
(i32.const 4) ;; Pointer to "callee" address.
|
||||
(i32.const 8) ;; Length of "callee" address.
|
||||
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||
(i32.const 12) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||
(i32.const 20) ;; Pointer to input data buffer address
|
||||
(i32.const 4) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; Destination AccountId to transfer the funds.
|
||||
;; Represented by u64 (8 bytes long) in little endian.
|
||||
(data (i32.const 4) "\02\00\00\00\00\00\00\00")
|
||||
|
||||
(data (i32.const 4) "\09\00\00\00\00\00\00\00")
|
||||
;; Amount of value to transfer.
|
||||
;; Represented by u64 (8 bytes long) in little endian.
|
||||
(data (i32.const 12) "\06\00\00\00\00\00\00\00")
|
||||
|
||||
(data (i32.const 20) "\01\02\03\04")
|
||||
)
|
||||
"#;
|
||||
|
||||
@@ -361,11 +382,83 @@ mod tests {
|
||||
let mut mock_ext = MockExt::default();
|
||||
execute(
|
||||
&code_transfer,
|
||||
&[],
|
||||
&mut mock_ext,
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(&mock_ext.transfers, &[TransferEntry { to: 2, value: 6 }]);
|
||||
assert_eq!(
|
||||
&mock_ext.transfers,
|
||||
&[TransferEntry {
|
||||
to: 9,
|
||||
value: 6,
|
||||
data: vec![
|
||||
1, 2, 3, 4,
|
||||
],
|
||||
gas_left: 49990,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
const CODE_CREATE: &str = r#"
|
||||
(module
|
||||
;; ext_create(
|
||||
;; code_ptr: u32,
|
||||
;; code_len: u32,
|
||||
;; gas: u64,
|
||||
;; value_ptr: u32,
|
||||
;; value_len: u32,
|
||||
;; input_data_ptr: u32,
|
||||
;; input_data_len: u32,
|
||||
;; ) -> u32
|
||||
(import "env" "ext_create" (func $ext_create (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(func (export "call")
|
||||
(drop
|
||||
(call $ext_create
|
||||
(i32.const 12) ;; Pointer to `code`
|
||||
(i32.const 8) ;; Length of `code`
|
||||
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||
(i32.const 4) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer
|
||||
(i32.const 20) ;; Pointer to input data buffer address
|
||||
(i32.const 4) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
)
|
||||
;; Amount of value to transfer.
|
||||
;; Represented by u64 (8 bytes long) in little endian.
|
||||
(data (i32.const 4) "\03\00\00\00\00\00\00\00")
|
||||
;; Embedded wasm code.
|
||||
(data (i32.const 12) "\00\61\73\6d\01\00\00\00")
|
||||
;; Input data to pass to the contract being created.
|
||||
(data (i32.const 20) "\01\02\03\04")
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn contract_create() {
|
||||
let code_create = wabt::wat2wasm(CODE_CREATE).unwrap();
|
||||
|
||||
let mut mock_ext = MockExt::default();
|
||||
execute(
|
||||
&code_create,
|
||||
&[],
|
||||
&mut mock_ext,
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.creates,
|
||||
&[CreateEntry {
|
||||
code: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00],
|
||||
endowment: 3,
|
||||
data: vec![
|
||||
1, 2, 3, 4,
|
||||
],
|
||||
gas_left: 49990,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
const CODE_MEM: &str = r#"
|
||||
@@ -388,10 +481,73 @@ mod tests {
|
||||
assert_matches!(
|
||||
execute(
|
||||
&code_mem,
|
||||
&[],
|
||||
&mut mock_ext,
|
||||
&mut GasMeter::with_limit(100_000, 1)
|
||||
),
|
||||
Err(_)
|
||||
);
|
||||
}
|
||||
|
||||
const CODE_TRANSFER_LIMITED_GAS: &str = r#"
|
||||
(module
|
||||
;; ext_call(
|
||||
;; callee_ptr: u32,
|
||||
;; callee_len: u32,
|
||||
;; gas: u64,
|
||||
;; value_ptr: u32,
|
||||
;; value_len: u32,
|
||||
;; input_data_ptr: u32,
|
||||
;; input_data_len: u32
|
||||
;;) -> u32
|
||||
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(func (export "call")
|
||||
(drop
|
||||
(call $ext_call
|
||||
(i32.const 4) ;; Pointer to "callee" address.
|
||||
(i32.const 8) ;; Length of "callee" address.
|
||||
(i64.const 228) ;; How much gas to devote for the execution.
|
||||
(i32.const 12) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||
(i32.const 20) ;; Pointer to input data buffer address
|
||||
(i32.const 4) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
)
|
||||
;; Destination AccountId to transfer the funds.
|
||||
;; Represented by u64 (8 bytes long) in little endian.
|
||||
(data (i32.const 4) "\09\00\00\00\00\00\00\00")
|
||||
;; Amount of value to transfer.
|
||||
;; Represented by u64 (8 bytes long) in little endian.
|
||||
(data (i32.const 12) "\06\00\00\00\00\00\00\00")
|
||||
|
||||
(data (i32.const 20) "\01\02\03\04")
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn contract_call_limited_gas() {
|
||||
let code_transfer = wabt::wat2wasm(CODE_TRANSFER_LIMITED_GAS).unwrap();
|
||||
|
||||
let mut mock_ext = MockExt::default();
|
||||
execute(
|
||||
&code_transfer,
|
||||
&[],
|
||||
&mut mock_ext,
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.transfers,
|
||||
&[TransferEntry {
|
||||
to: 9,
|
||||
value: 6,
|
||||
data: vec![
|
||||
1, 2, 3, 4,
|
||||
],
|
||||
gas_left: 228,
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user