diff --git a/Cargo.lock b/Cargo.lock index ea0d83227f384..f6b2206016927 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2546,6 +2546,7 @@ version = "0.1.0" dependencies = [ "assert_matches 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-codec 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-codec-derive 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "parity-wasm 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", "pwasm-utils 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index e63a904797826..5fa76e6d67794 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -174,6 +174,7 @@ impl treasury::Trait for Runtime { impl contract::Trait for Runtime { type Gas = u64; type DetermineContractAddress = contract::SimpleAddressDeterminator; + type Event = Event; } impl DigestItem for Log { @@ -208,7 +209,7 @@ construct_runtime!( CouncilVoting: council_voting::{Module, Call, Storage, Event}, CouncilMotions: council_motions::{Module, Call, Storage, Event, Origin}, Treasury: treasury, - Contract: contract::{Module, Call, Config}, + Contract: contract::{Module, Call, Config, Event}, } ); diff --git a/node/runtime/wasm/Cargo.lock b/node/runtime/wasm/Cargo.lock index b8e41a4afec68..efd07a478d51a 100644 --- a/node/runtime/wasm/Cargo.lock +++ b/node/runtime/wasm/Cargo.lock @@ -661,6 +661,7 @@ name = "srml-contract" version = "0.1.0" dependencies = [ "parity-codec 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-codec-derive 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "parity-wasm 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", "pwasm-utils 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/srml/contract/Cargo.toml b/srml/contract/Cargo.toml index 9ac17dc616b64..e3707d9c20e66 100644 --- a/srml/contract/Cargo.toml +++ b/srml/contract/Cargo.toml @@ -7,7 +7,8 @@ authors = ["Parity Technologies "] serde = { version = "1.0", default_features = false } serde_derive = { version = "1.0", optional = true } pwasm-utils = { version = "0.3", default_features = false } -parity-codec = { version = "2.0", default_features = false } +parity-codec = { version = "~2.0.1", default_features = false } +parity-codec-derive = { version = "~2.0.1", default-features = false } parity-wasm = { version = "0.31", default_features = false } substrate-primitives = { path = "../../core/primitives", default_features = false } sr-primitives = { path = "../../core/sr-primitives", default_features = false } @@ -28,6 +29,7 @@ std = [ "serde_derive", "serde/std", "parity-codec/std", + "parity-codec-derive/std", "substrate-primitives/std", "sr-primitives/std", "sr-io/std", diff --git a/srml/contract/src/exec.rs b/srml/contract/src/exec.rs index 1ef7a373352c2..79b6e0eee3b69 100644 --- a/srml/contract/src/exec.rs +++ b/srml/contract/src/exec.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use super::{CodeOf, MaxDepth, ContractAddressFor, Module, Trait}; +use super::{CodeOf, MaxDepth, ContractAddressFor, Module, Trait, Event, RawEvent}; use account_db::{AccountDb, OverlayAccountDb}; use gas::GasMeter; use vm; @@ -37,6 +37,7 @@ pub struct ExecutionContext<'a, T: Trait + 'a> { pub self_account: T::AccountId, pub overlay: OverlayAccountDb<'a, T>, pub depth: usize, + pub events: Vec>, } impl<'a, T: Trait> ExecutionContext<'a, T> { @@ -61,9 +62,16 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { let dest_code = >::get(&dest); - let change_set = { + let (change_set, events) = { let mut overlay = OverlayAccountDb::new(&self.overlay); + let mut nested = ExecutionContext { + overlay: overlay, + self_account: dest.clone(), + depth: self.depth + 1, + events: Vec::new(), + }; + if value > T::Balance::zero() { transfer( gas_meter, @@ -71,16 +79,10 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { &self.self_account, &dest, value, - &mut overlay, + &mut nested, )?; } - let mut nested = ExecutionContext { - overlay: overlay, - self_account: dest.clone(), - depth: self.depth + 1, - }; - if !dest_code.is_empty() { vm::execute( &dest_code, @@ -95,10 +97,11 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { ).map_err(|_| "vm execute returned error while call")?; } - nested.overlay.into_change_set() + (nested.overlay.into_change_set(), nested.events) }; self.overlay.commit(change_set); + self.events.extend(events); Ok(CallReceipt) } @@ -126,9 +129,16 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { return Err("contract already exists"); } - let change_set = { + let (change_set, events) = { let mut overlay = OverlayAccountDb::new(&self.overlay); + let mut nested = ExecutionContext { + overlay: overlay, + self_account: dest.clone(), + depth: self.depth + 1, + events: Vec::new(), + }; + if endowment > T::Balance::zero() { transfer( gas_meter, @@ -136,16 +146,10 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { &self.self_account, &dest, endowment, - &mut overlay, + &mut nested, )?; } - let mut nested = ExecutionContext { - overlay: overlay, - self_account: dest.clone(), - depth: self.depth + 1, - }; - let mut contract_code = Vec::new(); vm::execute( init_code, @@ -160,10 +164,11 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { ).map_err(|_| "vm execute returned error while create")?; nested.overlay.set_code(&dest, contract_code); - nested.overlay.into_change_set() + (nested.overlay.into_change_set(), nested.events) }; self.overlay.commit(change_set); + self.events.extend(events); Ok(CreateReceipt { address: dest, @@ -183,15 +188,15 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { /// Note, that the fee is denominated in `T::Balance` units, but /// charged in `T::Gas` from the provided `gas_meter`. This means /// that the actual amount charged might differ. -fn transfer( +fn transfer<'a, T: Trait>( gas_meter: &mut GasMeter, contract_create: bool, transactor: &T::AccountId, dest: &T::AccountId, value: T::Balance, - overlay: &mut OverlayAccountDb, + ctx: &mut ExecutionContext<'a, T>, ) -> Result<(), &'static str> { - let would_create = overlay.get_balance(dest).is_zero(); + let would_create = ctx.overlay.get_balance(dest).is_zero(); let fee: T::Balance = if contract_create { >::contract_fee() @@ -207,7 +212,7 @@ fn transfer( return Err("not enough gas to pay transfer fee"); } - let from_balance = overlay.get_balance(transactor); + let from_balance = ctx.overlay.get_balance(transactor); let new_from_balance = match from_balance.checked_sub(&value) { Some(b) => b, None => return Err("balance too low to send value"), @@ -217,15 +222,16 @@ fn transfer( } ::EnsureAccountLiquid::ensure_account_liquid(transactor)?; - let to_balance = overlay.get_balance(dest); + let to_balance = ctx.overlay.get_balance(dest); let new_to_balance = match to_balance.checked_add(&value) { Some(b) => b, None => return Err("destination balance too high to receive value"), }; if transactor != dest { - overlay.set_balance(transactor, new_from_balance); - overlay.set_balance(dest, new_to_balance); + ctx.overlay.set_balance(transactor, new_from_balance); + ctx.overlay.set_balance(dest, new_to_balance); + ctx.events.push(RawEvent::Transfer(transactor.clone(), dest.clone(), value)); } Ok(()) diff --git a/srml/contract/src/lib.rs b/srml/contract/src/lib.rs index a332fa6f9bd3b..43653bd26d067 100644 --- a/srml/contract/src/lib.rs +++ b/srml/contract/src/lib.rs @@ -59,6 +59,9 @@ extern crate serde_derive; #[cfg(feature = "std")] extern crate serde; +#[macro_use] +extern crate parity_codec_derive; + extern crate parity_wasm; extern crate pwasm_utils; @@ -116,6 +119,9 @@ pub trait Trait: balances::Trait { // As is needed for wasm-utils type Gas: Parameter + Default + Codec + SimpleArithmetic + Copy + As + As + As; + + /// The overarching event type. + type Event: From> + Into<::Event>; } pub trait ContractAddressFor { @@ -169,6 +175,17 @@ decl_module! { } } +decl_event! { + pub enum Event + where + ::Balance, + ::AccountId + { + /// Transfer happened `from` -> `to` with given `value` as part of a `message-call` or `create`. + Transfer(AccountId, AccountId, Balance), + } +} + decl_storage! { trait Store for Module as Contract { /// The fee required to create a contract. At least as big as staking's ReclaimRebate. @@ -206,6 +223,11 @@ impl double_map::StorageDoubleMap for StorageOf { } impl Module { + /// Deposit one of this module's events. + fn deposit_event(event: Event) { + >::deposit_event(::Event::from(event).into()); + } + /// Make a call to a specified account, optionally transferring some balance. fn call( origin: ::Origin, @@ -226,6 +248,7 @@ impl Module { self_account: origin.clone(), depth: 0, overlay: OverlayAccountDb::::new(&account_db::DirectAccountDb), + events: Vec::new(), }; let mut output_data = Vec::new(); @@ -234,6 +257,9 @@ impl Module { if let Ok(_) = result { // Commit all changes that made it thus far into the persistant storage. account_db::DirectAccountDb.commit(ctx.overlay.into_change_set()); + + // Then deposit all events produced. + ctx.events.into_iter().for_each(Self::deposit_event); } // Refund cost of the unused gas. @@ -273,12 +299,16 @@ impl Module { self_account: origin.clone(), depth: 0, overlay: OverlayAccountDb::::new(&account_db::DirectAccountDb), + events: Vec::new(), }; let result = ctx.create(origin.clone(), endowment, &mut gas_meter, &ctor_code, &data); if let Ok(_) = result { // Commit all changes that made it thus far into the persistant storage. account_db::DirectAccountDb.commit(ctx.overlay.into_change_set()); + + // Then deposit all events produced. + ctx.events.into_iter().for_each(Self::deposit_event); } // Refund cost of the unused gas. diff --git a/srml/contract/src/tests.rs b/srml/contract/src/tests.rs index 2a4b30116d8d6..c60d3dd9bf0ea 100644 --- a/srml/contract/src/tests.rs +++ b/srml/contract/src/tests.rs @@ -21,16 +21,26 @@ use runtime_primitives::traits::{BlakeTwo256}; use runtime_primitives::BuildStorage; use runtime_support::StorageMap; use substrate_primitives::{Blake2Hasher}; +use system::{Phase, EventRecord}; use wabt; use { runtime_io, balances, system, CodeOf, ContractAddressFor, - GenesisConfig, Module, StorageOf, Trait, + GenesisConfig, Module, StorageOf, Trait, RawEvent, }; impl_outer_origin! { pub enum Origin for Test {} } +mod contract { + pub use super::super::*; +} +impl_outer_event! { + pub enum MetaEvent for Test { + balances, contract, + } +} + #[derive(Clone, Eq, PartialEq)] pub struct Test; impl system::Trait for Test { @@ -42,7 +52,7 @@ impl system::Trait for Test { type Digest = Digest; type AccountId = u64; type Header = Header; - type Event = (); + type Event = MetaEvent; type Log = DigestItem; } impl balances::Trait for Test { @@ -50,15 +60,17 @@ impl balances::Trait for Test { type AccountIndex = u64; type OnFreeBalanceZero = Contract; type EnsureAccountLiquid = (); - type Event = (); + type Event = MetaEvent; } impl Trait for Test { type Gas = u64; type DetermineContractAddress = DummyContractAddressFor; + type Event = MetaEvent; } type Balances = balances::Module; type Contract = Module; +type System = system::Module; pub struct DummyContractAddressFor; impl ContractAddressFor for DummyContractAddressFor { @@ -205,6 +217,27 @@ fn contract_transfer() { Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), CONTRACT_SHOULD_TRANSFER_VALUE, ); + + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances( + balances::RawEvent::NewAccount( + CONTRACT_SHOULD_TRANSFER_TO, + 0, + balances::NewAccountOutcome::NoHint + ) + ), + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Transfer(0, 1, 3)), + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Transfer(1, CONTRACT_SHOULD_TRANSFER_TO, 6)), + }, + ]); }); } @@ -316,6 +349,13 @@ fn contract_transfer_oog() { assert_eq!(Balances::free_balance(&1), 14); // But `ext_call` should not. assert_eq!(Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 0); + + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Transfer(0, 1, 3)), + }, + ]); }); } @@ -464,6 +504,27 @@ fn contract_create() { assert_eq!(Balances::free_balance(&1), 8); assert_eq!(Balances::free_balance(&derived_address), 3); + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances( + balances::RawEvent::NewAccount( + derived_address, + 0, + balances::NewAccountOutcome::NoHint + ) + ), + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Transfer(0, 1, 11)), + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Transfer(1, 2, 3)), + }, + ]); + // Initiate transfer to the newly created contract. assert_ok!(Contract::call(Origin::signed(0), derived_address, 22, 100_000, Vec::new())); @@ -516,6 +577,13 @@ fn top_level_create() { assert_eq!(Balances::free_balance(&derived_address), 30 + 11); assert_eq!(>::get(&derived_address), code_transfer); + + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Transfer(0, derived_address, 11)), + }, + ]); }); } @@ -643,6 +711,8 @@ fn top_level_call_refunds_even_if_fails() { ); assert_eq!(Balances::free_balance(&0), 100_000_000 - (4 * 3) - (4 * 135)); + + assert_eq!(System::events(), vec![]); }); }