Bladeren bron

Implement event selectors (#1584)

```solidity
contract e {
    event E();
    function f() public {
       bytes32 selector = E.selector;
    }
}
```

Signed-off-by: Sean Young <sean@mess.org>
Co-authored-by: Lucas Steuernagel <38472950+LucasSte@users.noreply.github.com>
Sean Young 2 jaren geleden
bovenliggende
commit
fe662028d5

+ 6 - 1
docs/language/events.rst

@@ -9,7 +9,7 @@ you can emit events but you cannot access events on the chain.
 Once those events are added to the chain, an off-chain application can listen for events. For example, the Web3.js
 Once those events are added to the chain, an off-chain application can listen for events. For example, the Web3.js
 interface has a `subscribe()` function. Another is example is
 interface has a `subscribe()` function. Another is example is
 `Hyperledger Burrow <https://hyperledger.github.io/burrow/#/reference/vent>`_
 `Hyperledger Burrow <https://hyperledger.github.io/burrow/#/reference/vent>`_
-which has a vent command which listens to events and inserts them into a Postgres database.
+which has a vent command that listens to events and inserts them into a database.
 
 
 An event has two parts. First, there is a limited set of topics. Usually there are no more than 3 topics,
 An event has two parts. First, there is a limited set of topics. Usually there are no more than 3 topics,
 and each of those has a fixed length of 32 bytes. They are there so that an application listening for events
 and each of those has a fixed length of 32 bytes. They are there so that an application listening for events
@@ -22,6 +22,11 @@ fields are stored in the data section of the event. The event name does not need
 functions, they can be overloaded as long as the fields are of different types, or the event has
 functions, they can be overloaded as long as the fields are of different types, or the event has
 a different number of arguments.
 a different number of arguments.
 
 
+The first topic is used to identify the event, this is a hash of the event signature, also known
+as the selector. If the event is declared ``anonymous`` then this is omitted, and an additional
+``indexed`` field is available. The selector is available in Solidity using the ``eventname.selector``
+syntax.
+
 .. warning::
 .. warning::
     On Solana, the ``indexed`` event field attribute has no effect. All event attributes will be encoded as data to
     On Solana, the ``indexed`` event field attribute has no effect. All event attributes will be encoded as data to
     be passed for Solana's ``sol_log_data`` system call, regardless if the ``indexed`` keyword is present or not.
     be passed for Solana's ``sol_log_data`` system call, regardless if the ``indexed`` keyword is present or not.

+ 15 - 4
src/abi/anchor.rs

@@ -20,15 +20,26 @@ use solang_parser::pt::FunctionTy;
 /// Generate discriminator based on the name of the function. This is the 8 byte
 /// Generate discriminator based on the name of the function. This is the 8 byte
 /// value anchor uses to dispatch function calls on. This should match
 /// value anchor uses to dispatch function calls on. This should match
 /// anchor's behaviour - we need to match the discriminator exactly
 /// anchor's behaviour - we need to match the discriminator exactly
-pub fn discriminator(namespace: &'static str, name: &str) -> Vec<u8> {
-    let mut hasher = Sha256::new();
+pub fn function_discriminator(name: &str) -> Vec<u8> {
     // must match snake-case npm library, see
     // must match snake-case npm library, see
     // https://github.com/coral-xyz/anchor/blob/master/ts/packages/anchor/src/coder/borsh/instruction.ts#L389
     // https://github.com/coral-xyz/anchor/blob/master/ts/packages/anchor/src/coder/borsh/instruction.ts#L389
     let normalized = name
     let normalized = name
         .from_case(Case::Camel)
         .from_case(Case::Camel)
         .without_boundaries(&[Boundary::LowerDigit])
         .without_boundaries(&[Boundary::LowerDigit])
         .to_case(Case::Snake);
         .to_case(Case::Snake);
-    hasher.update(format!("{namespace}:{normalized}"));
+    discriminator("global", &normalized)
+}
+
+/// Generate discriminator based on the name of the event. This is the 8 byte
+/// value anchor uses for events. This should match anchor's behaviour,
+///  generating the same discriminator
+pub fn event_discriminator(name: &str) -> Vec<u8> {
+    discriminator("event", name)
+}
+
+fn discriminator(namespace: &'static str, name: &str) -> Vec<u8> {
+    let mut hasher = Sha256::new();
+    hasher.update(format!("{namespace}:{name}"));
     hasher.finalize()[..8].to_vec()
     hasher.finalize()[..8].to_vec()
 }
 }
 
 
@@ -85,7 +96,7 @@ fn idl_events(
             }
             }
 
 
             events.push(IdlEvent {
             events.push(IdlEvent {
-                name: def.name.clone(),
+                name: def.id.name.clone(),
                 fields,
                 fields,
             });
             });
         }
         }

+ 1 - 1
src/abi/ethereum.rs

