From 28c86b3f38b3e747fcab4d652e943abe7869f73b Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 3 Apr 2024 18:07:50 +0200 Subject: [PATCH] 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 --- .github/workflows/test.yml | 2 +- Cargo.toml | 6 +- docs/language/events.rst | 14 +- integration/polkadot/UniswapV2Factory.spec.ts | 2 +- integration/polkadot/events.spec.ts | 69 +-- integration/polkadot/msg_sender.spec.ts | 2 +- .../polkadot/upgradeable_proxy.spec.ts | 3 +- integration/subxt-tests/Cargo.toml | 4 +- integration/subxt-tests/src/cases/asserts.rs | 2 +- integration/subxt-tests/src/cases/events.rs | 6 - .../subxt-tests/src/cases/msg_sender.rs | 4 - src/abi/polkadot.rs | 35 +- src/codegen/events/polkadot.rs | 110 +--- src/codegen/polkadot.rs | 3 + src/emit/polkadot/mod.rs | 4 +- src/emit/polkadot/target.rs | 3 +- testdata/ink/mother.json | 469 +++++++++++++++--- .../common_subexpression_elimination.sol | 6 +- tests/polkadot_tests/events.rs | 98 ++-- 19 files changed, 535 insertions(+), 307 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6aa6e424a..d803c5bed 100644 --- a/.github/workflows/test.yml +++ b/.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: diff --git a/Cargo.toml b/Cargo.toml index ac6cd4fb8..1a0587091 100644 --- a/Cargo.toml +++ b/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"]} diff --git a/docs/language/events.rst b/docs/language/events.rst index 55edb5fc7..38115eef7 100644 --- a/docs/language/events.rst +++ b/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 `_: +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 `_. 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. + diff --git a/integration/polkadot/UniswapV2Factory.spec.ts b/integration/polkadot/UniswapV2Factory.spec.ts index 3de5ce411..8afcea625 100644 --- a/integration/polkadot/UniswapV2Factory.spec.ts +++ b/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(); diff --git a/integration/polkadot/events.spec.ts b/integration/polkadot/events.spec.ts index 0bbac7165..fde537c11 100644 --- a/integration/polkadot/events.spec.ts +++ b/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); }); }); diff --git a/integration/polkadot/msg_sender.spec.ts b/integration/polkadot/msg_sender.spec.ts index 36d2351f8..7b47b01d9 100644 --- a/integration/polkadot/msg_sender.spec.ts +++ b/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]); }); }); \ No newline at end of file diff --git a/integration/polkadot/upgradeable_proxy.spec.ts b/integration/polkadot/upgradeable_proxy.spec.ts index fcff47a2a..be7c9de3f 100644 --- a/integration/polkadot/upgradeable_proxy.spec.ts +++ b/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()); } diff --git a/integration/subxt-tests/Cargo.toml b/integration/subxt-tests/Cargo.toml index 5e312fd2b..62e955a00 100644 --- a/integration/subxt-tests/Cargo.toml +++ b/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 = [] diff --git a/integration/subxt-tests/src/cases/asserts.rs b/integration/subxt-tests/src/cases/asserts.rs index 71b24e758..9dcdcc249 100644 --- a/integration/subxt-tests/src/cases/asserts.rs +++ b/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 diff --git a/integration/subxt-tests/src/cases/events.rs b/integration/subxt-tests/src/cases/events.rs index 4af8d9281..5f694201d 100644 --- a/integration/subxt-tests/src/cases/events.rs +++ b/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 { diff --git a/integration/subxt-tests/src/cases/msg_sender.rs b/integration/subxt-tests/src/cases/msg_sender.rs index 835b635a5..159617f84 100644 --- a/integration/subxt-tests/src/cases/msg_sender.rs +++ b/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 = ::decode(&mut evt_buffer)?; assert_eq!(addr, sp_keyring::AccountKeyring::Alice.to_account_id()); diff --git a/src/abi/polkadot.rs b/src/abi/polkadot.rs index b1b575b6e..5c1e5aaf6 100644 --- a/src/abi/polkadot.rs +++ b/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, ®istry), + 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::>>(); - let mut event_spec = |e: &EventDecl| -> EventSpec { + // 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) { let args = e .fields .iter() @@ -460,19 +464,29 @@ pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject { .done() }) .collect::>(); - EventSpec::new(e.id.name.clone()) + let topic = (!e.anonymous).then(|| { + let mut buf = [0; 32]; + ::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::>>() + .drain() + .map(|(_, spec)| spec) .collect::>>(); let environment: EnvironmentSpec = 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![ diff --git a/src/codegen/events/polkadot.rs b/src/codegen/events/polkadot.rs index 9ae1ba6c3..31effff76 100644 --- a/src/codegen/events/polkadot.rs +++ b/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 { - 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 { + 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 { - ::hash(encoded.as_bytes(), &mut buf); - }; + ::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> = 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, }, ); diff --git a/src/codegen/polkadot.rs b/src/codegen/polkadot.rs index 12ad13873..511eec0c9 100644 --- a/src/codegen/polkadot.rs +++ b/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, diff --git a/src/emit/polkadot/mod.rs b/src/emit/polkadot/mod.rs index 7be53f978..4078efadf 100644 --- a/src/emit/polkadot/mod.rs +++ b/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 { diff --git a/src/emit/polkadot/target.rs b/src/emit/polkadot/target.rs index cdd22bfde..70faeaa02 100644 --- a/src/emit/polkadot/target.rs +++ b/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; diff --git a/testdata/ink/mother.json b/testdata/ink/mother.json index ce4f35bc7..006529a8c 100644 --- a/testdata/ink/mother.json +++ b/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 " ], "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" + }, + { + "name": "elements", + "type": 22, + "typeName": "Mapping" + } + ] + } + }, + "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": ",>>::Type" + }, + { + "name": "balances", + "type": 13, + "typeName": " as::ink::storage::traits::\nAutoStorableHint<::ink::storage::traits::ManualKey<20606507u32, ()\n>,>>::Type" + }, + { + "name": "log", + "type": 18, + "typeName": " 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 } \ No newline at end of file diff --git a/tests/codegen_testcases/solidity/common_subexpression_elimination.sol b/tests/codegen_testcases/solidity/common_subexpression_elimination.sol index 8e952d25b..c55dd479a 100644 --- a/tests/codegen_testcases/solidity/common_subexpression_elimination.sol +++ b/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"); } diff --git a/tests/polkadot_tests/events.rs b/tests/polkadot_tests/events.rs index 3f2e8c910..c746eaff3 100644 --- a/tests/polkadot_tests/events.rs +++ b/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]; + ::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())); }