From 4765c8288c583a61a81ff97eea1ef49df13eeca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Rodr=C3=ADguez?= Date: Thu, 28 Sep 2023 12:13:14 +0100 Subject: [PATCH] feat: Contract events in artifacts (#2873) --- compiler/noirc_driver/src/contract.rs | 7 ++++- compiler/noirc_driver/src/lib.rs | 17 ++++++++-- .../noirc_frontend/src/hir/def_map/mod.rs | 21 ++++++++++--- .../src/hir/def_map/module_data.rs | 4 +++ compiler/noirc_frontend/src/lexer/token.rs | 4 +++ compiler/noirc_frontend/src/node_interner.rs | 11 ++++++- tooling/nargo/src/artifacts/contract.rs | 4 ++- tooling/nargo_cli/src/cli/compile_cmd.rs | 1 + tooling/noirc_abi/src/lib.rs | 31 ++++++++++++++++++- 9 files changed, 90 insertions(+), 10 deletions(-) diff --git a/compiler/noirc_driver/src/contract.rs b/compiler/noirc_driver/src/contract.rs index 69a92764318..a16da313ff6 100644 --- a/compiler/noirc_driver/src/contract.rs +++ b/compiler/noirc_driver/src/contract.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use acvm::acir::circuit::Circuit; use fm::FileId; -use noirc_abi::Abi; +use noirc_abi::{Abi, ContractEvent}; use noirc_errors::debug_info::DebugInfo; use super::debug::DebugFile; @@ -34,6 +34,11 @@ pub struct CompiledContract { /// stored in this `Vector`. pub functions: Vec, + /// All the events defined inside the contract scope. + /// An event is a struct value that can be emitted via oracles + /// by any contract function during execution. + pub events: Vec, + pub file_map: BTreeMap, } diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index 60073f9cc84..381a29ffcda 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -6,7 +6,7 @@ use clap::Args; use debug::filter_relevant_files; use fm::FileId; -use noirc_abi::{AbiParameter, AbiType}; +use noirc_abi::{AbiParameter, AbiType, ContractEvent}; use noirc_errors::{CustomDiagnostic, FileDiagnostic}; use noirc_evaluator::{create_circuit, into_abi_params}; use noirc_frontend::graph::{CrateId, CrateName}; @@ -285,7 +285,20 @@ fn compile_contract_inner( let debug_infos: Vec<_> = functions.iter().map(|function| function.debug.clone()).collect(); let file_map = filter_relevant_files(&debug_infos, &context.file_manager); - Ok(CompiledContract { name: contract.name, functions, file_map }) + Ok(CompiledContract { + name: contract.name, + events: contract + .events + .iter() + .map(|event_id| { + let typ = context.def_interner.get_struct(*event_id); + let typ = typ.borrow(); + ContractEvent::from_struct_type(context, &typ) + }) + .collect(), + functions, + file_map, + }) } else { Err(errors) } diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index 4226454f903..27f757074f6 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -1,9 +1,9 @@ use crate::graph::CrateId; use crate::hir::def_collector::dc_crate::{CompilationError, DefCollector}; use crate::hir::Context; -use crate::node_interner::{FuncId, NodeInterner}; +use crate::node_interner::{FuncId, NodeInterner, StructId}; use crate::parser::{parse_program, ParsedModule, ParserError}; -use crate::token::{FunctionAttribute, TestScope}; +use crate::token::{FunctionAttribute, SecondaryAttribute, TestScope}; use arena::{Arena, Index}; use fm::{FileId, FileManager}; use noirc_errors::Location; @@ -182,8 +182,20 @@ impl CrateDefMap { }) .collect(); + let events = module + .type_definitions() + .filter_map(|id| { + id.as_type().filter(|struct_id| { + interner + .struct_attributes(struct_id) + .iter() + .any(|attr| attr == &SecondaryAttribute::Event) + }) + }) + .collect(); + let name = self.get_module_path(id, module.parent); - Some(Contract { name, location: module.location, functions }) + Some(Contract { name, location: module.location, functions, events }) } else { None } @@ -236,13 +248,14 @@ pub struct ContractFunctionMeta { pub is_entry_point: bool, } -/// A 'contract' in Noir source code with the given name and functions. +/// A 'contract' in Noir source code with a given name, functions and events. /// This is not an AST node, it is just a convenient form to return for CrateDefMap::get_all_contracts. pub struct Contract { /// To keep `name` semi-unique, it is prefixed with the names of parent modules via CrateDefMap::get_module_path pub name: String, pub location: Location, pub functions: Vec, + pub events: Vec, } /// Given a FileId, fetch the File, from the FileManager and parse it's content diff --git a/compiler/noirc_frontend/src/hir/def_map/module_data.rs b/compiler/noirc_frontend/src/hir/def_map/module_data.rs index 1f1fa44108d..f86cce75479 100644 --- a/compiler/noirc_frontend/src/hir/def_map/module_data.rs +++ b/compiler/noirc_frontend/src/hir/def_map/module_data.rs @@ -97,6 +97,10 @@ impl ModuleData { self.scope.find_name(name) } + pub fn type_definitions(&self) -> impl Iterator + '_ { + self.definitions.types().values().map(|(id, _)| *id) + } + /// Return an iterator over all definitions defined within this module, /// excluding any type definitions. pub fn value_definitions(&self) -> impl Iterator + '_ { diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 9eaa4fa75e5..6aabacd8940 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -467,6 +467,7 @@ impl Attribute { ["contract_library_method"] => { Attribute::Secondary(SecondaryAttribute::ContractLibraryMethod) } + ["event"] => Attribute::Secondary(SecondaryAttribute::Event), ["deprecated", name] => { if !name.starts_with('"') && !name.ends_with('"') { return Err(LexerErrorKind::MalformedFuncAttribute { @@ -544,6 +545,7 @@ pub enum SecondaryAttribute { // is a helper method for a contract and should not be seen as // the entry point. ContractLibraryMethod, + Event, Custom(String), } @@ -556,6 +558,7 @@ impl fmt::Display for SecondaryAttribute { } SecondaryAttribute::Custom(ref k) => write!(f, "#[{k}]"), SecondaryAttribute::ContractLibraryMethod => write!(f, "#[contract_library_method]"), + SecondaryAttribute::Event => write!(f, "#[event]"), } } } @@ -578,6 +581,7 @@ impl AsRef for SecondaryAttribute { SecondaryAttribute::Deprecated(None) => "", SecondaryAttribute::Custom(string) => string, SecondaryAttribute::ContractLibraryMethod => "", + SecondaryAttribute::Event => "", } } } diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 01633c29178..ce08f3ebec1 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -19,7 +19,7 @@ use crate::hir_def::{ function::{FuncMeta, HirFunction}, stmt::HirStatement, }; -use crate::token::Attributes; +use crate::token::{Attributes, SecondaryAttribute}; use crate::{ ContractFunctionType, FunctionDefinition, Generics, Shared, TypeAliasType, TypeBinding, TypeBindings, TypeVariable, TypeVariableId, TypeVariableKind, Visibility, @@ -32,6 +32,8 @@ pub struct TraitImplKey { // pub generics: Generics - TODO } +type StructAttributes = Vec; + /// The node interner is the central storage location of all nodes in Noir's Hir (the /// various node types can be found in hir_def). The interner is also used to collect /// extra information about the Hir, such as the type of each node, information about @@ -73,6 +75,7 @@ pub struct NodeInterner { // methods from impls to the type. structs: HashMap>, + struct_attributes: HashMap, // Type Aliases map. // // Map type aliases to the actual type. @@ -365,6 +368,7 @@ impl Default for NodeInterner { definitions: vec![], id_to_type: HashMap::new(), structs: HashMap::new(), + struct_attributes: HashMap::new(), type_aliases: Vec::new(), traits: HashMap::new(), trait_implementations: HashMap::new(), @@ -456,6 +460,7 @@ impl NodeInterner { let new_struct = StructType::new(struct_id, name, typ.struct_def.span, no_fields, generics); self.structs.insert(struct_id, Shared::new(new_struct)); + self.struct_attributes.insert(struct_id, typ.struct_def.attributes.clone()); struct_id } @@ -678,6 +683,10 @@ impl NodeInterner { &self.function_modifiers[func_id].attributes } + pub fn struct_attributes(&self, struct_id: &StructId) -> &StructAttributes { + &self.struct_attributes[struct_id] + } + /// Returns the interned statement corresponding to `stmt_id` pub fn statement(&self, stmt_id: &StmtId) -> HirStatement { let def = diff --git a/tooling/nargo/src/artifacts/contract.rs b/tooling/nargo/src/artifacts/contract.rs index 4db7d95731e..fa161b63a5b 100644 --- a/tooling/nargo/src/artifacts/contract.rs +++ b/tooling/nargo/src/artifacts/contract.rs @@ -1,5 +1,5 @@ use acvm::acir::circuit::Circuit; -use noirc_abi::Abi; +use noirc_abi::{Abi, ContractEvent}; use noirc_driver::ContractFunctionType; use serde::{Deserialize, Serialize}; @@ -16,6 +16,8 @@ pub struct PreprocessedContract { pub backend: String, /// Each of the contract's functions are compiled into a separate program stored in this `Vec`. pub functions: Vec, + /// All the events defined inside the contract scope. + pub events: Vec, } /// Each function in the contract will be compiled as a separate noir program. diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index d7bcf475a40..7ac7fe58500 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -267,6 +267,7 @@ fn save_contract( name: contract.name, backend: String::from(BACKEND_IDENTIFIER), functions: preprocessed_functions, + events: contract.events, }; save_contract_to_file( diff --git a/tooling/noirc_abi/src/lib.rs b/tooling/noirc_abi/src/lib.rs index d0c7e4e58c1..eb654e25f8c 100644 --- a/tooling/noirc_abi/src/lib.rs +++ b/tooling/noirc_abi/src/lib.rs @@ -12,7 +12,9 @@ use acvm::{ use errors::AbiError; use input_parser::InputValue; use iter_extended::{try_btree_map, try_vecmap, vecmap}; -use noirc_frontend::{hir::Context, Signedness, Type, TypeBinding, TypeVariableKind, Visibility}; +use noirc_frontend::{ + hir::Context, Signedness, StructType, Type, TypeBinding, TypeVariableKind, Visibility, +}; use serde::{Deserialize, Serialize}; // This is the ABI used to bridge the different TOML formats for the initial // witness, the partial witness generator and the interpreter. @@ -477,6 +479,33 @@ fn decode_string_value(field_elements: &[FieldElement]) -> String { final_string.to_owned() } +#[derive(Debug, Serialize, Deserialize)] +pub struct ContractEvent { + /// Event name + name: String, + /// The fully qualified path to the event definition + path: String, + + /// Fields of the event + #[serde( + serialize_with = "serialization::serialize_struct_fields", + deserialize_with = "serialization::deserialize_struct_fields" + )] + fields: Vec<(String, AbiType)>, +} + +impl ContractEvent { + pub fn from_struct_type(context: &Context, struct_type: &StructType) -> Self { + let fields = vecmap(struct_type.get_fields(&[]), |(name, typ)| { + (name, AbiType::from_type(context, &typ)) + }); + // For the ABI, we always want to resolve the struct paths from the root crate + let path = context.fully_qualified_struct_path(context.root_crate_id(), struct_type.id); + + Self { name: struct_type.name.0.contents.clone(), path, fields } + } +} + #[cfg(test)] mod test { use std::collections::BTreeMap;