@@ -119,7 +119,7 @@ pub fn gen_abi(contract_no: usize, ns: &Namespace) -> Vec<ABI> {
                     let event = &ns.events[*event_no];
                     let event = &ns.events[*event_no];
 
 
                     ABI {
                     ABI {
-                        name: event.name.to_owned(),
+                        name: event.id.name.to_owned(),
                         mutability: String::new(),
                         mutability: String::new(),
                         inputs: Some(
                         inputs: Some(
                             event
                             event

+ 1 - 1
src/abi/polkadot.rs

@@ -449,7 +449,7 @@ pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
                     .done()
                     .done()
             })
             })
             .collect::<Vec<_>>();
             .collect::<Vec<_>>();
-        EventSpec::new(e.name.clone())
+        EventSpec::new(e.id.name.clone())
             .args(args)
             .args(args)
             .docs(vec![render(&e.tags).as_str()])
             .docs(vec![render(&e.tags).as_str()])
             .done()
             .done()

+ 2 - 2
src/bin/doc/mod.rs

@@ -183,7 +183,7 @@ pub fn generate_docs(outdir: &OsString, files: &[ast::Namespace], verbose: bool)
             }
             }
 
 
             top.events.push(EventDecl {
             top.events.push(EventDecl {
-                name: &event_decl.name,
+                name: &event_decl.id.name,
                 contract: event_decl
                 contract: event_decl
                     .contract
                     .contract
                     .map(|contract_no| file.contracts[contract_no].id.name.as_str()),
                     .map(|contract_no| file.contracts[contract_no].id.name.as_str()),
@@ -192,7 +192,7 @@ pub fn generate_docs(outdir: &OsString, files: &[ast::Namespace], verbose: bool)
                 author: get_tag("author", &event_decl.tags),
                 author: get_tag("author", &event_decl.tags),
                 dev: get_tag("dev", &event_decl.tags),
                 dev: get_tag("dev", &event_decl.tags),
                 anonymous: event_decl.anonymous,
                 anonymous: event_decl.anonymous,
-                loc: event_decl.loc,
+                loc: event_decl.id.loc,
                 field,
                 field,
             });
             });
         }
         }

+ 2 - 2
src/bin/idl/mod.rs

@@ -4,7 +4,7 @@ use crate::cli::IdlCommand;
 use anchor_syn::idl::types::{Idl, IdlAccountItem, IdlInstruction, IdlType, IdlTypeDefinitionTy};
 use anchor_syn::idl::types::{Idl, IdlAccountItem, IdlInstruction, IdlType, IdlTypeDefinitionTy};
 use itertools::Itertools;
 use itertools::Itertools;
 use serde_json::Value as JsonValue;
 use serde_json::Value as JsonValue;
-use solang::abi::anchor::discriminator;
+use solang::abi::anchor::function_discriminator;
 use solang_parser::lexer::is_keyword;
 use solang_parser::lexer::is_keyword;
 use std::{ffi::OsStr, fs::File, io::Write, path::PathBuf, process::exit};
 use std::{ffi::OsStr, fs::File, io::Write, path::PathBuf, process::exit};
 
 
@@ -254,7 +254,7 @@ fn instruction(
             .1;
             .1;
 
 
         // The anchor discriminator is what Solidity calls a selector
         // The anchor discriminator is what Solidity calls a selector
-        let selector = discriminator("global", &instr.name);
+        let selector = function_discriminator(&instr.name);
 
 
         write!(
         write!(
             f,
             f,

+ 3 - 3
src/bin/languageserver/mod.rs

@@ -1676,8 +1676,8 @@ impl<'a> Builder<'a> {
             self.hovers.push((
             self.hovers.push((
                 file_no,
                 file_no,
                 HoverEntry {
                 HoverEntry {
-                    start: event.loc.start(),
-                    stop: event.loc.start() + event.name.len(),
+                    start: event.id.loc.start(),
+                    stop: event.id.loc.exclusive_end(),
                     val: render(&event.tags[..]),
                     val: render(&event.tags[..]),
                 },
                 },
             ));
             ));
@@ -1687,7 +1687,7 @@ impl<'a> Builder<'a> {
                     def_path: file.path.clone(),
                     def_path: file.path.clone(),
                     def_type: DefinitionType::Event(ei),
                     def_type: DefinitionType::Event(ei),
                 },
                 },
-                loc_to_range(&event.loc, file),
+                loc_to_range(&event.id.loc, file),
             );
             );
         }
         }
 
 

+ 3 - 0
src/codegen/events/mod.rs

@@ -27,6 +27,9 @@ pub(super) trait EventEmitter {
         vartab: &mut Vartable,
         vartab: &mut Vartable,
         opt: &Options,
         opt: &Options,
     );
     );
+
+    /// Generates the selector
+    fn selector(&self, emitting_contract_no: usize) -> Vec<u8>;
 }
 }
 
 
 /// Create a new event emitter based on the target blockchain
 /// Create a new event emitter based on the target blockchain

+ 29 - 22
src/codegen/events/polkadot.rs

@@ -23,18 +23,27 @@ pub(super) struct PolkadotEventEmitter<'a> {
     pub(super) event_no: usize,
     pub(super) event_no: usize,
 }
 }
 
 
-/// Takes a scale-encoded topic and makes it into a topic hash.
-fn topic_hash(encoded: &[u8]) -> Vec<u8> {
-    let mut buf = [0; 32];
-    if encoded.len() <= 32 {
-        buf[..encoded.len()].copy_from_slice(encoded);
-    } else {
-        <Blake2x256 as CryptoHash>::hash(encoded, &mut buf);
-    };
-    buf.into()
-}
-
 impl EventEmitter for PolkadotEventEmitter<'_> {
 impl EventEmitter for PolkadotEventEmitter<'_> {
