Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Contract runtime polishing #601

Merged
merged 12 commits into from
Sep 1, 2018
6 changes: 4 additions & 2 deletions substrate/runtime/contract/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,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");
Expand Down Expand Up @@ -83,6 +83,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,
Expand Down Expand Up @@ -112,7 +113,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");
Expand Down Expand Up @@ -151,6 +152,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> {
let exec_result = {
vm::execute(
ctor,
data,
&mut CallContext {
ctx: &mut nested,
_caller: caller,
Expand Down
1 change: 0 additions & 1 deletion substrate/runtime/contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
//!
Expand Down
170 changes: 133 additions & 37 deletions substrate/runtime/contract/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,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.
Expand Down Expand Up @@ -205,10 +218,10 @@ fn contract_transfer() {
assert_eq!(
Staking::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!(
Staking::free_balance(&1),
Expand All @@ -235,20 +248,20 @@ fn contract_transfer_oog() {
Staking::set_free_balance(&1, 11);
Staking::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!(
Staking::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!(Staking::free_balance(&1), 11);

// Transaction level transfer should succeed.
assert_eq!(Staking::free_balance(&1), 14);
// But `ext_call` should not.
assert_eq!(Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 0);
});
}
Expand All @@ -267,20 +280,17 @@ fn contract_transfer_max_depth() {
Staking::set_free_balance(&CONTRACT_SHOULD_TRANSFER_TO, 11);
Staking::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!(
Staking::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!(Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 11);
assert_eq!(Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 14);
});
}

Expand Down Expand Up @@ -330,15 +340,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.
Expand Down Expand Up @@ -377,12 +400,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!(Staking::free_balance(&0), expected_gas_after_create);
assert_eq!(Staking::free_balance(&1), 8);
assert_eq!(Staking::free_balance(&derived_address), 3);
Expand All @@ -393,10 +416,10 @@ fn contract_create() {
assert_eq!(
Staking::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!(Staking::free_balance(&derived_address), 22 - 3);
assert_eq!(Staking::free_balance(&9), 36);
Expand Down Expand Up @@ -428,12 +451,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!(
Staking::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!(Staking::free_balance(&derived_address), 30 + 11);

Expand Down Expand Up @@ -609,3 +632,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());

Staking::set_free_balance(&0, 100_000_000);
Staking::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.
},
);
}
10 changes: 5 additions & 5 deletions substrate/runtime/contract/src/vm/env_def/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

#[macro_export]
macro_rules! convert_args {
() => ([]);
() => (vec![]);
( $( $t:ty ),* ) => ( vec![ $( { use $crate::vm::env_def::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] );
}

Expand Down Expand Up @@ -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::<(), _>(|| {
Expand All @@ -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],
Expand All @@ -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> {
Expand All @@ -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>
},
Expand Down
Loading