ソースを参照

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 年 前
コミット
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
 interface has a `subscribe()` function. Another is example is
 `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,
 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
 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::
     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.

+ 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
 /// value anchor uses to dispatch function calls on. This should match
 /// 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
     // https://github.com/coral-xyz/anchor/blob/master/ts/packages/anchor/src/coder/borsh/instruction.ts#L389
     let normalized = name
         .from_case(Case::Camel)
         .without_boundaries(&[Boundary::LowerDigit])
         .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()
 }
 
@@ -85,7 +96,7 @@ fn idl_events(
             }
 
             events.push(IdlEvent {
-                name: def.name.clone(),
+                name: def.id.name.clone(),
                 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];
 
                     ABI {
-                        name: event.name.to_owned(),
+                        name: event.id.name.to_owned(),
                         mutability: String::new(),
                         inputs: Some(
                             event

+ 1 - 1
src/abi/polkadot.rs

@@ -449,7 +449,7 @@ pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
                     .done()
             })
             .collect::<Vec<_>>();
-        EventSpec::new(e.name.clone())
+        EventSpec::new(e.id.name.clone())
             .args(args)
             .docs(vec![render(&e.tags).as_str()])
             .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 {
-                name: &event_decl.name,
+                name: &event_decl.id.name,
                 contract: event_decl
                     .contract
                     .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),
                 dev: get_tag("dev", &event_decl.tags),
                 anonymous: event_decl.anonymous,
-                loc: event_decl.loc,
+                loc: event_decl.id.loc,
                 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 itertools::Itertools;
 use serde_json::Value as JsonValue;
-use solang::abi::anchor::discriminator;
+use solang::abi::anchor::function_discriminator;
 use solang_parser::lexer::is_keyword;
 use std::{ffi::OsStr, fs::File, io::Write, path::PathBuf, process::exit};
 
@@ -254,7 +254,7 @@ fn instruction(
             .1;
 
         // The anchor discriminator is what Solidity calls a selector
-        let selector = discriminator("global", &instr.name);
+        let selector = function_discriminator(&instr.name);
 
         write!(
             f,

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

@@ -1676,8 +1676,8 @@ impl<'a> Builder<'a> {
             self.hovers.push((
                 file_no,
                 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[..]),
                 },
             ));
@@ -1687,7 +1687,7 @@ impl<'a> Builder<'a> {
                     def_path: file.path.clone(),
                     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,
         opt: &Options,
     );
+
+    /// Generates the selector
+    fn selector(&self, emitting_contract_no: usize) -> Vec<u8>;
 }
 
 /// 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,
 }
 
-/// 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<'_> {
+    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(
         &self,
         contract_no: usize,
@@ -46,7 +55,9 @@ impl EventEmitter for PolkadotEventEmitter<'_> {
         let loc = pt::Loc::Builtin;
         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(contract_no)].id;
+        let contract_name = &self.ns.contracts[event.contract.unwrap_or(contract_no)]
+            .id
+            .name;
         let hash_len = Box::new(Expression::NumberLiteral {
             loc,
             ty: Type::Uint(32),
@@ -67,13 +78,14 @@ impl EventEmitter for PolkadotEventEmitter<'_> {
         // Events that are not anonymous always have themselves as a topic.
         // This is static and can be calculated at compile time.
         if !event.anonymous {
+            let topic_hash = self.selector(contract_no);
+
             // 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 {
                 loc,
                 ty: Type::Slice(Type::Uint(8).into()),
                 size: hash_len.clone(),
-                initializer: Some(topic_hash(encoded.as_bytes())),
+                initializer: Some(topic_hash),
             });
         };
 
@@ -83,14 +95,9 @@ impl EventEmitter for PolkadotEventEmitter<'_> {
             .iter()
             .filter(|field| field.indexed)
             .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();
 

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

@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: Apache-2.0
 
+use crate::abi::anchor::event_discriminator;
 use crate::codegen::cfg::{ControlFlowGraph, Instr};
 use crate::codegen::encoding::abi_encode;
 use crate::codegen::events::EventEmitter;
@@ -8,7 +9,6 @@ use crate::codegen::vartable::Vartable;
 use crate::codegen::{Expression, Options};
 use crate::sema::ast;
 use crate::sema::ast::{Function, Namespace, Type};
-use sha2::{Digest, Sha256};
 use solang_parser::pt::Loc;
 
 /// 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<'_> {
+    fn selector(&self, _: usize) -> Vec<u8> {
+        event_discriminator(&self.ns.events[self.event_no].id.name)
+    }
+
     fn emit(
         &self,
         contract_no: usize,
@@ -29,15 +33,10 @@ impl EventEmitter for SolanaEventEmitter<'_> {
         vartab: &mut Vartable,
         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 {
             loc: Loc::Codegen,
             ty: Type::Bytes(8),
-            value: result[..8].to_vec(),
+            value: self.selector(contract_no),
         };
 
         let mut codegen_args = self

+ 10 - 0
src/codegen/expression.rs

@@ -14,6 +14,7 @@ use super::{
 use super::{polkadot, Options};
 use crate::codegen::array_boundary::handle_array_assign;
 use crate::codegen::constructor::call_constructor;
+use crate::codegen::events::new_event_emitter;
 use crate::codegen::unused_variable::should_remove_assignment;
 use crate::codegen::{Builtin, Expression};
 use crate::sema::ast::ExternalCallAccounts;
@@ -595,6 +596,15 @@ pub fn expression(
                 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::ExternalFunctionCall { .. }
         | ast::Expression::ExternalFunctionCallRaw { .. }

+ 15 - 8
src/sema/ast.rs

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

+ 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 num_bigint::{BigInt, Sign};
 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::CodeLocation;
 use std::ops::{Shl, Sub};
@@ -69,6 +69,19 @@ pub(super) fn member_access(
         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)
     if let Some(expr) = contract_constant(
         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
 fn type_name_expr(
     loc: &pt::Loc,

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

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

+ 5 - 5
src/sema/statements.rs

@@ -1302,7 +1302,7 @@ fn emit_event(
                         *loc,
                         format!(
                             "event type '{}' has {} fields, {} provided",
-                            event.name,
+                            event.id,
                             event.fields.len(),
                             args.len()
                         ),
@@ -1417,10 +1417,10 @@ fn emit_event(
                     temp_diagnostics.push(Diagnostic::cast_error_with_note(
                         *loc,
                         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;
                 } else if params_len != arguments.len() {
@@ -1454,7 +1454,7 @@ fn emit_event(
                                 format!(
                                     "missing argument '{}' to event '{}'",
                                     param.name_as_str(),
-                                    ns.events[*event_no].name,
+                                    ns.events[*event_no].id,
                                 ),
                             ));
                             continue;

+ 6 - 6
src/sema/types.rs

@@ -129,8 +129,8 @@ pub fn resolve_typenames<'a>(
 
                 ns.events.push(EventDecl {
                     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,
                     fields: Vec::new(),
                     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);
 
         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].tags = tags;
     }
@@ -513,7 +513,7 @@ fn resolve_contract<'a>(
                     broken = true;
                 }
             }
-            pt::ContractPart::EventDefinition(ref pt) => {
+            pt::ContractPart::EventDefinition(pt) => {
                 annotions_not_allowed(&parts.annotations, "event", ns);
 
                 let event_no = ns.events.len();
@@ -536,8 +536,8 @@ fn resolve_contract<'a>(
 
                 ns.events.push(EventDecl {
                     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),
                     fields: Vec::new(),
                     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
             if let Some(ast::Symbol::Event(events)) =
                 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);
             }
@@ -525,10 +525,11 @@ pub fn check_unused_events(ns: &mut Namespace) {
             for base_no in ns.contract_bases(contract_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);
                 }
             }
@@ -552,8 +553,8 @@ pub fn check_unused_events(ns: &mut Namespace) {
             }
 
             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 ----
-// 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
 // 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 ----
-// 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

+ 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();
 
-    assert_eq!(errors, 993);
+    assert_eq!(errors, 986);
 }
 
 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},
     vm::{BuiltinProgram, Config, ContextObject, EbpfVm, ProgramResult, StableResult},
 };
-use solang::abi::anchor::discriminator;
+use solang::abi::anchor::function_discriminator;
 use solang::{
     abi::anchor::generate_anchor_idl,
     codegen::{OptimizationLevel, Options},
@@ -1712,7 +1712,7 @@ impl<'a, 'b> VmFunction<'a, 'b> {
     fn call_with_error_code(&mut self) -> Result<Option<BorshToken>, u64> {
         self.vm.return_data = None;
         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 {
             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 {
             let mut encoded_data = encode_arguments(args);
             calldata.append(&mut encoded_data);

+ 41 - 11
tests/solana_tests/events.rs

@@ -1,9 +1,9 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use crate::build_solidity;
+use crate::{borsh_encoding::BorshToken, build_solidity};
 use borsh::BorshDeserialize;
 use borsh_derive::BorshDeserialize;
-use sha2::{Digest, Sha256};
+use solang::abi::anchor::event_discriminator;
 
 #[test]
 fn simple_event() {
@@ -21,6 +21,10 @@ fn simple_event() {
             function go() public {
                 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 discriminator = calculate_discriminator("myevent");
+    let discriminator = event_discriminator("myevent");
 
     assert_eq!(&encoded[..8], &discriminator[..]);
 
     let decoded = MyEvent::try_from_slice(&encoded[8..]).unwrap();
     assert_eq!(decoded.a, 1);
     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]
@@ -78,6 +97,10 @@ fn less_simple_event() {
             function go() public {
                 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 discriminator = calculate_discriminator("MyOtherEvent");
+    let discriminator = event_discriminator("MyOtherEvent");
     assert_eq!(&encoded[..8], &discriminator[..]);
 
     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.c, [55431, 7452]);
     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()
+        )
+    );
 }