Browse Source

Polkadot: `ink!` 5.0 (#1632)

Upgrade Solang to be compatible with [`ink!` v5.0
](https://use.ink/faq/migrating-from-ink-4-to-5).

- Simplify events
- Latest substrate node on CI
- Update tests and docs
Cyrill Leutwiler 1 year ago
parent
commit
28c86b3f38

+ 1 - 1
.github/workflows/test.yml

@@ -390,7 +390,7 @@ jobs:
       # We can't run substrate node as a github actions service, since it requires
       # command line arguments. See https://github.com/actions/runner/pull/1152
     - name: Start substrate
-      run: echo id=$(docker run -d -p 9944:9944 ghcr.io/hyperledger/solang-substrate-ci:ad6da01 substrate-contracts-node --dev --rpc-external -lwarn,runtime::contracts=trace) >> $GITHUB_OUTPUT
+      run: echo id=$(docker run -d -p 9944:9944 ghcr.io/hyperledger/solang-substrate-ci:62a8a6c substrate-contracts-node --dev --rpc-external -lwarn,runtime::contracts=trace) >> $GITHUB_OUTPUT
       id: substrate
     - uses: actions/download-artifact@v3
       with:

+ 3 - 3
Cargo.toml

@@ -54,8 +54,8 @@ anchor-syn = { version = "0.29.0", features = ["idl-build"] }
 convert_case = "0.6"
 parse-display = "0.9"
 parity-scale-codec = "3.6"
-ink_env = "4.3.0"
-ink_metadata = "4.3.0"
+ink_env = "5.0.0"
+ink_metadata = "5.0.0"
 scale-info = "2.10"
 petgraph = "0.6"
 wasmparser = "0.121.0"
@@ -89,7 +89,7 @@ borsh = "1.1"
 borsh-derive = "1.1"
 rayon = "1"
 walkdir = "2.4"
-ink_primitives = "4.3.0"
+ink_primitives = "5.0.0"
 wasm_host_attr = { path = "tests/wasm_host_attr" }
 num-bigint = { version = "0.4", features = ["rand", "serde"]}
 

+ 8 - 6
docs/language/events.rst

@@ -32,9 +32,9 @@ syntax.
     be passed for Solana's ``sol_log_data`` system call, regardless if the ``indexed`` keyword is present or not.
     This behavior follows what Solana's Anchor framework does.
 
-In Polkadot, the topic fields are always the hash of the value of the field. Ethereum only hashes fields
-which do not fit in the 32 bytes. Since a cryptographic hash is used, it is only possible to compare the topic against a
-known value.
+In Polkadot, field topics are culculated the same as in `ink! v5.0 <https://use.ink/basics/events/#topics>`_:
+Topic fields are either the encoded value of the field or its blake2b256 hash
+if the encoded value length exceeds 32 bytes.
 
 An event can be declared in a contract, or outside.
 
@@ -49,8 +49,10 @@ make it clearer which exact event is being emitted.
 .. include:: ../examples/event_positional_fields.sol
   :code: solidity
 
-In the transaction log, the first topic of an event is the keccak256 hash of the signature of the
-event. The signature is the event name, followed by the fields types in a comma separated list in parentheses. So
-the first topic for the second UserModified event would be the keccak256 hash of ``UserModified(address,uint64)``.
+In the transaction log, the topic of an event is the blake2b256 hash of the signature of the
+event. The signature is the event name, followed by the fields types in a comma separated list in parentheses,
+like event signatures in `Ethereum Solidity <https://docs.soliditylang.org/en/v0.8.25/abi-spec.html#events>`_. So
+the first topic for the second UserModified event would be the blake2b256 hash of ``UserModified(address,uint64)``.
 You can leave this topic out by declaring the event ``anonymous``. This makes the event slightly smaller (32 bytes
 less) and makes it possible to have 4 ``indexed`` fields rather than 3.
+

+ 1 - 1
integration/polkadot/UniswapV2Factory.spec.ts

@@ -58,7 +58,7 @@ describe('UniswapV2Factory', () => {
 
     let events: DecodedEvent[] = res0.contractEvents;
     expect(events.length).toEqual(1)
-    expect(events[0].event.identifier).toBe('PairCreated')
+    expect(events[0].event.identifier).toBe('UniswapV2Factory::PairCreated')
     expect(events[0].args[0].toString()).toBe(TEST_ADDRESSES[0])
     expect(events[0].args[1].toString()).toBe(TEST_ADDRESSES[1])
     expect(events[0].args[3].eq(1)).toBeTruthy();

+ 22 - 47
integration/polkadot/events.spec.ts

@@ -29,62 +29,37 @@ describe('Deploy events contract and test event data, docs and topics', () => {
 
         expect(events.length).toEqual(4);
 
-        expect(events[0].event.identifier).toBe("foo1");
+        expect(events[0].event.identifier).toBe("Events::foo1");
         expect(events[0].event.docs).toEqual(["Ladida tada"]);
         expect(events[0].args.map(a => a.toJSON())).toEqual([254, "hello there"]);
 
-        expect(events[1].event.identifier).toBe("foo2");
+        expect(events[1].event.identifier).toBe("Events::foo2");
         expect(events[1].event.docs).toEqual(["Event Foo2\n\nJust a test\n\nAuthor: them is me"]);
         expect(events[1].args.map(a => a.toJSON())).toEqual(["0x7fffffffffffffff", "minor", deploy_contract.address.toString()]);
 
-        expect(events[2].event.identifier).toBe("ThisEventTopicShouldGetHashed");
+        expect(events[2].event.identifier).toBe("Events::ThisEventTopicShouldGetHashed");
         expect(events[2].args.map(a => a.toJSON())).toEqual([alice.address]);
 
-        // In ink! the 3rd event does look like this:
-        //
-        //  #[ink(event)]
-        //  pub struct ThisEventTopicShouldGetHashed {
-        //      #[ink(topic)]
-        //      caller: AccountId,
-        //  }
-        //
-        // It yields the following event topics:
-        //
-        //  topics: [
-        //      0x5dde952854d38c37cff349bfc574a48a831de385b82457a5c25d9d39c220f3a7
-        //      0xa5af79de4a26a64813f980ffbb64ac0d7c278f67b17721423daed26ec5d3fe51
-        //  ]
-        //
-        // So we expect our solidity contract to produce the exact same topics:
-
-        let hashed_event_topics = await conn.query.system.eventTopics("0x5dde952854d38c37cff349bfc574a48a831de385b82457a5c25d9d39c220f3a7");
-        expect(hashed_event_topics.length).toBe(1);
-        let hashed_topics = await conn.query.system.eventTopics("0xa5af79de4a26a64813f980ffbb64ac0d7c278f67b17721423daed26ec5d3fe51");
-        expect(hashed_topics.length).toBe(1);
-
-        expect(events[3].event.identifier).toBe("Event");
+        // Expect the 3rd event to yield the following event topics:
+        // - blake2x256 sum of its signature: 'ThisEventTopicShouldGetHashed(address)'
+        // - Address of the caller
+
+        let field_topic = await conn.query.system.eventTopics(alice.addressRaw);
+        expect(field_topic.length).toBe(1);
+
+        let event_topic = await conn.query.system.eventTopics("0x95c29b3e1b835071ab157a22d89cfc81d176add91127a1ee8766abf406a2cbc3");
+        expect(event_topic.length).toBe(1);
+
+        expect(events[3].event.identifier).toBe("Events::Event");
         expect(events[3].args.map(a => a.toJSON())).toEqual([true]);
 
-        // In ink! the 4th event does look like this:
-        //
-        //  #[ink(event)]
-        //  pub struct Event {
-        //      #[ink(topic)]
-        //      something: bool,
-        //  }
-        //
-        // It yields the following event topics:
-        //
-        //  topics: [
-        //      0x004576656e74733a3a4576656e74000000000000000000000000000000000000
-        //      0x604576656e74733a3a4576656e743a3a736f6d657468696e6701000000000000
-        //  ]
-        //
-        // So we expect our solidity contract to produce the exact same topics:
-
-        let unhashed_event_topics = await conn.query.system.eventTopics("0x004576656e74733a3a4576656e74000000000000000000000000000000000000");
-        expect(unhashed_event_topics.length).toBe(1);
-        let unhashed_topics = await conn.query.system.eventTopics("0x604576656e74733a3a4576656e743a3a736f6d657468696e6701000000000000");
-        expect(unhashed_topics.length).toBe(1);
+        // The 4th event yields the following event topics:
+        // - blake2x256 sum of its signature: 'Event(bool)'
+        // - unhashed data (because encoded length is <= 32 bytes) of 'true'
+
+        field_topic = await conn.query.system.eventTopics("0x0100000000000000000000000000000000000000000000000000000000000000");
+        expect(field_topic.length).toBe(1);
+        event_topic = await conn.query.system.eventTopics("0xc2bc7a077121efada8bc6a0af16c1e886406e8c2d1716979cb1b92098d8b49bc");
+        expect(event_topic.length).toBe(1);
     });
 });

+ 1 - 1
integration/polkadot/msg_sender.spec.ts

@@ -45,7 +45,7 @@ describe('Deploy mytoken contract and test', () => {
 
         expect(events.length).toEqual(1);
 
-        expect(events[0].event.identifier).toBe("Debugging");
+        expect(events[0].event.identifier).toBe("mytokenEvent::Debugging");
         expect(events[0].args.map(a => a.toJSON())).toEqual([alice.address]);
     });
 });

