Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ink! v5 #5791

Merged
merged 28 commits into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
db3822d
adds definitions and types according to ink v5 changes
peetzweg Feb 1, 2024
9f93a19
adds toV5 boilerplate code draft
peetzweg Feb 1, 2024
0cf749e
adds v5 flipper test contract code
peetzweg Feb 1, 2024
cfba268
fix license dates
peetzweg Feb 1, 2024
7d404f4
adds test v5 toLatest test
peetzweg Feb 1, 2024
1f9e6f5
implements new scheme to determine event
peetzweg Feb 1, 2024
985365f
apply linter changes
peetzweg Feb 1, 2024
5db72af
adds test result outputs
peetzweg Feb 1, 2024
cc946da
change `EventRecord['topics'][0]` type to plain `Hash`
peetzweg Feb 16, 2024
54fd609
adds testcases for decoding payload data of a ink!v4 and ink!v5 event
peetzweg Feb 23, 2024
013ea59
changes `Abi.decodeEvent(data:Bytes)` method interface to `Abi.decode…
peetzweg Feb 26, 2024
a028c55
draft implementation with version metadata
peetzweg Feb 26, 2024
3e0d8d1
cleaner implementation of versioned Metadata by actually leveraging t…
peetzweg Feb 27, 2024
ada84b3
Merge branch 'polkadot-js:master' into pz/ink-v5
peetzweg Feb 27, 2024
0029e18
trying to make linter happy
peetzweg Feb 27, 2024
f000f24
Merge branch 'pz/ink-v5' of github.com:peetzweg/pjs-api into pz/ink-v5
peetzweg Feb 27, 2024
0124e14
makes `ContractMetadataSupported` in internal to `Abi` type and not e…
peetzweg Feb 27, 2024
fcb684d
properly types unused parameter for tsc :shrug:
peetzweg Feb 27, 2024
64feb63
adds `@polkadot/types-support` dev dependency
peetzweg Feb 27, 2024
df9956c
merge master
peetzweg Feb 27, 2024
f1a1b9d
Update yarn.lock
peetzweg Feb 27, 2024
1cda4b9
references `types-support` in `api-contract
peetzweg Feb 27, 2024
246570d
resolving change requests
peetzweg Feb 28, 2024
f88fcf7
resolves linter warnings
peetzweg Feb 28, 2024
f67a88a
changes ContractMetadataV5 field to `u64` from `Text`
peetzweg Feb 28, 2024
27d6b46
adds contracts and contract metadata compiled with the most recent in…
peetzweg Feb 28, 2024
0e039e7
implements decoding of anonymous events if possible
peetzweg Mar 1, 2024
3b48940
removes done todo comments
peetzweg Mar 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions packages/api-contract/src/Abi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import type { Bytes } from '@polkadot/types';
import type { ChainProperties, ContractConstructorSpecLatest, ContractEventSpecLatest, ContractMessageParamSpecLatest, ContractMessageSpecLatest, ContractMetadata, ContractMetadataLatest, ContractProjectInfo, ContractTypeSpec } from '@polkadot/types/interfaces';
import type { ChainProperties, ContractConstructorSpecLatest, ContractEventSpecLatest, ContractMessageParamSpecLatest, ContractMessageSpecLatest, ContractMetadata, ContractMetadataLatest, ContractProjectInfo, ContractTypeSpec, EventRecord } from '@polkadot/types/interfaces';
import type { Codec, Registry, TypeDef } from '@polkadot/types/types';
import type { AbiConstructor, AbiEvent, AbiMessage, AbiParam, DecodedEvent, DecodedMessage } from '../types.js';