+    fn selector(&self, emitting_contract_no: usize) -> Vec<u8> {
+        let event = &self.ns.events[self.event_no];
+        // For freestanding events the name of the emitting contract is used
+        let contract_name = &self.ns.contracts[event.contract.unwrap_or(emitting_contract_no)]
+            .id
+            .name;
+
+        // First byte is 0 because there is no prefix for the event topic
+        let encoded = format!("\0{}::{}", contract_name, &event.id);
+
+        // Takes a scale-encoded topic and makes it into a topic hash.
+        let mut buf = [0; 32];
+        if encoded.len() <= 32 {
+            buf[..encoded.len()].copy_from_slice(encoded.as_bytes());
+        } else {
+            <Blake2x256 as CryptoHash>::hash(encoded.as_bytes(), &mut buf);
+        };
+        buf.into()
+    }
+
     fn emit(
     fn emit(
         &self,
         &self,
         contract_no: usize,
         contract_no: usize,
@@ -46,7 +55,9 @@ impl EventEmitter for PolkadotEventEmitter<'_> {
         let loc = pt::Loc::Builtin;
         let loc = pt::Loc::Builtin;
         let event = &self.ns.events[self.event_no];
         let event = &self.ns.events[self.event_no];
         // For freestanding events the name of the emitting contract is used
         // For freestanding events the name of the emitting contract is used
-        let contract_name = &self.ns.contracts[event.contract.unwrap_or(contract_no)].id;
+        let contract_name = &self.ns.contracts[event.contract.unwrap_or(contract_no)]
+            .id
+            .name;
         let hash_len = Box::new(Expression::NumberLiteral {
         let hash_len = Box::new(Expression::NumberLiteral {
             loc,
             loc,
             ty: Type::Uint(32),
             ty: Type::Uint(32),
@@ -67,13 +78,14 @@ impl EventEmitter for PolkadotEventEmitter<'_> {
         // Events that are not anonymous always have themselves as a topic.
         // Events that are not anonymous always have themselves as a topic.
         // This is static and can be calculated at compile time.
         // This is static and can be calculated at compile time.
         if !event.anonymous {
         if !event.anonymous {
+            let topic_hash = self.selector(contract_no);
+
             // First byte is 0 because there is no prefix for the event topic
             // First byte is 0 because there is no prefix for the event topic
-            let encoded = format!("\0{}::{}", contract_name, &event.name);
             topics.push(Expression::AllocDynamicBytes {
             topics.push(Expression::AllocDynamicBytes {
                 loc,
                 loc,
                 ty: Type::Slice(Type::Uint(8).into()),
                 ty: Type::Slice(Type::Uint(8).into()),
                 size: hash_len.clone(),
                 size: hash_len.clone(),
-                initializer: Some(topic_hash(encoded.as_bytes())),
+                initializer: Some(topic_hash),
             });
             });
         };
         };
 
 
@@ -83,14 +95,9 @@ impl EventEmitter for PolkadotEventEmitter<'_> {
             .iter()
             .iter()
             .filter(|field| field.indexed)
             .filter(|field| field.indexed)
             .map(|field| {
             .map(|field| {
-                format!(
-                    "{}::{}::{}",
-                    contract_name,
-                    &event.name,
-                    &field.name_as_str()
-                )
-                .into_bytes()
-                .encode()
+                format!("{}::{}::{}", contract_name, &event.id, &field.name_as_str())
+                    .into_bytes()
+                    .encode()
             })
             })
             .collect();
             .collect();
 
 

+ 6 - 7
src/codegen/events/solana.rs

@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: Apache-2.0
 
 
+use crate::abi::anchor::event_discriminator;
 use crate::codegen::cfg::{ControlFlowGraph, Instr};
 use crate::codegen::cfg::{ControlFlowGraph, Instr};
 use crate::codegen::encoding::abi_encode;
 use crate::codegen::encoding::abi_encode;
 use crate::codegen::events::EventEmitter;
 use crate::codegen::events::EventEmitter;
@@ -8,7 +9,6 @@ use crate::codegen::vartable::Vartable;
 use crate::codegen::{Expression, Options};
 use crate::codegen::{Expression, Options};
 use crate::sema::ast;
 use crate::sema::ast;
 use crate::sema::ast::{Function, Namespace, Type};
 use crate::sema::ast::{Function, Namespace, Type};
-use sha2::{Digest, Sha256};
 use solang_parser::pt::Loc;
 use solang_parser::pt::Loc;
 
 
 /// This struct implements the trait 'EventEmitter' to handle the emission of events for Solana.
 /// This struct implements the trait 'EventEmitter' to handle the emission of events for Solana.
@@ -21,6 +21,10 @@ pub(super) struct SolanaEventEmitter<'a> {
 }
 }
 
 
 impl EventEmitter for SolanaEventEmitter<'_> {
 impl EventEmitter for SolanaEventEmitter<'_> {
+    fn selector(&self, _: usize) -> Vec<u8> {
+        event_discriminator(&self.ns.events[self.event_no].id.name)
+    }
+
     fn emit(
     fn emit(
         &self,
         &self,
         contract_no: usize,
         contract_no: usize,
@@ -29,15 +33,10 @@ impl EventEmitter for SolanaEventEmitter<'_> {
         vartab: &mut Vartable,
         vartab: &mut Vartable,
         opt: &Options,
         opt: &Options,
     ) {
     ) {
-        let discriminator_image = format!("event:{}", self.ns.events[self.event_no].name);
-        let mut hasher = Sha256::new();
-        hasher.update(discriminator_image);
-        let result = hasher.finalize();
-
         let discriminator = Expression::BytesLiteral {
         let discriminator = Expression::BytesLiteral {
             loc: Loc::Codegen,
             loc: Loc::Codegen,
             ty: Type::Bytes(8),
             ty: Type::Bytes(8),
-            value: result[..8].to_vec(),
+            value: self.selector(contract_no),
         };
         };
 
 
         let mut codegen_args = self
         let mut codegen_args = self

+ 10 - 0
src/codegen/expression.rs

@@ -14,6 +14,7 @@ use super::{
 use super::{polkadot, Options};
 use super::{polkadot, Options};
 use crate::codegen::array_boundary::handle_array_assign;
 use crate::codegen::array_boundary::handle_array_assign;
 use crate::codegen::constructor::call_constructor;
 use crate::codegen::constructor::call_constructor;
+use crate::codegen::events::new_event_emitter;
 use crate::codegen::unused_variable::should_remove_assignment;
 use crate::codegen::unused_variable::should_remove_assignment;
 use crate::codegen::{Builtin, Expression};
 use crate::codegen::{Builtin, Expression};
 use crate::sema::ast::ExternalCallAccounts;
 use crate::sema::ast::ExternalCallAccounts;
@@ -595,6 +596,15 @@ pub fn expression(
                 func_expr.external_function_selector()
                 func_expr.external_function_selector()
             }
             }
         },
         },
+        ast::Expression::EventSelector { loc, ty, event_no } => {
+            let emitter = new_event_emitter(loc, *event_no, &[], ns);
+
+            Expression::BytesLiteral {
+                loc: *loc,
+                ty: ty.clone(),
+                value: emitter.selector(contract_no),
+            }
+        }
         ast::Expression::InternalFunctionCall { .. }
         ast::Expression::InternalFunctionCall { .. }
         | ast::Expression::ExternalFunctionCall { .. }
         | ast::Expression::ExternalFunctionCall { .. }
         | ast::Expression::ExternalFunctionCallRaw { .. }
         | ast::Expression::ExternalFunctionCallRaw { .. }

+ 15 - 8
src/sema/ast.rs

@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: Apache-2.0
 
 
 use super::symtable::Symtable;
 use super::symtable::Symtable;
-use crate::abi::anchor::discriminator;
+use crate::abi::anchor::function_discriminator;
 use crate::codegen::cfg::{ControlFlowGraph, Instr};
 use crate::codegen::cfg::{ControlFlowGraph, Instr};
 use crate::diagnostics::Diagnostics;
 use crate::diagnostics::Diagnostics;
 use crate::sema::ast::ExternalCallAccounts::{AbsentArgument, NoAccount};
 use crate::sema::ast::ExternalCallAccounts::{AbsentArgument, NoAccount};
@@ -169,7 +169,7 @@ pub struct StructDecl {
 #[derive(PartialEq, Eq, Clone, Debug)]
 #[derive(PartialEq, Eq, Clone, Debug)]
 pub struct EventDecl {
 pub struct EventDecl {
     pub tags: Vec<Tag>,
     pub tags: Vec<Tag>,
-    pub name: String,
+    pub id: pt::Identifier,
     pub loc: pt::Loc,
     pub loc: pt::Loc,
     pub contract: Option<usize>,
     pub contract: Option<usize>,
     pub fields: Vec<Parameter>,
     pub fields: Vec<Parameter>,
@@ -181,8 +181,8 @@ pub struct EventDecl {
 impl EventDecl {
 impl EventDecl {
     pub fn symbol_name(&self, ns: &Namespace) -> String {
     pub fn symbol_name(&self, ns: &Namespace) -> String {
         match &self.contract {
         match &self.contract {
-            Some(c) => format!("{}.{}", ns.contracts[*c].id, self.name),
-            None => self.name.to_string(),
+            Some(c) => format!("{}.{}", ns.contracts[*c].id, self.id),
+            None => self.id.to_string(),
         }
         }
     }
     }
 }
 }
@@ -468,14 +468,14 @@ impl Function {
             selector.clone()
             selector.clone()
         } else if ns.target == Target::Solana {
         } else if ns.target == Target::Solana {
             match self.ty {
             match self.ty {
-                FunctionTy::Constructor => discriminator("global", "new"),
+                FunctionTy::Constructor => function_discriminator("new"),
                 _ => {
                 _ => {
                     let discriminator_image = if self.mangled_name_contracts.contains(contract_no) {
                     let discriminator_image = if self.mangled_name_contracts.contains(contract_no) {
                         &self.mangled_name
                         &self.mangled_name
                     } else {
                     } else {
                         &self.name
                         &self.name
                     };
                     };
-                    discriminator("global", discriminator_image.as_str())
+                    function_discriminator(discriminator_image.as_str())
                 }
                 }
             }
             }
         } else {
         } else {
@@ -1198,6 +1198,11 @@ pub enum Expression {
         function_no: usize,
         function_no: usize,
         args: Vec<Expression>,
         args: Vec<Expression>,
     },
     },
+    EventSelector {
+        loc: pt::Loc,
+        ty: Type,
+        event_no: usize,
+    },
 }
 }
 
 
 #[derive(PartialEq, Eq, Clone, Default, Debug)]
 #[derive(PartialEq, Eq, Clone, Default, Debug)]
@@ -1442,7 +1447,8 @@ impl Recurse for Expression {
                 | Expression::RationalNumberLiteral { .. }
                 | Expression::RationalNumberLiteral { .. }
                 | Expression::CodeLiteral { .. }
                 | Expression::CodeLiteral { .. }
                 | Expression::BytesLiteral { .. }
                 | Expression::BytesLiteral { .. }
-                | Expression::BoolLiteral { .. } => (),
+                | Expression::BoolLiteral { .. }
+                | Expression::EventSelector { .. } => (),
             }
             }
         }
         }
     }
     }
@@ -1516,7 +1522,8 @@ impl CodeLocation for Expression {
             | Expression::InterfaceId { loc, .. }
             | Expression::InterfaceId { loc, .. }
             | Expression::And { loc, .. }
             | Expression::And { loc, .. }
             | Expression::NamedMember { loc, .. }
             | Expression::NamedMember { loc, .. }
-            | Expression::UserDefinedOperator { loc, .. } => *loc,
+            | Expression::UserDefinedOperator { loc, .. }
+            | Expression::EventSelector { loc, .. } => *loc,
         }
         }
     }
     }
 }
 }