+ 2 - 1
integration/polkadot/upgradeable_proxy.spec.ts

@@ -19,8 +19,9 @@ describe('Deploy the upgradable proxy and implementations; expect the upgrade me
         let result: any = await transaction(proxy.tx.upgradeToAndCall({ gasLimit }, ...params), aliceKeypair());
 
         let events: DecodedEvent[] = result.contractEvents;
+        console.log(events);
         expect(events.length).toEqual(1);
-        expect(events[0].event.identifier).toBe("Upgraded");
+        expect(events[0].event.identifier).toBe("UpgradeableProxy::Upgraded");
         expect(events[0].args.map(a => a.toJSON())[0]).toEqual(params[0].toJSON());
     }
 

+ 2 - 2
integration/subxt-tests/Cargo.toml

@@ -20,8 +20,8 @@ serde_json = "1.0.96"
 sp-keyring = "24.0.0"
 subxt = { version = "0.31.0", features = ["substrate-compat"] }
 tokio = {version = "1.28.2", features = ["rt-multi-thread", "macros", "time"]}
-contract-metadata = "3.0.1"
-contract-transcode = "3.0.1"
+contract-metadata = "4.0.2"
+contract-transcode = "4.0.2"
 
 [workspace]
 members = []

+ 1 - 1
integration/subxt-tests/src/cases/asserts.rs

@@ -59,7 +59,7 @@ async fn case() -> anyhow::Result<()> {
         .unwrap_err();
     assert!(res
         .to_string()
-        .contains("ModuleError { index: 8, error: [25, 0, 0, 0] }"));
+        .contains("ModuleError { index: 8, error: [26, 0, 0, 0] }"));
 
     // state should not change after failed operation
     let rv = contract

+ 0 - 6
integration/subxt-tests/src/cases/events.rs