Expand Down Expand Up @@ -162,9 +162,18 @@ export class Abi {
/**
* Warning: Unstable API, bound to change
*/
public decodeEvent (data: Bytes | Uint8Array): DecodedEvent {
public decodeEvent (data: Bytes | Uint8Array, topic: EventRecord['topics'][0]): DecodedEvent {
peetzweg marked this conversation as resolved.
Show resolved Hide resolved
// try to find a topic signature match - ink! v5 upwards
let event = this.events.find((e) => e.signatureTopic === topic.toHex());
peetzweg marked this conversation as resolved.
Show resolved Hide resolved

if (event) {
return event.fromU8a(data.subarray(0));
}

// otherwise fallback to using the index to determine event - ink! v4 downwards
const index = data[0];
const event = this.events[index];

event = this.events[index];

if (!event) {
throw new Error(`Unable to find event with index ${index}`);
Expand Down Expand Up @@ -235,15 +244,17 @@ export class Abi {

#createEvent = (spec: ContractEventSpecLatest, index: number): AbiEvent => {
const args = this.#createArgs(spec.args, spec);

const event = {
args,
docs: spec.docs.map((d) => d.toString()),
fromU8a: (data: Uint8Array): DecodedEvent => ({
args: this.#decodeArgs(args, data),
event
}),
identifier: spec.label.toString(),
index
identifier: [spec.module_path, spec.label.toString()].join('::'),
index,
signatureTopic: spec.signature_topic.toHex() || undefined
peetzweg marked this conversation as resolved.
Show resolved Hide resolved
};

return event;
Expand Down
18 changes: 17 additions & 1 deletion packages/api-contract/src/Abi/toLatest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { TypeRegistry } from '@polkadot/types';

import abis from '../test/contracts/index.js';
import { v0ToLatest, v1ToLatest, v2ToLatest, v3ToLatest, v4ToLatest } from './toLatest.js';
import { v0ToLatest, v1ToLatest, v2ToLatest, v3ToLatest, v4ToLatest, v5ToLatest } from './toLatest.js';

describe('v0ToLatest', (): void => {
const registry = new TypeRegistry();
Expand Down Expand Up @@ -137,3 +137,19 @@ describe('v4ToLatest', (): void => {
).toEqual(false);
});
});

describe('v5ToLatest', (): void => {
const registry = new TypeRegistry();
const contract = registry.createType('ContractMetadata', { V5: abis['ink_v5_erc20'] });
const latest = v5ToLatest(registry, contract.asV5);

it('has new event fields', (): void => {
expect(
latest.spec.events.every((e) => e.has('module_path'))
).toEqual(true);

expect(
latest.spec.events.every((e) => e.has('signature_topic'))
).toEqual(true);
});
});
11 changes: 7 additions & 4 deletions packages/api-contract/src/Abi/toLatest.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
// Copyright 2017-2024 @polkadot/api-contract authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { ContractMetadataLatest, ContractMetadataV4 } from '@polkadot/types/interfaces';
import type { ContractMetadataLatest, ContractMetadataV5 } from '@polkadot/types/interfaces';
import type { Registry } from '@polkadot/types/types';

import { v0ToV1 } from './toV1.js';
import { v1ToV2 } from './toV2.js';
import { v2ToV3 } from './toV3.js';
import { v3ToV4 } from './toV4.js';
import { v4ToV5 } from './toV5.js';

// The versions where an enum is used, aka V0 is missing
// (Order from newest, i.e. we expect more on newest vs oldest)
export const enumVersions = ['V4', 'V3', 'V2', 'V1'] as const;
export const enumVersions = ['V5', 'V4', 'V3', 'V2', 'V1'] as const;

type Versions = typeof enumVersions[number] | 'V0';

Expand All @@ -23,16 +24,18 @@ function createConverter <I, O> (next: (registry: Registry, input: O) => Contrac
next(registry, step(registry, input));
}

export function v4ToLatest (_registry: Registry, v4: ContractMetadataV4): ContractMetadataLatest {
return v4;
export function v5ToLatest (_registry: Registry, v5: ContractMetadataV5): ContractMetadataLatest {
return v5;
}

export const v4ToLatest = /*#__PURE__*/ createConverter(v5ToLatest, v4ToV5);
export const v3ToLatest = /*#__PURE__*/ createConverter(v4ToLatest, v3ToV4);
export const v2ToLatest = /*#__PURE__*/ createConverter(v3ToLatest, v2ToV3);
export const v1ToLatest = /*#__PURE__*/ createConverter(v2ToLatest, v1ToV2);
export const v0ToLatest = /*#__PURE__*/ createConverter(v1ToLatest, v0ToV1);

export const convertVersions: [Versions, Converter][] = [
['V5', v5ToLatest],
['V4', v4ToLatest],
['V3', v3ToLatest],
['V2', v2ToLatest],
Expand Down
20 changes: 20 additions & 0 deletions packages/api-contract/src/Abi/toV5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2017-2024 @polkadot/api-contract authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { ContractMetadataV4, ContractMetadataV5 } from '@polkadot/types/interfaces';
import type { Registry } from '@polkadot/types/types';

import { objectSpread } from '@polkadot/util';

export function v4ToV5 (registry: Registry, v4: ContractMetadataV4): ContractMetadataV5 {
return registry.createType('ContractMetadataV5', objectSpread({}, v4, {
spec: objectSpread({}, v4.spec, {
events: v4.spec.events.map((e) =>
registry.createType('ContractEventSpecV3', objectSpread({
module_path: undefined,
signature_topic: undefined
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or do we consider this a breaking change and don't allow converting from v4 to v5?

Copy link

@cmichi cmichi Feb 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the implications of this convert process, but here's how we describe the change:

Previously the order of the events in the events array was significant (i.e. the first one had an implied index of 0), and this index could be used to determine which event to decode. Now that is replaced by the signature_topic, and the order of the events in the metadata no longer has any significance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual decoding takes place here, using the first topic of the substrate event treating as the signature_topic. Still need to add a proper test for it.

https://github.com/peetzweg/pjs-api/blob/pz/ink-v5/packages/api-contract/src/Abi/index.ts#L165-L183

As all previous version where migrate-able the underlying code always works converts it to the latest version of the metadata. However, this time there is newly added data we can't create for older version of the metadata. This would imply that pjs needs switch decoding events based on the version of the metadata and not handle everything the same way. I think this would be nice to get input from @jacogr how to change the codebase to handle this. How I have done it is not the prettiest. Potentially could come up with a draft though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, this time there is newly added data we can't create for older version of the metadata.

I am getting myself familiar with the correlation between Ink v5 and the conversion process above, and found this on the following signatureTopic:

/// # The Signature Topic
///
/// By default, the [`ink::Event::SIGNATURE_TOPIC`] is calculated as follows:
///
/// `blake2b("EventStructName(field1_type_name,field2_type_name)")`
/// The hashing of the topic is done at codegen time in the derive macro, and as such only has
/// access to the **names** of the field types as they appear in the code. As such, if the
/// name of a field of a struct changes, the signature topic will change too, even if the
/// concrete type itself has not changed. This can happen with type aliases, generics, or a
/// change in the use of a `path::to::Type` qualification.
  1. Would it be invalid to construct the signatureTopic for a v4 event when converting to v5?

  2. In this case do we not have the valid info/data to construct it if the conversion can be considered valid?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if relevant here, but the topic calculation changed in general from v4 to v5, so it's a breaking change for any clients that used topics to filter for events.

We have a migration guide for the events changes here.

}, e))
)
})
}));
}
4 changes: 2 additions & 2 deletions packages/api-contract/src/base/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ export class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
// ContractEmitted is the current generation, ContractExecution is the previous generation
new ContractSubmittableResult(result, applyOnEvent(result, ['ContractEmitted', 'ContractExecution'], (records: EventRecord[]) =>
records
.map(({ event: { data: [, data] } }): DecodedEvent | null => {
.map(({ event: { data: [, data] }, topics }): DecodedEvent | null => {
try {
return this.abi.decodeEvent(data as Bytes);
return this.abi.decodeEvent(data as Bytes, topics[0]);
peetzweg marked this conversation as resolved.
Show resolved Hide resolved
} catch (error) {
l.error(`Unable to decode contract event: ${(error as Error).message}`);

Expand Down
Loading
Loading