+ 16 - 2
src/sema/dotgraphviz.rs

@@ -1452,6 +1452,20 @@ impl Dot {
                 );
                 );
                 self.add_expression(array, func, ns, node, format!("member: {}", name));
                 self.add_expression(array, func, ns, node, format!("member: {}", name));
             }
             }
+            Expression::EventSelector { loc, event_no, .. } => {
+                let event = &ns.events[*event_no];
+
+                let labels = vec![
+                    format!("event selector {}", event.symbol_name(ns)),
+                    ns.loc_to_string(PathDisplay::FullPath, loc),
+                ];
+
+                self.add_node(
+                    Node::new("event_selector", labels),
+                    Some(parent),
+                    Some(parent_rel),
+                );
+            }
         }
         }
     }
     }
 
 
@@ -2484,7 +2498,7 @@ impl Namespace {
 
 
             for decl in &self.events {
             for decl in &self.events {
                 let mut labels = vec![
                 let mut labels = vec![
-                    format!("name:{}", decl.name),
+                    format!("name:{}", decl.id),
                     self.loc_to_string(PathDisplay::FullPath, &decl.loc),
                     self.loc_to_string(PathDisplay::FullPath, &decl.loc),
                 ];
                 ];
 
 
@@ -2505,7 +2519,7 @@ impl Namespace {
                     ));
                     ));
                 }
                 }
 
 