@@ -32,9 +32,6 @@ async fn case() -> anyhow::Result<()> {
 
     let e1_buffer = &mut e1.data.as_slice();
 
-    let topic = e1_buffer.read_byte()?;
-    assert_eq!(topic, 0);
-
     // mimic the solidity struct type
     #[derive(Decode)]
     struct Foo1 {
@@ -48,9 +45,6 @@ async fn case() -> anyhow::Result<()> {
     let e2 = &rs[1];
     let e2_buffer = &mut e2.data.as_slice();
 
-    let topic = e2_buffer.read_byte()?;
-    assert_eq!(topic, 1);
-
     // mimic the solidity struct type
     #[derive(Decode)]
     struct Foo2 {

+ 0 - 4
integration/subxt-tests/src/cases/msg_sender.rs

@@ -87,10 +87,6 @@ async fn case() -> anyhow::Result<()> {
 
     let mut evt_buffer = evt.data.as_slice();
 
-    let topic_id = evt_buffer.read_byte()?;
-
-    assert_eq!(topic_id, 0);
-
     let addr = <AccountId32>::decode(&mut evt_buffer)?;
 
     assert_eq!(addr, sp_keyring::AccountKeyring::Alice.to_account_id());

+ 25 - 10
src/abi/polkadot.rs

@@ -3,6 +3,7 @@ use contract_metadata::{
     CodeHash, Compiler, Contract, ContractMetadata, Language, Source, SourceCompiler,
     SourceLanguage, SourceWasm,
 };
+use ink_env::hash::{Blake2x256, CryptoHash};
 use ink_metadata::{
     layout::{FieldLayout, Layout, LayoutKey, LeafLayout, RootLayout, StructLayout},
     ConstructorSpec, ContractSpec, EnvironmentSpec, EventParamSpec, EventSpec, InkProject,
@@ -21,6 +22,7 @@ use semver::Version;
 use solang_parser::pt;
 
 use crate::{
+    codegen::polkadot::SCRATCH_SIZE,
     codegen::revert::{SolidityError, ERROR_SELECTOR, PANIC_SELECTOR},
     sema::{
         ast::{self, ArrayLength, EventDecl, Function},
@@ -285,7 +287,7 @@ fn type_to_storage_layout(
 }
 
 /// Generate `InkProject` from `ast::Type` and `ast::Namespace`
-pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
+pub fn gen_project<'a>(contract_no: usize, ns: &'a ast::Namespace) -> InkProject {
     let mut registry = PortableRegistryBuilder::new();
 
     // This is only used by off-chain tooling. At the moment there is no such tooling available yet.
@@ -302,6 +304,7 @@ pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
                 let root = RootLayout::new(
                     layout_key,
                     type_to_storage_layout(ty, layout_key, &registry),
+                    ty.into(),
                 );
                 Some(FieldLayout::new(var.name.clone(), root))
             } else {
@@ -341,7 +344,7 @@ pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
         .payable(payable)
         .args(args)
         .docs(vec![render(&f.tags).as_str()])
-        .returns(ReturnTypeSpec::new(None))
+        .returns(ReturnTypeSpec::new(TypeSpec::default()))
         .done()
     };
 
@@ -407,7 +410,7 @@ pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
                 Some(TypeSpec::new(ty.into(), path))
             }
         };
-        let ret_type = ReturnTypeSpec::new(ret_spec);
+        let ret_type = ReturnTypeSpec::new(ret_spec.unwrap_or_default());
         let args = f
             .params
             .iter()
@@ -445,7 +448,8 @@ pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
         .map(message_spec)
         .collect::<Vec<MessageSpec<PortableForm>>>();
 
-    let mut event_spec = |e: &EventDecl| -> EventSpec<PortableForm> {
+    // ink! v5 ABI wants declared events to be unique; collect the signature into a HashMap
+    let mut event_spec = |e: &'a EventDecl| -> (&'a str, EventSpec<PortableForm>) {
         let args = e
             .fields
             .iter()
@@ -460,19 +464,29 @@ pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
                     .done()
             })
             .collect::<Vec<_>>();
-        EventSpec::new(e.id.name.clone())
+        let topic = (!e.anonymous).then(|| {
+            let mut buf = [0; 32];
+            <Blake2x256 as CryptoHash>::hash(e.signature.as_bytes(), &mut buf);
+            buf
+        });
+        let event = EventSpec::new(e.id.name.clone())
             .args(args)
             .docs(vec![render(&e.tags).as_str()])
-            .done()
+            .signature_topic(topic)
+            .module_path(ns.contracts[contract_no].id.name.as_str())
+            .done();
+        let signature = e.signature.as_str();
+
+        (signature, event)
     };
 
     let events = ns.contracts[contract_no]
         .emits_events
         .iter()
-        .map(|event_no| {
-            let event = &ns.events[*event_no];
-            event_spec(event)
-        })
+        .map(|event_no| event_spec(&ns.events[*event_no]))
+        .collect::<std::collections::HashMap<&str, EventSpec<PortableForm>>>()
+        .drain()
+        .map(|(_, spec)| spec)
         .collect::<Vec<EventSpec<PortableForm>>>();
 
     let environment: EnvironmentSpec<PortableForm> = EnvironmentSpec::new()
@@ -510,6 +524,7 @@ pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
             primitive_to_ty(&ast::Type::Uint(64), &mut registry).into(),
             path!("Timestamp"),
         ))
+        .static_buffer_size(SCRATCH_SIZE as usize)
         .done();
 
     let mut error_definitions = vec![

+ 25 - 85
src/codegen/events/polkadot.rs

@@ -1,6 +1,5 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use std::collections::VecDeque;
 use std::vec;
 
 use crate::codegen::cfg::{ControlFlowGraph, Instr};
@@ -11,11 +10,12 @@ use crate::codegen::vartable::Vartable;
 use crate::codegen::{Builtin, Expression, Options};
 use crate::sema::ast::{self, Function, Namespace, RetrieveType, Type};
 use ink_env::hash::{Blake2x256, CryptoHash};
-use parity_scale_codec::Encode;
 use solang_parser::pt;
 
-/// This struct implements the trait 'EventEmitter' in order to handle the emission of events
-/// for Polkadot
+/// Implements [EventEmitter] to handle the emission of events on Polkadot.
+/// Data and topic encoding follow [ink! v5.0][0].
+///
+/// [0]: https://use.ink/basics/events/#topics
 pub(super) struct PolkadotEventEmitter<'a> {
     /// Arguments passed to the event
     pub(super) args: &'a [ast::Expression],
@@ -24,23 +24,10 @@ pub(super) struct PolkadotEventEmitter<'a> {
 }
 
 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.
+    fn selector(&self, _emitting_contract_no: usize) -> Vec<u8> {
+        let signature = self.ns.events[self.event_no].signature.as_bytes();
         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);
-        };
+        <Blake2x256 as CryptoHash>::hash(signature, &mut buf);
         buf.into()
     }
 
@@ -54,53 +41,24 @@ 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
-            .name;
         let hash_len = Box::new(Expression::NumberLiteral {
             loc,
             ty: Type::Uint(32),
             value: 32.into(),
         });
