Unverified Commit 324a98e0 authored by Hero Bird's avatar Hero Bird Committed by GitHub
Browse files

[lang] Implement event syntax (#78)



* [lang] Implement event syntax

* Update lang/src/ast.rs
Co-Authored-By: Hero Bird's avatarRobbepop <robbepop@web.de>

* [core] Build infrastructure to inspect emitted events

* [lang] Fix some bugs with codegen of events

* [examples/lang] Add example that uses events

* Update lang/src/ast.rs
Co-Authored-By: Hero Bird's avatarRobbepop <robbepop@web.de>

* Update lang/src/hir.rs
Co-Authored-By: Hero Bird's avatarRobbepop <robbepop@web.de>
parent 207d3e24
......@@ -33,3 +33,8 @@ pub fn total_writes() -> u64 {
pub fn set_caller(address: AccountId) {
ContractEnv::set_caller(address)
}
/// Returns an iterator over the uninterpreted bytes of all past emitted events.
pub fn emitted_events() -> impl Iterator<Item = Vec<u8>> {
ContractEnv::emitted_events().into_iter()
}
......@@ -31,7 +31,14 @@ use std::convert::TryFrom;
/// A wrapper for the generic bytearray used for data in contract events.
#[derive(Debug, Clone, PartialEq, Eq)]
struct EventData(Vec<u8>);
pub struct EventData(Vec<u8>);
impl EventData {
/// Returns the uninterpreted bytes of the emitted event.
fn as_bytes(&self) -> &[u8] {
self.0.as_slice()
}
}
/// An entry in the storage of the test environment.
///
......@@ -218,6 +225,13 @@ impl TestEnvData {
pub fn set_random_seed(&mut self, random_seed_hash: srml::Hash) {
self.random_seed = random_seed_hash;
}
/// Returns an iterator over all emitted events.
pub fn emitted_events(&self) -> impl Iterator<Item = &[u8]> {
self.events
.iter()
.map(|event_data| event_data.as_bytes())
}
}
impl TestEnvData {
......@@ -363,6 +377,18 @@ impl TestEnv {
TEST_ENV_DATA
.with(|test_env| test_env.borrow_mut().set_random_seed(random_seed_bytes))
}
/// Returns an iterator over all emitted events.
pub fn emitted_events() -> impl IntoIterator<Item = Vec<u8>> {
TEST_ENV_DATA
.with(|test_env| {
test_env
.borrow()
.emitted_events()
.map(|event_bytes| event_bytes.to_vec())
.collect::<Vec<_>>()
})
}
}
const TEST_ENV_LOG_TARGET: &str = "test-env";
......
[target.wasm32-unknown-unknown]
rustflags = [
"-C", "overflow-checks=on",
"-C", "link-args=-z stack-size=65536 --import-memory"
]
\ No newline at end of file
# Ignore build artifacts from the local tests sub-crate.
/target/
# Ignore backup files creates by cargo fmt.
**/*.rs.bk
# Remove Cargo.lock when creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
\ No newline at end of file
[package]
name = "events"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
ink_core = { path = "../../../core" }
ink_model = { path = "../../../model" }
ink_lang = { path = "../../../lang" }
parity-codec = { version = "3.3", default-features = false, features = ["derive"] }
[lib]
name = "events"
crate-type = ["cdylib"]
[features]
default = []
test-env = [
"ink_core/test-env",
"ink_model/test-env",
"ink_lang/test-env",
]
generate-api-description = [
"ink_lang/generate-api-description"
]
[profile.release]
panic = "abort"
lto = true
opt-level = "z"
\ No newline at end of file
#!/bin/bash
PROJNAME=events
# cargo clean
# rm Cargo.lock
CARGO_INCREMENTAL=0 cargo build --release --features generate-api-description --target=wasm32-unknown-unknown --verbose
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
wasm-opt -Oz target/$PROJNAME.wasm -o target/$PROJNAME-opt.wasm
wasm-prune --exports call,deploy target/$PROJNAME-opt.wasm target/$PROJNAME-pruned.wasm
\ No newline at end of file
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of ink!.
//
// ink! is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ink! is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
#![cfg_attr(not(any(test, feature = "test-env")), no_std)]
use ink_core::storage;
use ink_lang::contract;
contract! {
/// Tests emitting of custom defined events.
struct CallCounter {
/// A simple counter for the calls.
count: storage::Value<u32>,
}
impl Deploy for CallCounter {
fn deploy(&mut self) {
self.count.set(0)
}
}
event IncCalled { current: u32 }
event DecCalled { current: u32 }
impl CallCounter {
/// Increments the internal counter.
///
/// # Note
///
/// Also emits an event.
pub(external) fn inc(&mut self) {
self.count += 1;
env.emit(IncCalled { current: *self.count });
}
/// Decrements the internal counter.
///
/// # Note
///
/// Also emits an event.
pub(external) fn dec(&mut self) {
self.count -= 1;
env.emit(DecCalled { current: *self.count });
}
}
}
#[cfg(all(test, feature = "test-env"))]
mod tests {
use super::*;
use ink_core::env;
#[test]
fn it_works() {
let mut contract = CallCounter::deploy_mock();
assert_eq!(env::test::emitted_events().count(), 0);
contract.inc();
assert_eq!(env::test::emitted_events().count(), 1);
contract.dec();
assert_eq!(env::test::emitted_events().count(), 2);
}
}
#![no_std]
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of ink!.
//
// ink! is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ink! is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
#![cfg_attr(not(any(test, feature = "test-env")), no_std)]
use ink_core::{
env::println,
......
#![no_std]
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of ink!.
//
// ink! is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ink! is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
#![cfg_attr(not(any(test, feature = "test-env")), no_std)]
use ink_core::{
memory::format,
......
......@@ -61,6 +61,15 @@ impl Contract {
}
})
}
pub fn events<'a>(&'a self) -> impl Iterator<Item = &'a ItemEvent> + 'a {
self.items.iter().filter_map(|item| {
match *item {
Item::Event(ref event) => Some(event),
_ => None,
}
})
}
}
#[derive(Debug)]
......@@ -68,6 +77,28 @@ pub enum Item {
State(ItemState),
DeployImpl(ItemDeployImpl),
Impl(ItemImpl),
Event(ItemEvent),
}
/// An event declaration.
///
/// # Example
///
/// This mirrors the syntax for: `event Foo { bar: Bar };`
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ItemEvent {
pub attrs: Vec<syn::Attribute>,
pub event_tok: crate::parser::keywords::event,
pub ident: Ident,
pub brace_tok: token::Brace,
pub args: Punctuated<EventArg, token::Comma>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EventArg {
pub ident: Ident,
pub colon_tok: Token![:],
pub ty: syn::Type,
}
#[derive(Debug)]
......
......@@ -45,6 +45,130 @@ pub fn generate_code(tokens: &mut TokenStream2, contract: &hir::Contract) {
codegen_for_method_impls(tokens, contract);
codegen_for_instantiate(tokens, contract);
codegen_for_entry_points(tokens, contract);
codegen_for_event_mod(tokens, contract);
}
fn codegen_for_event_mod(tokens: &mut TokenStream2, contract: &hir::Contract) {
if contract.events.is_empty() {
// Do nothing if the user specified no events
return
}
let use_event_body = {
let mut content = quote! {};
for event in contract.events.iter() {
let ident = &event.ident;
content.extend(quote! {
#ident,
})
}
content
};
let mod_event_body = {
let mut content = quote! {};
codegen_for_event_private_mod(&mut content, contract);
codegen_for_events(&mut content, contract);
codegen_for_event_emit_trait(&mut content, contract);
content
};
tokens.extend(quote! {
mod events {
use super::*;
#mod_event_body
}
use events::{
EmitEventExt as _,
#use_event_body
};
})
}
fn codegen_for_event_private_mod(tokens: &mut TokenStream2, contract: &hir::Contract) {
let event_enum_mod_body = {
let mut content = quote! {};
for event in contract.events.iter() {
let name = &event.ident;
content.extend(quote! {
#name(#name),
})
}
content
};
tokens.extend(quote! {
mod private {
use super::*;
#[doc(hidden)]
#[derive(parity_codec::Encode, parity_codec::Decode)]
pub enum Event {
#event_enum_mod_body
}
/// Used to seal the emit trait.
pub trait Sealed {}
}
})
}
impl quote::ToTokens for hir::Event {
fn to_tokens(&self, tokens: &mut TokenStream2) {
<Token![pub]>::default().to_tokens(tokens);
<Token![struct]>::default().to_tokens(tokens);
self.ident.to_tokens(tokens);
syn::token::Brace::default().surround(tokens, |inner| {
for arg in self.args.iter() {
<Token![pub]>::default().to_tokens(inner);
arg.to_tokens(inner);
}
});
}
}
impl quote::ToTokens for ast::EventArg {
fn to_tokens(&self, tokens: &mut TokenStream2) {
self.ident.to_tokens(tokens);
self.colon_tok.to_tokens(tokens);
self.ty.to_tokens(tokens);
}
}
fn codegen_for_events(tokens: &mut TokenStream2, contract: &hir::Contract) {
for event in contract.events.iter() {
let ident = &event.ident;
tokens.extend(quote! {
/// The documentation for `BalanceChanged`.
#[derive(parity_codec::Encode, parity_codec::Decode)]
#event
impl From<#ident> for private::Event {
fn from(event: #ident) -> Self {
private::Event::#ident(event)
}
}
})
}
}
fn codegen_for_event_emit_trait(tokens: &mut TokenStream2, _contract: &hir::Contract) {
tokens.extend(quote! {
pub trait EmitEventExt: private::Sealed {
/// Emits the given event.
fn emit<E>(&self, event: E)
where
E: Into<private::Event>,
{
use parity_codec::Encode as _;
ink_core::env::deposit_raw_event(
event.into().encode().as_slice()
)
}
}
impl EmitEventExt for ink_model::EnvHandler {}
impl private::Sealed for ink_model::EnvHandler {}
})
}
fn codegen_for_entry_points(tokens: &mut TokenStream2, contract: &hir::Contract) {
......
......@@ -21,11 +21,15 @@ use crate::{
SynError,
},
};
use proc_macro2::{
Ident,
Span,
};
use syn::{
punctuated::Punctuated,
Token,
};
/// A smart contract.
#[derive(Debug)]
pub struct Contract {
......@@ -39,9 +43,63 @@ pub struct Contract {
pub messages: Vec<Message>,
/// Methods of the smart contract.
pub methods: Vec<Method>,
/// Events of the smart contract.
pub events: Vec<Event>,
}
/// An event definition.
#[derive(Debug)]
pub struct Event {
pub attrs: Vec<syn::Attribute>,
pub ident: Ident,
pub args: Punctuated<ast::EventArg, Token![,]>,
}
impl Contract {
/// Extracts all events from the contract.
///
/// Performs some semantic checks on them as a whole.
///
/// # Errors
///
/// - If there are multiple events with the same names.
/// - If an event has the same name as the contract
fn extract_events(
contract_ident: &Ident,
contract: &ast::Contract,
) -> Result<Vec<Event>> {
let events = contract.events().collect::<Vec<_>>();
let mut unique_events = std::collections::HashSet::new();
for event in &events {
if &event.ident == contract_ident {
bail!(
event.ident,
"cannot declare an event with the same name as the contract",
)
}
if !unique_events.contains(event) {
unique_events.insert(event.clone());
} else {
bail!(
event.ident,
"cannot declare multiple events with the same name",
)
}
}
let mut ret = unique_events
.iter()
.map(|event| {
Event {
attrs: event.attrs.clone(),
ident: event.ident.clone(),
args: event.args.clone(),
}
})
.collect::<Vec<_>>();
ret.sort_by(|e1, e2| e1.ident.partial_cmp(&e2.ident).unwrap());
Ok(ret)
}
/// Extracts the contract state from the contract items
/// and performs some integrity checks on it.
///
......@@ -256,12 +314,14 @@ impl Contract {
let (ident, state) = Self::extract_state(contract)?;
let deploy_handler = Self::extract_deploy_handler(ident, contract)?;
let (messages, methods) = Self::unpack_impl_blocks(ident, contract)?;
let events = Self::extract_events(ident, contract)?;
Ok(Self {
name: ident.clone(),
state,
on_deploy: deploy_handler,
messages,
methods,
events,
})
}
}
......
......@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
#![recursion_limit = "128"]
#![recursion_limit = "256"]
extern crate proc_macro;
......
......@@ -32,6 +32,7 @@ pub mod keywords {
custom_keyword!(Deploy);
custom_keyword!(deploy);
custom_keyword!(external);
custom_keyword!(event);
}
pub fn parse_contract(token_stream: TokenStream2) -> Result<ast::Contract> {
......@@ -77,6 +78,11 @@ impl Parse for ast::Item {
ast::Item::Impl(impl_block)
})
}
} else if lookahead.peek(keywords::event) {
input.parse().map(|mut event: ast::ItemEvent| {
event.attrs = attrs;
ast::Item::Event(event)
})
} else {
Err(lookahead.error())
}
......@@ -294,3 +300,36 @@ impl ast::FnArg {
})
}
}
impl Parse for ast::ItemEvent {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let event_tok = input.parse()?;
let ident = input.parse()?;
let (brace_tok, args) = {
let content;
let brace_tok = syn::braced!(content in input);
let inputs = content.parse_terminated(ast::EventArg::parse)?;
(brace_tok, inputs)
};
Ok(Self {
attrs: vec![],
event_tok,
ident,
brace_tok,
args,
})
}
}
impl Parse for ast::EventArg {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let ident = input.parse()?;
let colon_tok = input.parse()?;
let ty = input.parse()?;
Ok(Self {
ident,
colon_tok,
ty,
})
}
}
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of ink!.
//
// ink! is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ink! is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
use super::*;
#[test]
fn incrementer_contract() {
assert_eq_tokenstreams(
quote! {
/// Tests emitting of custom defined events.
struct CallCounter {
/// A simple counter for the calls.
count: storage::Value<u32>,
}
impl Deploy for CallCounter {
fn deploy(&mut self) {
}
}
event IncCalled { current: u32 }
event DecCalled { current: u32 }
impl CallCounter {
/// Increments the internal counter.
///
/// # Note