-                let e = Node::new(&decl.name, labels);
+                let e = Node::new(&decl.id.name, labels);
 
 
                 let node = dot.add_node(e, Some(events), None);
                 let node = dot.add_node(e, Some(events), None);
 
 

+ 72 - 1
src/sema/expression/member_access.rs

@@ -17,7 +17,7 @@ use crate::sema::unused_variable::{assigned_variable, used_variable};
 use crate::Target;
 use crate::Target;
 use num_bigint::{BigInt, Sign};
 use num_bigint::{BigInt, Sign};
 use num_traits::{FromPrimitive, One, Zero};
 use num_traits::{FromPrimitive, One, Zero};
-use solang_parser::diagnostics::Diagnostic;
+use solang_parser::diagnostics::{Diagnostic, Note};
 use solang_parser::pt;
 use solang_parser::pt;
 use solang_parser::pt::CodeLocation;
 use solang_parser::pt::CodeLocation;
 use std::ops::{Shl, Sub};
 use std::ops::{Shl, Sub};
@@ -69,6 +69,19 @@ pub(super) fn member_access(
         return Ok(expr);
         return Ok(expr);
     }
     }
 
 
+    // is it an event selector
+    if let Some(expr) = event_selector(
+        loc,
+        e,
+        id,
+        context.file_no,
+        context.contract_no,
+        ns,
+        diagnostics,
+    )? {
+        return Ok(expr);
+    }
+
     // is it a constant (unless basecontract is a local variable)
     // is it a constant (unless basecontract is a local variable)
     if let Some(expr) = contract_constant(
     if let Some(expr) = contract_constant(
         loc,
         loc,
@@ -643,6 +656,64 @@ fn enum_value(
     }
     }
 }
 }
 
 