-        let id = self.ns.contracts[contract_no]
-            .emits_events
-            .iter()
-            .position(|e| *e == self.event_no)
-            .expect("contract emits this event");
-        let mut data = vec![Expression::NumberLiteral {
-            loc,
-            ty: Type::Uint(8),
-            value: id.into(),
-        }];
-        let mut topics = vec![];
+        let (mut data, mut topics) = (Vec::new(), Vec::new());
 
         // 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
             topics.push(Expression::AllocDynamicBytes {
                 loc,
                 ty: Type::Slice(Type::Uint(8).into()),
                 size: hash_len.clone(),
-                initializer: Some(topic_hash),
+                initializer: self.selector(contract_no).into(),
             });
         };
 
-        // Topic prefixes are static and can be calculated at compile time.
-        let mut topic_prefixes: VecDeque<Vec<u8>> = event
-            .fields
-            .iter()
-            .filter(|field| field.indexed)
-            .map(|field| {
-                format!("{}::{}::{}", contract_name, &event.id, &field.name_as_str())
-                    .into_bytes()
-                    .encode()
-            })
-            .collect();
-
         for (ast_exp, field) in self.args.iter().zip(event.fields.iter()) {
             let value_exp = expression(ast_exp, cfg, contract_no, Some(func), self.ns, vartab, opt);
             let value_var = vartab.temp_anonymous(&value_exp.ty());
@@ -123,25 +81,7 @@ impl EventEmitter for PolkadotEventEmitter<'_> {
                 continue;
             }
 
-            let encoded = abi_encode(&loc, vec![value], self.ns, vartab, cfg, false).0;
-            let first_prefix = topic_prefixes.pop_front().unwrap();
-            let prefix = Expression::AllocDynamicBytes {
-                loc,
-                ty: Type::Slice(Type::Bytes(1).into()),
-                size: Expression::NumberLiteral {
-                    loc,
-                    ty: Type::Uint(32),
-                    value: first_prefix.len().into(),
-                }
-                .into(),
-                initializer: Some(first_prefix),
-            };
-            let concatenated = Expression::Builtin {
-                loc,
-                kind: Builtin::Concat,
-                tys: vec![Type::DynamicBytes],
-                args: vec![prefix, encoded],
-            };
+            let (value_encoded, size) = abi_encode(&loc, vec![value], self.ns, vartab, cfg, false);
 
             vartab.new_dirty_tracker();
             let var_buffer = vartab.temp_anonymous(&Type::DynamicBytes);
@@ -150,7 +90,7 @@ impl EventEmitter for PolkadotEventEmitter<'_> {
                 Instr::Set {
                     loc,
                     res: var_buffer,
-                    expr: concatenated,
+                    expr: value_encoded,
                 },
             );
             let buffer = Expression::Variable {
@@ -158,25 +98,19 @@ impl EventEmitter for PolkadotEventEmitter<'_> {
                 ty: Type::DynamicBytes,
                 var_no: var_buffer,
             };
-            let compare = Expression::More {
+
+            let hash_topic_block = cfg.new_basic_block("hash_topic".into());
+            let done_block = cfg.new_basic_block("done".into());
+            let size_is_greater_than_hash_length = Expression::More {
                 loc,
                 signed: false,
-                left: Expression::Builtin {
-                    loc,
-                    tys: vec![Type::Uint(32)],
-                    kind: Builtin::ArrayLength,
-                    args: vec![buffer.clone()],
-                }
-                .into(),
+                left: size.clone().into(),
                 right: hash_len.clone(),
             };
-
-            let hash_topic_block = cfg.new_basic_block("hash_topic".into());
-            let done_block = cfg.new_basic_block("done".into());
             cfg.add(
                 vartab,
                 Instr::BranchCond {
-                    cond: compare,
+                    cond: size_is_greater_than_hash_length,
                     true_block: hash_topic_block,
                     false_block: done_block,
                 },
@@ -209,12 +143,18 @@ impl EventEmitter for PolkadotEventEmitter<'_> {
             topics.push(buffer);
         }
 
-        let data = abi_encode(&loc, data, self.ns, vartab, cfg, false).0;
+        let data = self
+            .args
+            .iter()
+            .map(|e| expression(e, cfg, contract_no, Some(func), self.ns, vartab, opt))
+            .collect();
+        let encoded_data = abi_encode(&loc, data, self.ns, vartab, cfg, false).0;
+
         cfg.add(
             vartab,
             Instr::EmitEvent {
                 event_no: self.event_no,
-                data,
+                data: encoded_data,
                 topics,
             },
         );

+ 3 - 0
src/codegen/polkadot.rs

@@ -14,6 +14,9 @@ use crate::{
     sema::ast::{Namespace, Type},
 };
 
+// When using the seal api, we use our own scratch buffer.
+pub const SCRATCH_SIZE: u32 = 32 * 1024;
+
 /// Helper to handle error cases from external function and constructor calls.
 pub(crate) struct RetCodeCheck {
     pub success: usize,

+ 1 - 3
src/emit/polkadot/mod.rs

@@ -2,6 +2,7 @@
 
 use std::ffi::CString;
 
+use crate::codegen::polkadot::SCRATCH_SIZE;
 use crate::codegen::{Options, STORAGE_INITIALIZER};
 use crate::sema::ast::{Contract, Namespace};
 use inkwell::context::Context;
@@ -16,9 +17,6 @@ use crate::emit::{Binary, TargetRuntime};
 mod storage;
 pub(super) mod target;
 
-// When using the seal api, we use our own scratch buffer.
-const SCRATCH_SIZE: u32 = 32 * 1024;
-
 pub struct PolkadotTarget;
 
 impl PolkadotTarget {

+ 2 - 1
src/emit/polkadot/target.rs

@@ -1,10 +1,11 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use crate::codegen::cfg::HashTy;
+use crate::codegen::polkadot::SCRATCH_SIZE;
 use crate::codegen::revert::PanicCode;
 use crate::emit::binary::Binary;
 use crate::emit::expression::expression;
-use crate::emit::polkadot::{PolkadotTarget, SCRATCH_SIZE};
+use crate::emit::polkadot::PolkadotTarget;
 use crate::emit::storage::StorageSlot;
 use crate::emit::{ContractArgs, TargetRuntime, Variable};
 use crate::sema::ast;

+ 402 - 67
testdata/ink/mother.json

@@ -1,26 +1,27 @@
 {
   "source": {
-    "hash": "0x05cc2edbb7547b2311d2a442aac3c183e055bf1f3faa96568b4a68e3ac5e17f0",
-    "language": "ink! 4.2.0",
-    "compiler": "rustc 1.69.0",
+    "hash": "0xd3f781649eacf23e57a65b0dd8f59142c92e6e2cd4656da874a594431d7399a9",
+    "language": "ink! 5.0.0",
+    "compiler": "rustc 1.73.0",
     "build_info": {
       "build_mode": "Debug",
-      "cargo_contract_version": "3.0.1",
+      "cargo_contract_version": "4.0.2",
       "rust_toolchain": "stable-x86_64-unknown-linux-gnu",
       "wasm_opt_settings": {
         "keep_debug_symbols": false,
-        "optimization_passes": "Zero"
+        "optimization_passes": "Z"
       }
     }
   },
   "contract": {
     "name": "mother",
-    "version": "4.2.0",
+    "version": "5.0.0",
     "authors": [
       "Parity Technologies <admin@parity.io>"
     ],
     "description": "Mother of all contracts"
   },
+  "image": null,
   "spec": {
     "constructors": [
       {
@@ -31,7 +32,7 @@
               "displayName": [
                 "Auction"
               ],
-              "type": 13
+              "type": 24
             }
           }
         ],
@@ -44,7 +45,7 @@
             "ink_primitives",
             "ConstructorResult"
           ],
-          "type": 18
+          "type": 29
         },
         "selector": "0x9bae9d5e"
       },
@@ -59,7 +60,7 @@
             "ink_primitives",
             "ConstructorResult"
           ],
