Dispatch Calls to other modules (#1473)

* WIP

* Use system::Origin::Signed as an origin

* Add a vm test for ext_dispatch_call

* Take fee for dispatching a Call

# Conflicts:
#	node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm

* Clean & Rebuild

# Conflicts:
#	node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm

* Dispatch call test.

* Rebuild the runtime.

* Fix the length of the buffer.

* Rebuild binaries.
This commit is contained in:
Sergei Pepyakin
2019-01-22 13:10:14 +01:00
committed by Gav Wood
parent 22b65c9cb0
commit 58cd6530be
11 changed files with 298 additions and 35 deletions
+2
View File
@@ -933,6 +933,7 @@ dependencies = [
name = "sr-version"
version = "0.1.0"
dependencies = [
"impl-serde 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-codec 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-codec-derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1073,6 +1074,7 @@ version = "0.1.0"
dependencies = [
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-codec 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+3 -1
View File
@@ -206,9 +206,11 @@ impl treasury::Trait for Runtime {
}
impl contract::Trait for Runtime {
type Call = Call;
type Event = Event;
type Gas = u64;
type DetermineContractAddress = contract::SimpleAddressDeterminator<Runtime>;
type Event = Event;
type ComputeDispatchFee = contract::DefaultDispatchFeeComputor<Runtime>;
}
impl sudo::Trait for Runtime {
+2
View File
@@ -1025,6 +1025,7 @@ dependencies = [
name = "sr-version"
version = "0.1.0"
dependencies = [
"impl-serde 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-codec 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-codec-derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1451,6 +1452,7 @@ version = "0.1.0"
dependencies = [
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-codec 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+24 -11
View File
@@ -16,14 +16,15 @@
use super::{CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait};
use account_db::{AccountDb, DirectAccountDb, OverlayAccountDb};
use gas::{GasMeter, Token};
use gas::{GasMeter, Token, approx_gas_for_balance};
use balances::{self, EnsureAccountLiquid};
use rstd::prelude::*;
use runtime_primitives::traits::{As, CheckedAdd, CheckedSub, Zero};
use runtime_primitives::traits::{CheckedAdd, CheckedSub, Zero};
pub type BalanceOf<T> = <T as balances::Trait>::Balance;
pub type AccountIdOf<T> = <T as system::Trait>::AccountId;
pub type CallOf<T> = <T as Trait>::Call;
#[cfg_attr(test, derive(Debug))]
pub struct InstantiateReceipt<AccountId> {
@@ -77,12 +78,16 @@ pub trait Ext {
empty_output_buf: EmptyOutputBuf,
) -> Result<CallReceipt, &'static str>;
/// Notes a call dispatch.
fn note_dispatch_call(&mut self, call: CallOf<Self::T>);
/// Returns a reference to the account id of the caller.
fn caller(&self) -> &AccountIdOf<Self::T>;
/// Returns a reference to the account id of the current contract.
fn address(&self) -> &AccountIdOf<Self::T>;
/// Returns the balance of the current contract.
///
/// The `value_transferred` is already added.
@@ -214,6 +219,7 @@ pub struct ExecutionContext<'a, T: Trait + 'a, V, L> {
pub overlay: OverlayAccountDb<'a, T>,
pub depth: usize,
pub events: Vec<Event<T>>,
pub calls: Vec<(T::AccountId, T::Call)>,
pub config: &'a Config<T>,
pub vm: &'a V,
pub loader: &'a L,
@@ -235,6 +241,7 @@ where
depth: 0,
overlay,
events: Vec::new(),
calls: Vec::new(),
config: &cfg,
vm: &vm,
loader: &loader,
@@ -247,6 +254,7 @@ where
self_account: dest,
depth: self.depth + 1,
events: Vec::new(),
calls: Vec::new(),
config: self.config,
vm: self.vm,
loader: self.loader,
@@ -276,7 +284,7 @@ where
let dest_code_hash = self.overlay.get_code(&dest);
let mut output_data = Vec::new();
let (change_set, events) = {
let (change_set, events, calls) = {
let mut overlay = OverlayAccountDb::new(&self.overlay);
let mut nested = self.nested(overlay, dest.clone());
@@ -309,11 +317,12 @@ where
.into_result()?;
}
(nested.overlay.into_change_set(), nested.events)
(nested.overlay.into_change_set(), nested.events, nested.calls)
};
self.overlay.commit(change_set);
self.events.extend(events);
self.calls.extend(calls);
Ok(CallReceipt { output_data })
}
@@ -347,7 +356,7 @@ where
return Err("contract already exists");
}
let (change_set, events) = {
let (change_set, events, calls) = {
let mut overlay = OverlayAccountDb::new(&self.overlay);
overlay.set_code(&dest, Some(code_hash.clone()));
let mut nested = self.nested(overlay, dest.clone());
@@ -381,11 +390,12 @@ where
// Deposit an instantiation event.
nested.events.push(RawEvent::Instantiated(self.self_account.clone(), dest.clone()));
(nested.overlay.into_change_set(), nested.events)
(nested.overlay.into_change_set(), nested.events, nested.calls)
};
self.overlay.commit(change_set);
self.events.extend(events);
self.calls.extend(calls);
Ok(InstantiateReceipt { address: dest })
}
@@ -416,11 +426,7 @@ impl<T: Trait> Token<T> for TransferFeeToken<T::Balance> {
TransferFeeKind::AccountCreate => metadata.account_create_fee,
TransferFeeKind::Transfer => metadata.transfer_fee,
};
let amount_in_gas: T::Balance = balance_fee / self.gas_price;
let amount_in_gas: T::Gas = <T::Gas as As<T::Balance>>::sa(amount_in_gas);
amount_in_gas
approx_gas_for_balance::<T>(self.gas_price, balance_fee)
}
}
@@ -562,6 +568,13 @@ where
.call(to.clone(), value, gas_meter, input_data, empty_output_buf)
}
/// Notes a call dispatch.
fn note_dispatch_call(&mut self, call: CallOf<Self::T>) {
self.ctx.calls.push(
(self.ctx.self_account.clone(), call)
);
}
fn address(&self) -> &T::AccountId {
&self.ctx.self_account
}
+7
View File
@@ -247,6 +247,13 @@ pub fn refund_unused_gas<T: Trait>(transactor: &T::AccountId, gas_meter: GasMete
<balances::Module<T>>::increase_total_stake_by(refund);
}
/// A little handy utility for converting a value in balance units into approximitate value in gas units
/// at the given gas price.
pub fn approx_gas_for_balance<T: Trait>(gas_price: T::Balance, balance: T::Balance) -> T::Gas {
let amount_in_gas: T::Balance = balance / gas_price;
<T::Gas as As<T::Balance>>::sa(amount_in_gas)
}
/// A simple utility macro that helps to match against a
/// list of tokens.
#[macro_export]
+53 -11
View File
@@ -104,26 +104,41 @@ use rstd::prelude::*;
use rstd::marker::PhantomData;
use codec::{Codec, HasCompact};
use runtime_primitives::traits::{Hash, As, SimpleArithmetic,Bounded, StaticLookup};
use runtime_support::dispatch::Result;
use runtime_support::dispatch::{Result, Dispatchable};
use runtime_support::{Parameter, StorageMap, StorageValue, StorageDoubleMap};
use system::ensure_signed;
use system::{ensure_signed, RawOrigin};
use runtime_io::{blake2_256, twox_128};
pub type CodeHash<T> = <T as system::Trait>::Hash;
/// A function that generates an `AccountId` for a contract upon instantiation.
pub trait ContractAddressFor<CodeHash, AccountId: Sized> {
fn contract_address_for(code_hash: &CodeHash, data: &[u8], origin: &AccountId) -> AccountId;
}
/// A function that returns the fee for dispatching a `Call`.
pub trait ComputeDispatchFee<Call, Balance> {
fn compute_dispatch_fee(call: &Call) -> Balance;
}
pub trait Trait: balances::Trait {
/// Function type to get the contract address given the creator.
type DetermineContractAddress: ContractAddressFor<CodeHash<Self>, Self::AccountId>;
/// The outer call dispatch type.
type Call: Parameter + Dispatchable<Origin=<Self as system::Trait>::Origin>;
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
// As<u32> is needed for wasm-utils
type Gas: Parameter + Default + Codec + SimpleArithmetic + Bounded + Copy + As<Self::Balance> + As<u64> + As<u32>;
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
/// A function type to get the contract address given the creator.
type DetermineContractAddress: ContractAddressFor<CodeHash<Self>, Self::AccountId>;
pub trait ContractAddressFor<CodeHash, AccountId: Sized> {
fn contract_address_for(code_hash: &CodeHash, data: &[u8], origin: &AccountId) -> AccountId;
/// A function type that computes the fee for dispatching the given `Call`.
///
/// It is recommended (though not required) for this function to return a fee that would be taken
/// by executive module for regular dispatch.
type ComputeDispatchFee: ComputeDispatchFee<Self::Call, <Self as balances::Trait>::Balance>;
}
/// Simple contract address determintator.
@@ -133,7 +148,6 @@ pub trait ContractAddressFor<CodeHash, AccountId: Sized> {
///
/// Formula: `blake2_256(blake2_256(code) + blake2_256(data) + origin)`
pub struct SimpleAddressDeterminator<T: Trait>(PhantomData<T>);
impl<T: Trait> ContractAddressFor<CodeHash<T>, T::AccountId> for SimpleAddressDeterminator<T>
where
T::AccountId: From<T::Hash> + AsRef<[u8]>
@@ -150,9 +164,21 @@ where
}
}
/// The default dispatch fee computor computes the fee in the same way that
/// implementation of `MakePayment` for balances module does.
pub struct DefaultDispatchFeeComputor<T: Trait>(PhantomData<T>);
impl<T: Trait> ComputeDispatchFee<T::Call, T::Balance> for DefaultDispatchFeeComputor<T> {
fn compute_dispatch_fee(call: &T::Call) -> T::Balance {
let encoded_len = codec::Encode::encode(&call).len();
let base_fee = <balances::Module<T>>::transaction_base_fee();
let byte_fee = <balances::Module<T>>::transaction_byte_fee();
base_fee + byte_fee * <T::Balance as As<u64>>::sa(encoded_len as u64)
}
}
decl_module! {
/// Contracts module.
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
pub struct Module<T: Trait> for enum Call where origin: <T as system::Trait>::Origin {
fn deposit_event<T>() = default;
/// Updates the schedule for metering contracts.
@@ -231,6 +257,12 @@ decl_module! {
// can alter the balance of the caller.
gas::refund_unused_gas::<T>(&origin, gas_meter);
// Dispatch every recorded call with an appropriate origin.
ctx.calls.into_iter().for_each(|(who, call)| {
let result = call.dispatch(RawOrigin::Signed(who.clone()).into());
Self::deposit_event(RawEvent::Dispatched(who, result.is_ok()));
});
result.map(|_| ())
}
@@ -280,6 +312,12 @@ decl_module! {
// can alter the balance of the caller.
gas::refund_unused_gas::<T>(&origin, gas_meter);
// Dispatch every recorded call with an appropriate origin.
ctx.calls.into_iter().for_each(|(who, call)| {
let result = call.dispatch(RawOrigin::Signed(who.clone()).into());
Self::deposit_event(RawEvent::Dispatched(who, result.is_ok()));
});
result.map(|_| ())
}
@@ -307,6 +345,10 @@ decl_event! {
/// Triggered when the current schedule is updated.
ScheduleUpdated(u32),
/// A call was dispatched from the given account. The bool signals whether it was
/// successful execution or not.
Dispatched(AccountId, bool),
}
}
+129 -7
View File
@@ -25,17 +25,13 @@ use runtime_primitives::traits::{BlakeTwo256, IdentityLookup};
use runtime_primitives::BuildStorage;
use runtime_support::{StorageMap, StorageDoubleMap};
use substrate_primitives::{Blake2Hasher};
use system::{Phase, EventRecord};
use system::{ensure_signed, Phase, EventRecord};
use wabt;
use {
balances, runtime_io, system, ContractAddressFor, GenesisConfig, Module, RawEvent, StorageOf,
Trait,
balances, runtime_io, system, ComputeDispatchFee, ContractAddressFor, GenesisConfig, Module, RawEvent, StorageOf,
Trait
};
impl_outer_origin! {
pub enum Origin for Test {}
}
mod contract {
// Re-export contents of the root. This basically
// needs to give a name for the current crate.
@@ -47,6 +43,15 @@ impl_outer_event! {
balances<T>, contract<T>,
}
}
impl_outer_origin! {
pub enum Origin for Test { }
}
impl_outer_dispatch! {
pub enum Call for Test where origin: Origin {
balances::Balances,
contract::Contract,
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct Test;
@@ -71,9 +76,11 @@ impl balances::Trait for Test {
type Event = MetaEvent;
}
impl Trait for Test {
type Call = Call;
type Gas = u64;
type DetermineContractAddress = DummyContractAddressFor;
type Event = MetaEvent;
type ComputeDispatchFee = DummyComputeDispatchFee;
}
type Balances = balances::Module<Test>;
@@ -87,6 +94,13 @@ impl ContractAddressFor<H256, u64> for DummyContractAddressFor {
}
}
pub struct DummyComputeDispatchFee;
impl ComputeDispatchFee<Call, u64> for DummyComputeDispatchFee {
fn compute_dispatch_fee(call: &Call) -> u64 {
69
}
}
const ALICE: u64 = 1;
const BOB: u64 = 2;
const CHARLIE: u64 = 3;
@@ -298,3 +312,111 @@ fn instantiate_and_call() {
},
);
}
const CODE_DISPATCH_CALL: &str = r#"
(module
(import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32)))
(import "env" "memory" (memory 1 1))
(func (export "call")
(call $ext_dispatch_call
(i32.const 8) ;; Pointer to the start of encoded call buffer
(i32.const 11) ;; Length of the buffer
)
)
(func (export "deploy"))
(data (i32.const 8) "\00\00\03\00\00\00\00\00\00\00\C8")
)
"#;
const HASH_DISPATCH_CALL: [u8; 32] = hex!("49dfdcaf9c1553be10634467e95b8e71a3bc15a4f8bf5563c0312b0902e0afb9");
#[test]
fn dispatch_call() {
// This test can fail due to the encoding changes. In case it becomes too annoying
// let's rewrite so as we use this module controlled call or we serialize it in runtime.
let encoded = codec::Encode::encode(&Call::Balances(balances::Call::transfer(CHARLIE, 50.into())));
assert_eq!(&encoded[..], &hex!("00000300000000000000C8")[..]);
let wasm = wabt::wat2wasm(CODE_DISPATCH_CALL).unwrap();
with_externalities(
&mut ExtBuilder::default().existential_deposit(50).build(),
|| {
Balances::set_free_balance(&ALICE, 1_000_000);
Balances::increase_total_stake_by(1_000_000);
assert_ok!(Contract::put_code(
Origin::signed(ALICE),
100_000.into(),
wasm,
));
// Let's keep this assert even though it's redundant. If you ever need to update the
// wasm source this test will fail and will show you the actual hash.
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(HASH_DISPATCH_CALL.into())),
},
]);
assert_ok!(Contract::create(
Origin::signed(ALICE),
100.into(),
100_000.into(),
HASH_DISPATCH_CALL.into(),
vec![],
));
assert_ok!(Contract::call(
Origin::signed(ALICE),
BOB, // newly created account
0.into(),
100_000.into(),
vec![],
));
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(HASH_DISPATCH_CALL.into())),
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(
balances::RawEvent::NewAccount(BOB, 100)
)
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Transfer(ALICE, BOB, 100))
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB))
},
// Dispatching the call.
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(
balances::RawEvent::NewAccount(CHARLIE, 50)
)
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(
balances::RawEvent::Transfer(BOB, CHARLIE, 50, 0)
)
},
// Event emited as a result of dispatch.
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Dispatched(BOB, true))
}
]);
},
);
}
+47 -1
View File
@@ -174,12 +174,15 @@ mod tests {
use std::collections::HashMap;
use substrate_primitives::H256;
use exec::{CallReceipt, Ext, InstantiateReceipt, EmptyOutputBuf};
use balances;
use gas::GasMeter;
use tests::Test;
use tests::{Test, Call};
use wabt;
use wasm::prepare::prepare_contract;
use CodeHash;
#[derive(Debug, PartialEq, Eq)]
struct DispatchEntry(Call);
#[derive(Debug, PartialEq, Eq)]
struct CreateEntry {
code_hash: H256,
@@ -199,6 +202,7 @@ mod tests {
storage: HashMap<Vec<u8>, Vec<u8>>,
creates: Vec<CreateEntry>,
transfers: Vec<TransferEntry>,
dispatches: Vec<DispatchEntry>,
next_account_id: u64,
}
impl Ext for MockExt {
@@ -248,6 +252,9 @@ mod tests {
output_data: Vec::new(),
})
}
fn note_dispatch_call(&mut self, call: Call) {
self.dispatches.push(DispatchEntry(call));
}
fn caller(&self) -> &u64 {
&42
}
@@ -942,6 +949,45 @@ mod tests {
.unwrap();
}
const CODE_DISPATCH_CALL: &str = r#"
(module
(import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32)))
(import "env" "memory" (memory 1 1))
(func (export "call")
(call $ext_dispatch_call
(i32.const 8) ;; Pointer to the start of encoded call buffer
(i32.const 13) ;; Length of the buffer
)
)
(func (export "deploy"))
(data (i32.const 8) "\00\01\2A\00\00\00\00\00\00\00\E5\14\00")
)
"#;
#[test]
fn dispatch_call() {
// This test can fail due to the encoding changes. In case it becomes too annoying
// let's rewrite so as we use this module controlled call or we serialize it in runtime.
let mut mock_ext = MockExt::default();
execute(
CODE_DISPATCH_CALL,
&[],
&mut Vec::new(),
&mut mock_ext,
&mut GasMeter::with_limit(50_000, 1),
)
.unwrap();
assert_eq!(
&mock_ext.dispatches,
&[DispatchEntry(
Call::Balances(balances::Call::set_balance(42, 1337.into(), 0.into())),
)]
);
}
const CODE_RETURN_FROM_START_FN: &str = r#"
(module
+31 -4
View File
@@ -21,11 +21,11 @@ use exec::{Ext, BalanceOf, VmExecResult, OutputBuf, EmptyOutputBuf, CallReceipt,
use rstd::prelude::*;
use rstd::mem;
use codec::{Decode, Encode};
use gas::{GasMeter, Token, GasMeterResult};
use gas::{GasMeter, Token, GasMeterResult, approx_gas_for_balance};
use runtime_primitives::traits::{As, CheckedMul, Bounded};
use sandbox;
use system;
use {Trait, CodeHash};
use {Trait, CodeHash, ComputeDispatchFee};
/// Enumerates all possible *special* trap conditions.
///
@@ -96,7 +96,7 @@ pub(crate) fn to_execution_result<E: Ext>(
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)]
pub enum RuntimeToken {
pub enum RuntimeToken<Gas> {
/// Explicit call to the `gas` function. Charge the gas meter
/// with the value provided.
Explicit(u32),
@@ -107,9 +107,11 @@ pub enum RuntimeToken {
/// The given number of bytes is read from the sandbox memory and
/// is returned as the return data buffer of the call.
ReturnData(u32),
/// Dispatch fee calculated by `T::ComputeDispatchFee`.
ComputedDispatchFee(Gas),
}
impl<T: Trait> Token<T> for RuntimeToken {
impl<T: Trait> Token<T> for RuntimeToken<T::Gas> {
type Metadata = Schedule<T::Gas>;
fn calculate_amount(&self, metadata: &Schedule<T::Gas>) -> T::Gas {
@@ -125,6 +127,7 @@ impl<T: Trait> Token<T> for RuntimeToken {
ReturnData(byte_count) => metadata
.return_data_per_byte_cost
.checked_mul(&<T::Gas as As<u32>>::sa(byte_count)),
ComputedDispatchFee(gas) => Some(gas),
};
value.unwrap_or_else(|| Bounded::max_value())
@@ -482,6 +485,30 @@ define_env!(Env, <E: Ext>,
Ok(())
},
// Decodes the given buffer as a `T::Call` and adds it to the list
// of to-be-dispatched calls.
//
// All calls made it to the top-level context will be dispatched before
// finishing the execution of the calling extrinsic.
ext_dispatch_call(ctx, call_ptr: u32, call_len: u32) => {
let call = {
let call_buf = read_sandbox_memory(ctx, call_ptr, call_len)?;
<<<E as Ext>::T as Trait>::Call>::decode(&mut &call_buf[..])
.ok_or_else(|| sandbox::HostError)?
};
// Charge gas for dispatching this call.
let fee = {
let balance_fee = <<E as Ext>::T as Trait>::ComputeDispatchFee::compute_dispatch_fee(&call);
approx_gas_for_balance::<<E as Ext>::T>(ctx.gas_meter.gas_price(), balance_fee)
};
charge_gas(&mut ctx.gas_meter, ctx.schedule, RuntimeToken::ComputedDispatchFee(fee))?;
ctx.ext.note_dispatch_call(call);
Ok(())
},
// Returns the size of the input buffer.
ext_input_size(ctx) -> u32 => {
Ok(ctx.input_data.len() as u32)