+fn event_selector(
+    loc: &pt::Loc,
+    expr: &pt::Expression,
+    id: &pt::Identifier,
+    file_no: usize,
+    contract_no: Option<usize>,
+    ns: &mut Namespace,
+    diagnostics: &mut Diagnostics,
+) -> Result<Option<Expression>, ()> {
+    if id.name != "selector" {
+        return Ok(None);
+    }
+
+    if let Ok(events) = ns.resolve_event(file_no, contract_no, expr, &mut Diagnostics::default()) {
+        if events.len() == 1 {
+            let event_no = events[0];
+
+            if ns.events[event_no].anonymous {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "anonymous event has no selector".into(),
+                ));
+                Err(())
+            } else {
+                Ok(Some(Expression::EventSelector {
+                    loc: *loc,
+                    event_no,
+                    ty: if ns.target == Target::Solana {
+                        Type::Bytes(8)
+                    } else {
+                        Type::Bytes(32)
+                    },
+                }))
+            }
+        } else {
+            let notes = events
+                .into_iter()
+                .map(|ev_no| {
+                    let ev = &ns.events[ev_no];
+                    Note {
+                        loc: ev.id.loc,
+                        message: format!("possible definition of '{}'", ev.id),
+                    }
+                })
+                .collect();
+
+            diagnostics.push(Diagnostic::error_with_notes(
+                *loc,
+                "multiple definitions of event".into(),
+                notes,
+            ));
+            Err(())
+        }
+    } else {
+        Ok(None)
+    }
+}
+
 /// Resolve type(x).foo
 /// Resolve type(x).foo
 fn type_name_expr(
 fn type_name_expr(
     loc: &pt::Loc,
     loc: &pt::Loc,

+ 2 - 1
src/sema/expression/retrieve_type.rs

@@ -62,7 +62,8 @@ impl RetrieveType for Expression {
             | Expression::InternalFunction { ty, .. }
             | Expression::InternalFunction { ty, .. }
             | Expression::ExternalFunction { ty, .. }
             | Expression::ExternalFunction { ty, .. }
             | Expression::NamedMember { ty, .. }
             | Expression::NamedMember { ty, .. }
-            | Expression::StorageArrayLength { ty, .. } => ty.clone(),
+            | Expression::StorageArrayLength { ty, .. }
+            | Expression::EventSelector { ty, .. } => ty.clone(),
             Expression::ExternalFunctionCallRaw { .. } => {
             Expression::ExternalFunctionCallRaw { .. } => {
                 panic!("two return values");
                 panic!("two return values");
             }
             }

+ 5 - 5
src/sema/statements.rs

@@ -1302,7 +1302,7 @@ fn emit_event(
                         *loc,
                         *loc,
                         format!(
                         format!(
                             "event type '{}' has {} fields, {} provided",
                             "event type '{}' has {} fields, {} provided",
-                            event.name,
+                            event.id,
                             event.fields.len(),
                             event.fields.len(),
                             args.len()
                             args.len()
                         ),
                         ),
@@ -1417,10 +1417,10 @@ fn emit_event(
                     temp_diagnostics.push(Diagnostic::cast_error_with_note(
                     temp_diagnostics.push(Diagnostic::cast_error_with_note(
                         *loc,
                         *loc,
                         format!(
                         format!(
-                            "event cannot be emmited with named fields as {unnamed_fields} of its fields do not have names"
+                            "event cannot be emitted with named fields as {unnamed_fields} of its fields do not have names"
                         ),
                         ),
-                        event.loc,
-                        format!("definition of {}", event.name),
+                        event.id.loc,
+                        format!("definition of {}", event.id),
                     ));
                     ));
                     matches = false;
                     matches = false;
                 } else if params_len != arguments.len() {
                 } else if params_len != arguments.len() {
@@ -1454,7 +1454,7 @@ fn emit_event(
                                 format!(
                                 format!(
                                     "missing argument '{}' to event '{}'",
                                     "missing argument '{}' to event '{}'",
                                     param.name_as_str(),
                                     param.name_as_str(),
-                                    ns.events[*event_no].name,
+                                    ns.events[*event_no].id,
                                 ),
                                 ),
                             ));
                             ));
                             continue;
                             continue;

+ 6 - 6
src/sema/types.rs

@@ -129,8 +129,8 @@ pub fn resolve_typenames<'a>(
 
 
                 ns.events.push(EventDecl {
                 ns.events.push(EventDecl {
                     tags: Vec::new(),
                     tags: Vec::new(),
-                    name: def.name.as_ref().unwrap().name.to_owned(),
-                    loc: def.name.as_ref().unwrap().loc,
+                    id: def.name.as_ref().unwrap().to_owned(),
+                    loc: def.loc,
                     contract: None,
                     contract: None,
                     fields: Vec::new(),
                     fields: Vec::new(),
                     anonymous: def.anonymous,
                     anonymous: def.anonymous,
@@ -411,7 +411,7 @@ pub fn resolve_fields(delay: ResolveFields, file_no: usize, ns: &mut Namespace)
         let (tags, fields) = event_decl(event.pt, file_no, &event.comments, contract_no, ns);
         let (tags, fields) = event_decl(event.pt, file_no, &event.comments, contract_no, ns);
 
 
         ns.events[event.event_no].signature =
         ns.events[event.event_no].signature =
-            ns.signature(&ns.events[event.event_no].name, &fields);
+            ns.signature(&ns.events[event.event_no].id.name, &fields);
         ns.events[event.event_no].fields = fields;
         ns.events[event.event_no].fields = fields;
         ns.events[event.event_no].tags = tags;
         ns.events[event.event_no].tags = tags;
     }
     }
@@ -513,7 +513,7 @@ fn resolve_contract<'a>(
                     broken = true;
                     broken = true;
                 }
                 }
             }
             }