-          "type": 18
+          "type": 29
         },
         "selector": "0x61ef7e3e"
       },
@@ -86,7 +87,7 @@
             "ink_primitives",
             "ConstructorResult"
           ],
-          "type": 21
+          "type": 31
         },
         "selector": "0x87a495f6"
       }
@@ -115,7 +116,7 @@
         "displayName": [
           "ChainExtension"
         ],
-        "type": 27
+        "type": 38
       },
       "hash": {
         "displayName": [
@@ -124,11 +125,12 @@
         "type": 1
       },
       "maxEventTopics": 4,
+      "staticBufferSize": 16384,
       "timestamp": {
         "displayName": [
           "Timestamp"
         ],
-        "type": 26
+        "type": 37
       }
     },
     "events": [
@@ -142,14 +144,16 @@
               "displayName": [
                 "Auction"
               ],
-              "type": 13
+              "type": 24
             }
           }
         ],
         "docs": [
           "Event emitted when an auction being echoed."
         ],
-        "label": "AuctionEchoed"
+        "label": "AuctionEchoed",
+        "module_path": "mother::mother",
+        "signature_topic": "0x9f3c1597e0c1071a300ddb58b0474976b0d066c9a445c8a4677e5cebb5f8980a"
       }
     ],
     "lang_error": {
@@ -157,7 +161,7 @@
         "ink",
         "LangError"
       ],
-      "type": 20
+      "type": 30
     },
     "messages": [
       {
@@ -168,7 +172,7 @@
               "displayName": [
                 "Auction"
               ],
-              "type": 13
+              "type": 24
             }
           }
         ],
@@ -184,7 +188,7 @@
             "ink",
             "MessageResult"
           ],
-          "type": 24
+          "type": 34
         },
         "selector": "0xbc7ac4cf"
       },
@@ -196,7 +200,7 @@
               "displayName": [
                 "Option"
               ],
-              "type": 25
+              "type": 35
             }
           }
         ],
@@ -212,7 +216,7 @@
             "ink",
             "MessageResult"
           ],
-          "type": 21
+          "type": 31
         },
         "selector": "0xe62a1df5"
       },
@@ -240,9 +244,37 @@
             "ink",
             "MessageResult"
           ],
-          "type": 18
+          "type": 29
         },
         "selector": "0x238582df"
+      },
+      {
+        "args": [
+          {
+            "label": "message",
+            "type": {
+              "displayName": [
+                "String"
+              ],
+              "type": 0
+            }
+          }
+        ],
+        "default": false,
+        "docs": [
+          " Mutates the input string to return \"Hello, { name }\""
+        ],
+        "label": "mut_hello_world",
+        "mutates": false,
+        "payable": false,
+        "returnType": {
+          "displayName": [
+            "ink",
+            "MessageResult"
+          ],
+          "type": 36
+        },
+        "selector": "0x23c47128"
       }
     ]
   },
@@ -411,20 +443,37 @@
                 "root": {
                   "layout": {
                     "leaf": {
-                      "key": "0x013a6e2b",
+                      "key": "0x2b6e3a01",
                       "ty": 9
                     }
                   },
-                  "root_key": "0x013a6e2b"
+                  "root_key": "0x2b6e3a01",
+                  "ty": 13
                 }
               },
               "name": "balances"
+            },
+            {
+              "layout": {
+                "root": {
+                  "layout": {
+                    "leaf": {
+                      "key": "0x8482a36e",
+                      "ty": 0
+                    }
+                  },
+                  "root_key": "0x8482a36e",
+                  "ty": 18
+                }
+              },
+              "name": "log"
             }
           ],
           "name": "Mother"
         }
       },
