Contracts module rejig (#1358)

* Move prepare under code.

* Schedule update

* CodeHash

* create takes code_hash

* pass mem def and use code in vm::execute

* Actually save and load code

* Use T::Hash as CodeHash

* Explicit entrypoint name

* Return code_hash and deposit an Event

* Charge for deployed code with gas.

* ImportSatisfyCheck and FunctionImplProvider

* Progress.

* Use new infrastructure for checking imports

* Rename entrypoint to entrypoint_name

* Use strings instead of a Error enum

* Clean

* WIP

* Fix macro_define_env test.

* Fix vm code tests.

* Remove tests for now.

* Fix borked merge

* Fix build for wasm

* fmt

* Scaffolding for abstracting vm.

* Hook up execution to exec layer.

* Fix vm tests.

* Use schedule directly in WasmLoader

* Implement test language.

* Add input_data test.

* Max depth test

* ext_caller

* Simplify test.

* Add TODO

* Some tests and todos.

* top_level

* Clean.

* Restore a couple of integration tests.

* Add a few comments.

* Add ext_address runtime call.

* Deduplicate caller/self_account

* Add not_exists test.

* Change bool to TransferCause.

* Add address tests.

* Remove output_buf from parameter.

* return from start fn.

* Smart gas meter

* Tracing

* Fix prepare tests.

* Code moving

* Add ExecFeeToken

* Use tokens everywhere.

* Make it compile in no_std.

* Lift all test requirements to TestAuxiliaries

* A minor clean

* First create tests

* Remove unneeded TODO

* Docs.

* Code shuffling

* Rename create → instantiate

* Add test address.

* Code shuffling

* Add base_fee tests.

* rejig the code

* Add some comments

* on_finalise comment

* Move event deposit further

* Update Cargo.lock

* Use crates.io version of pwasm-utils

* Format todo comments

* Fix formatting

* Comments

* EmptyOutputBuf and OutputBuf split.

* Restore code_hash

* Fix node-executor.

* Fix typo

* Fix fmt

* Update srml/contract/src/account_db.rs

Co-Authored-By: pepyakin <s.pepyakin@gmail.com>

* Update srml/contract/src/lib.rs

Co-Authored-By: pepyakin <s.pepyakin@gmail.com>

* Line wraps

* Wrapping macros

* Add _ prefix

* Grumbles

* Doc updates.

* Update srml/contract/src/wasm/mod.rs

Co-Authored-By: pepyakin <s.pepyakin@gmail.com>

* Update srml/contract/src/lib.rs

Co-Authored-By: pepyakin <s.pepyakin@gmail.com>

* Add comment

* Use saturation to signal overflow

* Add prepare_test! macro

* Require deploy function.

* Add entry point tests

* Add comment.

* Rename code → code_cache to better describe

* Get rid of weird match!

* Recompile binaries

* Add comments

* refuse_instantiate_with_value_below_existential_deposit

* Little fix

* Make test more complete

* Clean

* Add integration test for instantiation

* Rebuild runtime.

* Add some tests.

* Attach an issue to a TODO

* Attach another issue

* Apply suggestions from code review

Co-Authored-By: pepyakin <s.pepyakin@gmail.com>

* Update srml/contract/src/exec.rs

Co-Authored-By: pepyakin <s.pepyakin@gmail.com>

* Update srml/contract/src/exec.rs

Co-Authored-By: pepyakin <s.pepyakin@gmail.com>

* Recompile node_runtime
This commit is contained in:
Sergei Pepyakin
2019-01-17 12:01:12 +01:00
committed by GitHub
parent beeacf9cfa
commit c88b44f6db
18 changed files with 2717 additions and 1626 deletions
+181 -16
View File
@@ -14,10 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use {Trait, Module, GasSpent};
use balances;
use runtime_primitives::traits::{As, CheckedMul, CheckedSub, Zero};
use runtime_support::StorageValue;
use balances;
use {GasSpent, Module, Trait};
#[cfg(test)]
use std::{any::Any, fmt::Debug};
#[must_use]
#[derive(Debug, PartialEq, Eq)]
@@ -35,11 +38,54 @@ impl GasMeterResult {
}
}
#[cfg(not(test))]
pub trait TestAuxiliaries {}
#[cfg(not(test))]
impl<T> TestAuxiliaries for T {}
#[cfg(test)]
pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {}
#[cfg(test)]
impl<T: Any + Debug + PartialEq + Eq> TestAuxiliaries for T {}
/// This trait represents a token that can be used for charging `GasMeter`.
/// There is no other way of charging it.
///
/// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added
/// for consistency). If inlined there should be no observable difference compared
/// to a hand-written code.
pub trait Token<T: Trait>: Copy + Clone + TestAuxiliaries {
/// Metadata type, which the token can require for calculating the amount
/// of gas to charge. Can be a some configuration type or
/// just the `()`.
type Metadata;
/// Calculate amount of gas that should be taken by this token.
///
/// This function should be really lightweight and must not fail. It is not
/// expected that implementors will query the storage or do any kinds of heavy operations.
///
/// That said, implementors of this function still can run into overflows
/// while calculating the amount. In this case it is ok to use saturating operations
/// since on overflow they will return `max_value` which should consume all gas.
fn calculate_amount(&self, metadata: &Self::Metadata) -> T::Gas;
}
/// A wrapper around a type-erased trait object of what used to be a `Token`.
#[cfg(test)]
pub struct ErasedToken {
pub description: String,
pub token: Box<dyn Any>,
}
pub struct GasMeter<T: Trait> {
limit: T::Gas,
/// Amount of gas left from initial gas limit. Can reach zero.
gas_left: T::Gas,
gas_price: T::Balance,
#[cfg(test)]
tokens: Vec<ErasedToken>,
}
impl<T: Trait> GasMeter<T> {
#[cfg(test)]
@@ -48,17 +94,37 @@ impl<T: Trait> GasMeter<T> {
limit: gas_limit,
gas_left: gas_limit,
gas_price,
#[cfg(test)]
tokens: Vec::new(),
}
}
/// Account for used gas.
///
/// Amount is calculated by the given `token`.
///
/// Returns `OutOfGas` if there is not enough gas or addition of the specified
/// amount of gas has lead to overflow. On success returns `Proceed`.
///
/// NOTE that `amount` is always consumed, i.e. if there is not enough gas
/// NOTE that amount is always consumed, i.e. if there is not enough gas
/// then the counter will be set to zero.
pub fn charge(&mut self, amount: T::Gas) -> GasMeterResult {
#[inline]
pub fn charge<Tok: Token<T>>(
&mut self,
metadata: &Tok::Metadata,
token: Tok,
) -> GasMeterResult {
#[cfg(test)]
{
// Unconditionally add the token to the storage.
let erased_tok = ErasedToken {
description: format!("{:?}", token),
token: Box::new(token),
};
self.tokens.push(erased_tok);
}
let amount = token.calculate_amount(metadata);
let new_value = match self.gas_left.checked_sub(&amount) {
None => None,
Some(val) if val.is_zero() => None,
@@ -74,18 +140,6 @@ impl<T: Trait> GasMeter<T> {
}
}
/// Account for used gas expressed in balance units.
///
/// Same as [`charge`], but amount to be charged is converted from units of balance to
/// units of gas.
///
/// [`charge`]: #method.charge
pub fn charge_by_balance(&mut self, amount: T::Balance) -> GasMeterResult {
let amount_in_gas: T::Balance = amount / self.gas_price;
let amount_in_gas: T::Gas = <T::Gas as As<T::Balance>>::sa(amount_in_gas);
self.charge(amount_in_gas)
}
/// Allocate some amount of gas and perform some work with
/// a newly created nested gas meter.
///
@@ -108,6 +162,8 @@ impl<T: Trait> GasMeter<T> {
limit: amount,
gas_left: amount,
gas_price: self.gas_price,
#[cfg(test)]
tokens: Vec::new(),
};
let r = f(Some(&mut nested));
@@ -118,6 +174,10 @@ impl<T: Trait> GasMeter<T> {
}
}
pub fn gas_price(&self) -> T::Balance {
self.gas_price
}
/// Returns how much gas left from the initial budget.
pub fn gas_left(&self) -> T::Gas {
self.gas_left
@@ -127,6 +187,11 @@ impl<T: Trait> GasMeter<T> {
fn spent(&self) -> T::Gas {
self.limit - self.gas_left
}
#[cfg(test)]
pub fn tokens(&self) -> &[ErasedToken] {
&self.tokens
}
}
/// Buy the given amount of gas.
@@ -162,6 +227,8 @@ pub fn buy_gas<T: Trait>(
limit: gas_limit,
gas_left: gas_limit,
gas_price,
#[cfg(test)]
tokens: Vec::new(),
})
}
@@ -179,3 +246,101 @@ pub fn refund_unused_gas<T: Trait>(transactor: &T::AccountId, gas_meter: GasMete
<balances::Module<T>>::set_free_balance(transactor, b + refund);
<balances::Module<T>>::increase_total_stake_by(refund);
}
/// A simple utility macro that helps to match against a
/// list of tokens.
#[macro_export]
macro_rules! match_tokens {
($tokens_iter:ident,) => {
};
($tokens_iter:ident, $x:expr, $($rest:tt)*) => {
{
let next = ($tokens_iter).next().unwrap();
let pattern = $x;
// Note that we don't specify the type name directly in this macro,
// we only have some expression $x of some type. At the same time, we
// have an iterator of Box<dyn Any> and to downcast we need to specify
// the type which we want downcast to.
//
// So what we do is we assign `_pattern_typed_next_ref` to the a variable which has
// the required type.
//
// Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes
// rustc infer the type `T` (in `downcast_ref<T: Any>`) to be the same as in $x.
let mut _pattern_typed_next_ref = &pattern;
_pattern_typed_next_ref = match next.token.downcast_ref() {
Some(p) => {
assert_eq!(p, &pattern);
p
}
None => {
panic!("expected type {} got {}", stringify!($x), next.description);
}
};
}
match_tokens!($tokens_iter, $($rest)*);
};
}
#[cfg(test)]
mod tests {
use super::{GasMeter, Token};
use tests::Test;
/// A trivial token that charges 1 unit of gas.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct UnitToken;
impl Token<Test> for UnitToken {
type Metadata = ();
fn calculate_amount(&self, _metadata: &()) -> u64 { 1 }
}
struct DoubleTokenMetadata {
multiplier: u64,
}
/// A simple token that charges for the given amount multipled to
/// a multiplier taken from a given metadata.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct DoubleToken(u64);
impl Token<Test> for DoubleToken {
type Metadata = DoubleTokenMetadata;
fn calculate_amount(&self, metadata: &DoubleTokenMetadata) -> u64 {
// Probably you want to use saturating mul in producation code.
self.0 * metadata.multiplier
}
}
#[test]
fn it_works() {
let gas_meter = GasMeter::<Test>::with_limit(50000, 10);
assert_eq!(gas_meter.gas_left(), 50000);
}
#[test]
fn simple() {
let mut gas_meter = GasMeter::<Test>::with_limit(50000, 10);
let result = gas_meter.charge(&DoubleTokenMetadata { multiplier: 3 }, DoubleToken(10));
assert!(!result.is_out_of_gas());
assert_eq!(gas_meter.gas_left(), 49_970);
assert_eq!(gas_meter.spent(), 30);
assert_eq!(gas_meter.gas_price(), 10);
}
#[test]
fn tracing() {
let mut gas_meter = GasMeter::<Test>::with_limit(50000, 10);
assert!(!gas_meter.charge(&(), UnitToken).is_out_of_gas());
assert!(!gas_meter
.charge(&DoubleTokenMetadata { multiplier: 3 }, DoubleToken(10))
.is_out_of_gas());
let mut tokens = gas_meter.tokens()[0..2].iter();
match_tokens!(tokens, UnitToken, DoubleToken(10),);
}
}