-            pt::ContractPart::EventDefinition(ref pt) => {
+            pt::ContractPart::EventDefinition(pt) => {
                 annotions_not_allowed(&parts.annotations, "event", ns);
                 annotions_not_allowed(&parts.annotations, "event", ns);
 
 
                 let event_no = ns.events.len();
                 let event_no = ns.events.len();
@@ -536,8 +536,8 @@ fn resolve_contract<'a>(
 
 
                 ns.events.push(EventDecl {
                 ns.events.push(EventDecl {
                     tags: Vec::new(),
                     tags: Vec::new(),
-                    name: pt.name.as_ref().unwrap().name.to_owned(),
-                    loc: pt.name.as_ref().unwrap().loc,
+                    id: pt.name.as_ref().unwrap().to_owned(),
+                    loc: pt.loc,
                     contract: Some(contract_no),
                     contract: Some(contract_no),
                     fields: Vec::new(),
                     fields: Vec::new(),
                     anonymous: pt.anonymous,
                     anonymous: pt.anonymous,

+ 8 - 7
src/sema/unused_variable.rs

@@ -516,7 +516,7 @@ pub fn check_unused_events(ns: &mut Namespace) {
             // is there a global event with the same name
             // is there a global event with the same name
             if let Some(ast::Symbol::Event(events)) =
             if let Some(ast::Symbol::Event(events)) =
                 ns.variable_symbols
                 ns.variable_symbols
-                    .get(&(event.loc.file_no(), None, event.name.to_owned()))
+                    .get(&(event.loc.file_no(), None, event.id.name.to_owned()))
             {
             {
                 shadowing_events(event_no, event, &mut shadows, events, ns);
                 shadowing_events(event_no, event, &mut shadows, events, ns);
             }
             }
@@ -525,10 +525,11 @@ pub fn check_unused_events(ns: &mut Namespace) {
             for base_no in ns.contract_bases(contract_no) {
             for base_no in ns.contract_bases(contract_no) {
                 let base_file_no = ns.contracts[base_no].loc.file_no();
                 let base_file_no = ns.contracts[base_no].loc.file_no();
 
 
-                if let Some(ast::Symbol::Event(events)) =
-                    ns.variable_symbols
-                        .get(&(base_file_no, Some(base_no), event.name.to_owned()))
-                {
+                if let Some(ast::Symbol::Event(events)) = ns.variable_symbols.get(&(
+                    base_file_no,
+                    Some(base_no),
+                    event.id.name.to_owned(),
+                )) {
                     shadowing_events(event_no, event, &mut shadows, events, ns);
                     shadowing_events(event_no, event, &mut shadows, events, ns);
                 }
                 }
             }
             }
@@ -552,8 +553,8 @@ pub fn check_unused_events(ns: &mut Namespace) {
             }
             }
 
 
             ns.diagnostics.push(Diagnostic::warning(
             ns.diagnostics.push(Diagnostic::warning(
-                event.loc,
-                format!("event '{}' has never been emitted", event.name),
+                event.id.loc,
+                format!("event '{}' has never been emitted", event.id),
             ));
             ));
         }
         }
     }
     }

+ 1 - 1
tests/contract_testcases/polkadot/events/emit_04.sol

@@ -6,6 +6,6 @@
             }
             }
         }
         }
 // ---- Expect: diagnostics ----
 // ---- Expect: diagnostics ----
-// error: 5:17-44: event cannot be emmited with named fields as 2 of its fields do not have names
+// error: 5:17-44: event cannot be emitted with named fields as 2 of its fields do not have names
 // 	note 3:19-22: definition of foo
 // 	note 3:19-22: definition of foo
 // error: 5:36-37: duplicate argument with name 'a'
 // error: 5:36-37: duplicate argument with name 'a'

+ 1 - 1
tests/contract_testcases/polkadot/events/emit_05.sol

@@ -6,5 +6,5 @@
             }
             }
         }
         }
 // ---- Expect: diagnostics ----
 // ---- Expect: diagnostics ----
-// error: 5:17-44: event cannot be emmited with named fields as 2 of its fields do not have names
+// error: 5:17-44: event cannot be emitted with named fields as 2 of its fields do not have names
 // 	note 3:19-22: definition of foo
 // 	note 3:19-22: definition of foo

+ 59 - 0
tests/contract_testcases/polkadot/events/selector.sol

@@ -0,0 +1,59 @@
+event E2(bool);
+
+contract A {
+    event E2(bytes4);
+    event E1(int256);
+    event E3(int256) anonymous;
+
+    function a() public returns (bytes32) {
+        return E2.selector;
+    }
+
+    function b() public returns (bytes32) {
+        return E3.selector;
+    }
+}
+
+function test() returns (bytes32) {
+    return A.E2.selector;
+}
+
+contract B {
+    function a() public returns (bytes32) {
+        return E2.selector;
+    }
+
+    function b() public returns (bytes32) {
+        return A.E2.selector;
+    }
+
+    function c() public returns (bytes32) {
+        return A.E1.selector;
+    }
+}
+
+contract C is A {
+    function x() public returns (bytes32) {
+        return E2.selector;
+    }
+}
+
+contract D is B {
+    function y() public returns (bytes32) {
+        return E2.selector;
+    }
+
+    function z() public returns (bytes32) {
+        return E3.selector;
+    }
+}
+
+// ---- Expect: diagnostics ----
+// error: 9:16-27: multiple definitions of event
+// 	note 4:11-13: possible definition of 'E2'
+// 	note 1:7-9: possible definition of 'E2'
+// error: 13:16-27: anonymous event has no selector
+// error: 37:16-27: multiple definitions of event
+// 	note 4:11-13: possible definition of 'E2'
+// 	note 1:7-9: possible definition of 'E2'
+// error: 47:16-18: 'E3' not found

+ 1 - 1
tests/evm.rs

@@ -253,7 +253,7 @@ fn ethereum_solidity_tests() {
         })
         })
         .sum();
         .sum();
 
 