-      "root_key": "0x00000000"
+      "root_key": "0x00000000",
+      "ty": 23
     }
   },
   "types": [
@@ -595,6 +644,258 @@
     },
     {
       "id": 13,
+      "type": {
+        "def": {
+          "composite": {}
+        },
+        "params": [
+          {
+            "name": "K",
+            "type": 8
+          },
+          {
+            "name": "V",
+            "type": 9
+          },
+          {
+            "name": "KeyType",
+            "type": 14
+          }
+        ],
+        "path": [
+          "ink_storage",
+          "lazy",
+          "mapping",
+          "Mapping"
+        ]
+      }
+    },
+    {
+      "id": 14,
+      "type": {
+        "def": {
+          "composite": {}
+        },
+        "params": [
+          {
+            "name": "L",
+            "type": 15
+          },
+          {
+            "name": "R",
+            "type": 16
+          }
+        ],
+        "path": [
+          "ink_storage_traits",
+          "impls",
+          "ResolverKey"
+        ]
+      }
+    },
+    {
+      "id": 15,
+      "type": {
+        "def": {
+          "composite": {}
+        },
+        "path": [
+          "ink_storage_traits",
+          "impls",
+          "AutoKey"
+        ]
+      }
+    },
+    {
+      "id": 16,
+      "type": {
+        "def": {
+          "composite": {}
+        },
+        "params": [
+          {
+            "name": "ParentKey",
+            "type": 17
+          }
+        ],
+        "path": [
+          "ink_storage_traits",
+          "impls",
+          "ManualKey"
+        ]
+      }
+    },
+    {
+      "id": 17,
+      "type": {
+        "def": {
+          "tuple": []
+        }
+      }
+    },
+    {
+      "id": 18,
+      "type": {
+        "def": {
+          "composite": {
+            "fields": [
+              {
+                "name": "len",
+                "type": 21,
+                "typeName": "Lazy<u32, KeyType>"
+              },
+              {
+                "name": "elements",
+                "type": 22,
+                "typeName": "Mapping<u32, V, KeyType>"
+              }
+            ]
+          }
+        },
+        "params": [
+          {
+            "name": "V",
+            "type": 0
+          },
+          {
+            "name": "KeyType",
+            "type": 19
+          }
+        ],
+        "path": [
+          "ink_storage",
+          "lazy",
+          "vec",
+          "StorageVec"
+        ]
+      }
+    },
+    {
+      "id": 19,
+      "type": {
+        "def": {
+          "composite": {}
+        },
+        "params": [
+          {
+            "name": "L",
+            "type": 15
+          },
+          {
+            "name": "R",
+            "type": 20
+          }
+        ],
+        "path": [
+          "ink_storage_traits",
+          "impls",
+          "ResolverKey"
+        ]
+      }
+    },
+    {
+      "id": 20,
+      "type": {
+        "def": {
+          "composite": {}
+        },
+        "params": [
+          {
+            "name": "ParentKey",
+            "type": 17
+          }
+        ],
+        "path": [
+          "ink_storage_traits",
+          "impls",
+          "ManualKey"
+        ]
+      }
+    },
+    {
+      "id": 21,
+      "type": {
+        "def": {
+          "composite": {}
+        },
+        "params": [
+          {
+            "name": "V",
+            "type": 10
+          },
+          {
+            "name": "KeyType",
+            "type": 19
+          }
+        ],
+        "path": [
+          "ink_storage",
+          "lazy",
+          "Lazy"
+        ]
+      }
+    },
+    {
+      "id": 22,
+      "type": {
+        "def": {
+          "composite": {}
+        },
+        "params": [
+          {
+            "name": "K",
+            "type": 10
+          },
+          {
+            "name": "V",
+            "type": 0
+          },
+          {
+            "name": "KeyType",
+            "type": 19
+          }
+        ],
+        "path": [
+          "ink_storage",
+          "lazy",
+          "mapping",
+          "Mapping"
+        ]
+      }
+    },
+    {
+      "id": 23,
+      "type": {
+        "def": {
+          "composite": {
+            "fields": [
+              {
+                "name": "auction",
+                "type": 24,
+                "typeName": "<Auction as::ink::storage::traits::AutoStorableHint<::ink::\nstorage::traits::ManualKey<1146968252u32, ()>,>>::Type"
+              },
+              {
+                "name": "balances",
+                "type": 13,
+                "typeName": "<Mapping<AccountId, Balance> as::ink::storage::traits::\nAutoStorableHint<::ink::storage::traits::ManualKey<20606507u32, ()\n>,>>::Type"
+              },
+              {
+                "name": "log",
+                "type": 18,
+                "typeName": "<StorageVec<String> as::ink::storage::traits::AutoStorableHint<\n::ink::storage::traits::ManualKey<1856209540u32, ()>,>>::Type"
+              }
+            ]
+          }
+        },
+        "path": [
+          "mother",
+          "mother",
+          "Mother"
+        ]
+      }
+    },
+    {
+      "id": 24,
       "type": {
         "def": {
           "composite": {
@@ -611,17 +912,17 @@
               },
               {
                 "name": "bids",
-                "type": 14,
+                "type": 25,
                 "typeName": "Bids"
               },
               {
                 "name": "terms",
-                "type": 15,
+                "type": 26,
                 "typeName": "[BlockNumber; 3]"
               },
               {
                 "name": "status",
-                "type": 16,
+                "type": 27,
                 "typeName": "Status"
               },
               {
@@ -645,7 +946,7 @@
       }
     },
     {
-      "id": 14,
+      "id": 25,
       "type": {
         "def": {
           "composite": {
@@ -665,7 +966,7 @@
       }
     },
     {
-      "id": 15,
+      "id": 26,
       "type": {
         "def": {
           "array": {
@@ -676,7 +977,7 @@
       }
     },
     {
-      "id": 16,
+      "id": 27,
       "type": {
         "def": {
           "variant": {
@@ -702,7 +1003,7 @@
               {
                 "fields": [
                   {
-                    "type": 17,
+                    "type": 28,
                     "typeName": "Outline"
                   }
                 ],
@@ -730,7 +1031,7 @@
       }
     },
     {
-      "id": 17,
+      "id": 28,
       "type": {
         "def": {
           "variant": {
@@ -758,7 +1059,7 @@
       }
     },
     {
-      "id": 18,
+      "id": 29,
       "type": {
         "def": {
           "variant": {
@@ -766,7 +1067,7 @@
               {
                 "fields": [
                   {
-                    "type": 19
+                    "type": 17
                   }
                 ],
                 "index": 0,
@@ -775,7 +1076,7 @@
               {
                 "fields": [
                   {
-                    "type": 20
+                    "type": 30
                   }
                 ],
                 "index": 1,
@@ -787,11 +1088,11 @@
         "params": [
           {
             "name": "T",
-            "type": 19
+            "type": 17
           },
           {
             "name": "E",
-            "type": 20
+            "type": 30
           }
         ],
         "path": [
@@ -800,15 +1101,7 @@
       }
     },
     {
-      "id": 19,
-      "type": {
-        "def": {
-          "tuple": []
-        }
-      }
-    },
-    {
-      "id": 20,
+      "id": 30,
       "type": {
         "def": {
           "variant": {
@@ -827,7 +1120,7 @@
       }
     },
     {
-      "id": 21,
+      "id": 31,
       "type": {
         "def": {
           "variant": {
@@ -835,7 +1128,7 @@
               {
                 "fields": [
                   {
-                    "type": 22
+                    "type": 32
                   }
                 ],
                 "index": 0,
@@ -844,7 +1137,7 @@
               {
                 "fields": [
                   {
-                    "type": 20
+                    "type": 30
                   }
                 ],
                 "index": 1,
@@ -856,11 +1149,11 @@
         "params": [
           {
             "name": "T",
-            "type": 22
+            "type": 32
           },
           {
             "name": "E",
-            "type": 20
+            "type": 30
           }
         ],
         "path": [
@@ -869,7 +1162,7 @@
       }
     },
     {
-      "id": 22,
+      "id": 32,
       "type": {
         "def": {
           "variant": {
@@ -877,7 +1170,7 @@
               {
                 "fields": [
                   {
-                    "type": 19
+                    "type": 17
                   }
                 ],
                 "index": 0,
@@ -886,7 +1179,7 @@
               {
                 "fields": [
                   {
-                    "type": 23
+                    "type": 33
                   }
                 ],
                 "index": 1,
@@ -898,11 +1191,11 @@
         "params": [
           {
             "name": "T",
-            "type": 19
+            "type": 17
           },
           {
             "name": "E",
-            "type": 23
+            "type": 33
           }
         ],
         "path": [
@@ -911,7 +1204,7 @@
       }
     },
     {
-      "id": 23,
+      "id": 33,
       "type": {
         "def": {
           "variant": {
@@ -941,7 +1234,7 @@
       }
     },
     {
-      "id": 24,
+      "id": 34,
       "type": {
         "def": {
           "variant": {
@@ -949,7 +1242,7 @@
               {
                 "fields": [
                   {
-                    "type": 13
+                    "type": 24
                   }
                 ],
                 "index": 0,
@@ -958,7 +1251,7 @@
               {
                 "fields": [
                   {
-                    "type": 20
+                    "type": 30
                   }
                 ],
                 "index": 1,
@@ -970,11 +1263,11 @@
         "params": [
           {
             "name": "T",
-            "type": 13
+            "type": 24
           },
           {
             "name": "E",
-            "type": 20
+            "type": 30
           }
         ],
         "path": [
@@ -983,7 +1276,7 @@
       }
     },
     {
-      "id": 25,
+      "id": 35,
       "type": {
         "def": {
           "variant": {
@@ -995,7 +1288,7 @@
               {
                 "fields": [
                   {
-                    "type": 23
+                    "type": 33
                   }
                 ],
                 "index": 1,
@@ -1007,7 +1300,7 @@
         "params": [
           {
             "name": "T",
-            "type": 23
+            "type": 33
           }
         ],
         "path": [
@@ -1016,7 +1309,49 @@
       }
     },
     {
-      "id": 26,
+      "id": 36,
+      "type": {
+        "def": {
+          "variant": {
+            "variants": [
+              {
+                "fields": [
+                  {
+                    "type": 0
+                  }
+                ],
+                "index": 0,
+                "name": "Ok"
+              },
+              {
+                "fields": [
+                  {
+                    "type": 30
+                  }
+                ],
+                "index": 1,
+                "name": "Err"
+              }
+            ]
+          }
+        },
+        "params": [
+          {
+            "name": "T",
+            "type": 0
+          },
+          {
+            "name": "E",
+            "type": 30
+          }
+        ],
+        "path": [
+          "Result"
+        ]
+      }
+    },
+    {
+      "id": 37,
       "type": {
         "def": {
           "primitive": "u64"
@@ -1024,7 +1359,7 @@
       }
     },
     {
-      "id": 27,
+      "id": 38,
       "type": {
         "def": {
           "variant": {}
@@ -1037,5 +1372,5 @@
       }
     }
   ],
-  "version": "4"
+  "version": 5
 }

+ 3 - 3
tests/codegen_testcases/solidity/common_subexpression_elimination.sol

@@ -305,8 +305,8 @@ contract c1 {
         int p = a + get(a/(2*b), b);
 
         bool e = (ast == bst) || p < 2;
-        // CHECK: ty:bool %2.cse_temp = (strcmp (%ast) (%bst))
-        // CHECK: branchcond %2.cse_temp, block2, block1
+        // CHECK: ty:bool %3.cse_temp = (strcmp (%ast) (%bst))
+        // CHECK: branchcond %3.cse_temp, block2, block1
         bool e2 = e;
         // CHECK: branchcond (strcmp ((builtin Concat (%ast, %bst))) (%cst)), block3, block4
         if (string.concat(ast, bst) == cst) {
@@ -315,7 +315,7 @@ contract c1 {
             emit testEvent(a + get(a/(2*b) -p, b), p, string.concat(ast, bst));
         }
 
-        // CHECK: branchcond %2.cse_temp, block21, block22
+        // CHECK: branchcond %3.cse_temp, block21, block22
         if (ast == bst) {
             ast = string.concat(ast, "b");
         }

+ 33 - 65
tests/polkadot_tests/events.rs

@@ -1,10 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use crate::build_solidity;
-use ink_env::{
-    hash::{Blake2x256, CryptoHash},
-    topics::PrefixedValue,
-};
+use ink_env::hash::{Blake2x256, CryptoHash};
 use ink_primitives::{AccountId, Hash};
 use parity_scale_codec::Encode;
 use solang::{file_resolver::FileResolver, Target};
@@ -20,6 +17,12 @@ fn topic_hash(encoded: &[u8]) -> Hash {
     buf.into()
 }
 
+fn event_topic(signature: &str) -> Hash {
+    let mut buf = [0; 32];
+    <Blake2x256 as CryptoHash>::hash(signature.as_bytes(), &mut buf);
+    buf.into()
+}
+
 #[test]
 fn anonymous() {
     let mut runtime = build_solidity(
@@ -38,16 +41,15 @@ fn anonymous() {
     assert_eq!(runtime.events().len(), 1);
     let event = &runtime.events()[0];
     assert_eq!(event.topics.len(), 0);
-    assert_eq!(event.data, (0u8, true).encode());
+    assert_eq!(event.data, true.encode());
 }
 
 #[test]
 fn emit() {
     #[derive(Encode)]
-    enum Event {
-        Foo(bool, u32, i64),
-        Bar(u32, u64, String),
-    }
+    struct Foo(bool, u32, i64);
+    #[derive(Encode)]
+    struct Bar(u32, u64, String);
 
     let mut runtime = build_solidity(
         r#"
@@ -67,30 +69,20 @@ fn emit() {
     assert_eq!(runtime.events().len(), 2);
     let event = &runtime.events()[0];
     assert_eq!(event.topics.len(), 2);
-    assert_eq!(event.topics[0], topic_hash(b"\0a::foo"));
-    let topic = PrefixedValue {
-        prefix: b"a::foo::i",
-        value: &1i64,
-    }
-    .encode();
-    assert_eq!(event.topics[1], topic_hash(&topic[..]));
-    assert_eq!(event.data, Event::Foo(true, 102, 1).encode());
+    assert_eq!(event.topics[0], event_topic("foo(bool,uint32,int64)"));
+    assert_eq!(event.topics[1], topic_hash(&1i64.encode()[..]));
+    assert_eq!(event.data, Foo(true, 102, 1).encode());
 
     let event = &runtime.events()[1];
     assert_eq!(event.topics.len(), 2);
     println!("topic hash: {:?}", event.topics[0]);
     println!("topic hash: {:?}", event.topics[0]);
-    assert_eq!(event.topics[0], topic_hash(b"\0a::bar"));
-    let topic = PrefixedValue {
-        prefix: b"a::bar::s",
-        value: &String::from("foobar"),
-    }
-    .encode();
-    assert_eq!(event.topics[1], topic_hash(&topic[..]));
+    assert_eq!(event.topics[0], event_topic("bar(uint32,uint64,string)"));
     assert_eq!(
-        event.data,
-        Event::Bar(0xdeadcafe, 102, "foobar".into()).encode()
+        event.topics[1],
+        topic_hash(&"foobar".to_string().encode()[..])
     );
+    assert_eq!(event.data, Bar(0xdeadcafe, 102, "foobar".into()).encode());
 }
 
 #[test]
@@ -216,16 +208,7 @@ fn event_imported() {
 #[test]
 fn erc20_ink_example() {
     #[derive(Encode)]
-    enum Event {
-        Transfer(AccountId, AccountId, u128),
-    }
-
-    #[derive(Encode)]
-    struct Transfer {
-        from: AccountId,
-        to: AccountId,
-        value: u128,
-    }
+    struct Transfer(AccountId, AccountId, u128);
 
     let mut runtime = build_solidity(
         r##"
@@ -245,26 +228,19 @@ fn erc20_ink_example() {
     let from = AccountId::from([1; 32]);
     let to = AccountId::from([2; 32]);
     let value = 10;
-    runtime.function("emit_event", Transfer { from, to, value }.encode());
+    runtime.function("emit_event", Transfer(from, to, value).encode());
 
     assert_eq!(runtime.events().len(), 1);
     let event = &runtime.events()[0];
-    assert_eq!(event.data, Event::Transfer(from, to, value).encode());
+    assert_eq!(event.data, Transfer(from, to, value).encode());
 
     assert_eq!(event.topics.len(), 3);
-    assert_eq!(event.topics[0], topic_hash(b"\0Erc20::Transfer"));
-
-    let expected_topic = PrefixedValue {
-        prefix: b"Erc20::Transfer::from",
-        value: &from,
-    };
-    assert_eq!(event.topics[1], topic_hash(&expected_topic.encode()));
-
-    let expected_topic = PrefixedValue {
-        prefix: b"Erc20::Transfer::to",
-        value: &to,
-    };
-    assert_eq!(event.topics[2], topic_hash(&expected_topic.encode()));
+    assert_eq!(
+        event.topics[0],
+        event_topic("Transfer(address,address,uint128)")
+    );
+    assert_eq!(event.topics[1], topic_hash(&from.encode()));
+    assert_eq!(event.topics[2], topic_hash(&to.encode()));
 }
 
 #[test]
@@ -287,13 +263,9 @@ fn freestanding() {
 
     assert_eq!(runtime.events().len(), 1);
     let event = &runtime.events()[0];
-    assert_eq!(event.data, (0u8, true).encode());
-    assert_eq!(event.topics[0], topic_hash(b"\0a::A"));
-    let expected_topic = PrefixedValue {
-        prefix: b"a::A::b",
-        value: &true,
-    };
-    assert_eq!(event.topics[1], topic_hash(&expected_topic.encode()));
+    assert_eq!(event.data, true.encode());
+    assert_eq!(event.topics[0], event_topic("A(bool)"));
+    assert_eq!(event.topics[1], topic_hash(&true.encode()));
 }
 
 #[test]
@@ -308,11 +280,7 @@ fn different_contract() {
 
     assert_eq!(runtime.events().len(), 1);
     let event = &runtime.events()[0];
-    assert_eq!(event.data, (0u8, true).encode());
-    assert_eq!(event.topics[0], topic_hash(b"\0A::X"));
-    let expected_topic = PrefixedValue {
-        prefix: b"A::X::foo",
-        value: &true,
-    };
-    assert_eq!(event.topics[1], topic_hash(&expected_topic.encode()));
+    assert_eq!(event.data, true.encode());
+    assert_eq!(event.topics[0], event_topic("X(bool)"));
+    assert_eq!(event.topics[1], topic_hash(&true.encode()));
 }