diff --git a/substrate/core/test-runtime/wasm/Cargo.lock b/substrate/core/test-runtime/wasm/Cargo.lock index dd3ee2fea3..127b3d0a02 100644 --- a/substrate/core/test-runtime/wasm/Cargo.lock +++ b/substrate/core/test-runtime/wasm/Cargo.lock @@ -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)", diff --git a/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index 991bf00699..eb4ee4ac71 100644 Binary files a/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index f8cfae1edf..ed8bae6968 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -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; - type Event = Event; + type ComputeDispatchFee = contract::DefaultDispatchFeeComputor; } impl sudo::Trait for Runtime { diff --git a/substrate/node/runtime/wasm/Cargo.lock b/substrate/node/runtime/wasm/Cargo.lock index 0b5a12e7b0..a373a8d7d1 100644 --- a/substrate/node/runtime/wasm/Cargo.lock +++ b/substrate/node/runtime/wasm/Cargo.lock @@ -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)", diff --git a/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm b/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm index cc77de34eb..9a868619d4 100644 Binary files a/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm and b/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm differ diff --git a/substrate/srml/contract/src/exec.rs b/substrate/srml/contract/src/exec.rs index b78c3d7151..af900081d3 100644 --- a/substrate/srml/contract/src/exec.rs +++ b/substrate/srml/contract/src/exec.rs @@ -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 = ::Balance; pub type AccountIdOf = ::AccountId; +pub type CallOf = ::Call; #[cfg_attr(test, derive(Debug))] pub struct InstantiateReceipt { @@ -77,12 +78,16 @@ pub trait Ext { empty_output_buf: EmptyOutputBuf, ) -> Result; + /// Notes a call dispatch. + fn note_dispatch_call(&mut self, call: CallOf); + /// Returns a reference to the account id of the caller. fn caller(&self) -> &AccountIdOf; /// Returns a reference to the account id of the current contract. fn address(&self) -> &AccountIdOf; + /// 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>, + pub calls: Vec<(T::AccountId, T::Call)>, pub config: &'a Config, 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 Token for TransferFeeToken { 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 = >::sa(amount_in_gas); - - amount_in_gas + approx_gas_for_balance::(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.ctx.calls.push( + (self.ctx.self_account.clone(), call) + ); + } + fn address(&self) -> &T::AccountId { &self.ctx.self_account } diff --git a/substrate/srml/contract/src/gas.rs b/substrate/srml/contract/src/gas.rs index afec6dfcb7..61e915672d 100644 --- a/substrate/srml/contract/src/gas.rs +++ b/substrate/srml/contract/src/gas.rs @@ -247,6 +247,13 @@ pub fn refund_unused_gas(transactor: &T::AccountId, gas_meter: GasMete >::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(gas_price: T::Balance, balance: T::Balance) -> T::Gas { + let amount_in_gas: T::Balance = balance / gas_price; + >::sa(amount_in_gas) +} + /// A simple utility macro that helps to match against a /// list of tokens. #[macro_export] diff --git a/substrate/srml/contract/src/lib.rs b/substrate/srml/contract/src/lib.rs index d827b86718..9ea3d5cddd 100644 --- a/substrate/srml/contract/src/lib.rs +++ b/substrate/srml/contract/src/lib.rs @@ -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 = ::Hash; +/// A function that generates an `AccountId` for a contract upon instantiation. +pub trait ContractAddressFor { + 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 { + 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, Self::AccountId>; + /// The outer call dispatch type. + type Call: Parameter + Dispatchable::Origin>; + + /// The overarching event type. + type Event: From> + Into<::Event>; // As is needed for wasm-utils type Gas: Parameter + Default + Codec + SimpleArithmetic + Bounded + Copy + As + As + As; - /// The overarching event type. - type Event: From> + Into<::Event>; -} + /// A function type to get the contract address given the creator. + type DetermineContractAddress: ContractAddressFor, Self::AccountId>; -pub trait ContractAddressFor { - 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::Balance>; } /// Simple contract address determintator. @@ -133,7 +148,6 @@ pub trait ContractAddressFor { /// /// Formula: `blake2_256(blake2_256(code) + blake2_256(data) + origin)` pub struct SimpleAddressDeterminator(PhantomData); - impl ContractAddressFor, T::AccountId> for SimpleAddressDeterminator where T::AccountId: From + 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(PhantomData); +impl ComputeDispatchFee for DefaultDispatchFeeComputor { + fn compute_dispatch_fee(call: &T::Call) -> T::Balance { + let encoded_len = codec::Encode::encode(&call).len(); + let base_fee = >::transaction_base_fee(); + let byte_fee = >::transaction_byte_fee(); + base_fee + byte_fee * >::sa(encoded_len as u64) + } +} + decl_module! { /// Contracts module. - pub struct Module for enum Call where origin: T::Origin { + pub struct Module for enum Call where origin: ::Origin { fn deposit_event() = default; /// Updates the schedule for metering contracts. @@ -231,6 +257,12 @@ decl_module! { // can alter the balance of the caller. gas::refund_unused_gas::(&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::(&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), } } diff --git a/substrate/srml/contract/src/tests.rs b/substrate/srml/contract/src/tests.rs index 542f2c258f..87c99a6753 100644 --- a/substrate/srml/contract/src/tests.rs +++ b/substrate/srml/contract/src/tests.rs @@ -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, contract, } } +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; @@ -87,6 +94,13 @@ impl ContractAddressFor for DummyContractAddressFor { } } +pub struct DummyComputeDispatchFee; +impl ComputeDispatchFee 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)) + } + ]); + }, + ); +} diff --git a/substrate/srml/contract/src/wasm/mod.rs b/substrate/srml/contract/src/wasm/mod.rs index 0682c008be..0633dc3162 100644 --- a/substrate/srml/contract/src/wasm/mod.rs +++ b/substrate/srml/contract/src/wasm/mod.rs @@ -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>, creates: Vec, transfers: Vec, + dispatches: Vec, 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 diff --git a/substrate/srml/contract/src/wasm/runtime.rs b/substrate/srml/contract/src/wasm/runtime.rs index 314bdbf5a1..ce1bf89baa 100644 --- a/substrate/srml/contract/src/wasm/runtime.rs +++ b/substrate/srml/contract/src/wasm/runtime.rs @@ -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( #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[derive(Copy, Clone)] -pub enum RuntimeToken { +pub enum RuntimeToken { /// 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 Token for RuntimeToken { +impl Token for RuntimeToken { type Metadata = Schedule; fn calculate_amount(&self, metadata: &Schedule) -> T::Gas { @@ -125,6 +127,7 @@ impl Token for RuntimeToken { ReturnData(byte_count) => metadata .return_data_per_byte_cost .checked_mul(&>::sa(byte_count)), + ComputedDispatchFee(gas) => Some(gas), }; value.unwrap_or_else(|| Bounded::max_value()) @@ -482,6 +485,30 @@ define_env!(Env, , 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)?; + <<::T as Trait>::Call>::decode(&mut &call_buf[..]) + .ok_or_else(|| sandbox::HostError)? + }; + + // Charge gas for dispatching this call. + let fee = { + let balance_fee = <::T as Trait>::ComputeDispatchFee::compute_dispatch_fee(&call); + approx_gas_for_balance::<::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)