-    assert_eq!(errors, 993);
+    assert_eq!(errors, 986);
 }
 }
 
 
 fn set_file_contents(source: &str, path: &Path) -> (FileResolver, Vec<String>) {
 fn set_file_contents(source: &str, path: &Path) -> (FileResolver, Vec<String>) {

+ 3 - 3
tests/solana.rs

@@ -18,7 +18,7 @@ use solana_rbpf::{
     verifier::{RequisiteVerifier, TautologyVerifier},
     verifier::{RequisiteVerifier, TautologyVerifier},
     vm::{BuiltinProgram, Config, ContextObject, EbpfVm, ProgramResult, StableResult},
     vm::{BuiltinProgram, Config, ContextObject, EbpfVm, ProgramResult, StableResult},
 };
 };
-use solang::abi::anchor::discriminator;
+use solang::abi::anchor::function_discriminator;
 use solang::{
 use solang::{
     abi::anchor::generate_anchor_idl,
     abi::anchor::generate_anchor_idl,
     codegen::{OptimizationLevel, Options},
     codegen::{OptimizationLevel, Options},
@@ -1712,7 +1712,7 @@ impl<'a, 'b> VmFunction<'a, 'b> {
     fn call_with_error_code(&mut self) -> Result<Option<BorshToken>, u64> {
     fn call_with_error_code(&mut self) -> Result<Option<BorshToken>, u64> {
         self.vm.return_data = None;
         self.vm.return_data = None;
         let idl_instr = self.vm.stack[0].idl.as_ref().unwrap().instructions[self.idx].clone();
         let idl_instr = self.vm.stack[0].idl.as_ref().unwrap().instructions[self.idx].clone();
-        let mut calldata = discriminator("global", &idl_instr.name);
+        let mut calldata = function_discriminator(&idl_instr.name);
 
 
         if !self.has_remaining {
         if !self.has_remaining {
             assert_eq!(
             assert_eq!(
@@ -1781,7 +1781,7 @@ impl<'a, 'b> VmFunction<'a, 'b> {
             );
             );
         }
         }
 
 
-        let mut calldata = discriminator("global", &idl_instr.name);
+        let mut calldata = function_discriminator(&idl_instr.name);
         if let Some(args) = self.arguments {
         if let Some(args) = self.arguments {
             let mut encoded_data = encode_arguments(args);
             let mut encoded_data = encode_arguments(args);
             calldata.append(&mut encoded_data);
             calldata.append(&mut encoded_data);

+ 41 - 11
tests/solana_tests/events.rs

@@ -1,9 +1,9 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: Apache-2.0
 
 
-use crate::build_solidity;
+use crate::{borsh_encoding::BorshToken, build_solidity};
 use borsh::BorshDeserialize;
 use borsh::BorshDeserialize;
 use borsh_derive::BorshDeserialize;
 use borsh_derive::BorshDeserialize;
-use sha2::{Digest, Sha256};
+use solang::abi::anchor::event_discriminator;
 
 
 #[test]
 #[test]
 fn simple_event() {
 fn simple_event() {
@@ -21,6 +21,10 @@ fn simple_event() {
             function go() public {
             function go() public {
                 emit myevent(1, -2);
                 emit myevent(1, -2);
             }
             }
+
+            function selector() public returns (bytes8) {
+                return myevent.selector;
+            }
         }"#,
         }"#,
     );
     );
 
 
@@ -36,13 +40,28 @@ fn simple_event() {
 
 
     let encoded = &vm.events[0][0];
     let encoded = &vm.events[0][0];
 
 
-    let discriminator = calculate_discriminator("myevent");
+    let discriminator = event_discriminator("myevent");
 
 
     assert_eq!(&encoded[..8], &discriminator[..]);
     assert_eq!(&encoded[..8], &discriminator[..]);
 
 
     let decoded = MyEvent::try_from_slice(&encoded[8..]).unwrap();
     let decoded = MyEvent::try_from_slice(&encoded[8..]).unwrap();
     assert_eq!(decoded.a, 1);
     assert_eq!(decoded.a, 1);
     assert_eq!(decoded.b, -2);
     assert_eq!(decoded.b, -2);
+
+    let returns = vm.function("selector").call().unwrap();
+
+    assert_eq!(
+        returns,
+        BorshToken::FixedArray(
+            discriminator
+                .into_iter()
+                .map(|v| BorshToken::Uint {
+                    width: 8,
+                    value: v.into()
+                })
+                .collect()
+        )
+    );
 }
 }
 
 
 #[test]
 #[test]
@@ -78,6 +97,10 @@ fn less_simple_event() {
             function go() public {
             function go() public {
                 emit MyOtherEvent(-102, "foobar", [55431, 7452], S({ f1: 102, f2: true}));
                 emit MyOtherEvent(-102, "foobar", [55431, 7452], S({ f1: 102, f2: true}));
             }
             }
+
+            function selector() public returns (bytes8) {
+                return MyOtherEvent.selector;
+            }
         }"#,
         }"#,
     );
     );
 
 
@@ -93,7 +116,7 @@ fn less_simple_event() {
 
 
     let encoded = &vm.events[0][0];
     let encoded = &vm.events[0][0];
 
 
-    let discriminator = calculate_discriminator("MyOtherEvent");
+    let discriminator = event_discriminator("MyOtherEvent");
     assert_eq!(&encoded[..8], &discriminator[..]);
     assert_eq!(&encoded[..8], &discriminator[..]);
 
 
     let decoded = MyOtherEvent::try_from_slice(&encoded[8..]).unwrap();
     let decoded = MyOtherEvent::try_from_slice(&encoded[8..]).unwrap();
@@ -102,12 +125,19 @@ fn less_simple_event() {
     assert_eq!(decoded.b, "foobar");
     assert_eq!(decoded.b, "foobar");
     assert_eq!(decoded.c, [55431, 7452]);
     assert_eq!(decoded.c, [55431, 7452]);
     assert_eq!(decoded.d, S { f1: 102, f2: true });
     assert_eq!(decoded.d, S { f1: 102, f2: true });
-}
 
 
-fn calculate_discriminator(event_name: &str) -> Vec<u8> {
-    let image = format!("event:{event_name}");
-    let mut hasher = Sha256::new();
-    hasher.update(image.as_bytes());
-    let finalized = hasher.finalize();
-    finalized[..8].to_vec()
+    let returns = vm.function("selector").call().unwrap();
+
+    assert_eq!(
+        returns,
+        BorshToken::FixedArray(
+            discriminator
+                .into_iter()
+                .map(|v| BorshToken::Uint {
+                    width: 8,
+                    value: v.into()
+                })
+                .collect()
+        )
+    );
 }
 }