Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

Commit

Permalink
feat: add contract event decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
kratico committed Dec 15, 2022
1 parent 47145bb commit e3c5785
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 32 deletions.
87 changes: 64 additions & 23 deletions examples/smart_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,22 @@ const contract = await getContract(
getFilePath("smart_contract/metadata.json"),
)

const contractAddress = U.throwIfError(await instantiateContractTx().run())
console.log("Deployed Contract address", U.ss58.encode(42, contractAddress))
const ss58Prefix = C.const(client)("System", "SS58Prefix").access("value")

function instantiateContractTx() {
const constructor = findContractConstructorByLabel("default")!
const salt = Uint8Array.from(Array.from([0, 0, 0, 0]), () => Math.floor(Math.random() * 16))
const value = preSubmitContractInstantiateDryRunGasEstimate(constructor, contract.wasm, salt)
.next(({ gasRequired, result: { accountId } }) => {
const value = C.Z.ls(
preSubmitContractInstantiateDryRunGasEstimate(constructor, contract.wasm, salt),
ss58Prefix,
)
.next(([{ gasRequired, result: { accountId } }, prefix]) => {
// the contract address derived from the code hash and the salt
console.log("Derived contract address", U.ss58.encode(42, accountId))
console.log("Derived contract address", U.ss58.encode(prefix, accountId))
return {
type: "instantiateWithCode",
value: 0n,
gasLimit: {
refTime: gasRequired.refTime,
proofSize: gasRequired.proofSize,
},
gasLimit: gasRequired,
storageDepositLimit: undefined,
code: contract.wasm,
data: U.hex.decode(constructor.selector),
Expand All @@ -60,11 +59,11 @@ function instantiateContractTx() {
}
)
return C.events(tx, finalizedIn).next((events) => {
const extrinsicFailed = events.some((e) =>
const extrinsicFailedEvent = events.find((e) =>
e.event?.type === "System" && e.event?.value?.type === "ExtrinsicFailed"
)
if (extrinsicFailed) {
return new Error("extrinsic failed")
if (extrinsicFailedEvent) {
return new ExtrinsicFailed(extrinsicFailedEvent)
}
const event = events.find((e) =>
e.event?.type === "Contracts" && e.event?.value?.type === "Instantiated"
Expand All @@ -73,6 +72,18 @@ function instantiateContractTx() {
})
}

class ExtrinsicFailed extends Error {
override readonly name = "ExtrinsicFailedError"
constructor(
override readonly cause: {
event?: Record<string, any>
phase: { value: number }
},
) {
super()
}
}

function preSubmitContractInstantiateDryRunGasEstimate(
message: C.M.ContractMetadata.Constructor,
code: Uint8Array,
Expand Down Expand Up @@ -162,7 +173,7 @@ class SmartContract {
sign: C.Z.$<C.M.Signer>,
) {
const message = this.#getMessageByLabel(messageLabel)!
const [$args, _] = this.#getMessageCodecs(message)
const [$args, _, $events] = this.#getMessageCodecs(message)
const data = $args.encode([U.hex.decode(message.selector), ...args])
const value = this.#preSubmitContractCallDryRunGasEstimate(origin, data)
.next(({ gasRequired }) => {
Expand All @@ -171,10 +182,7 @@ class SmartContract {
dest: C.MultiAddress.Id(this.contractAddress),
value: 0n,
data,
gasLimit: {
refTime: gasRequired.refTime,
proofSize: gasRequired.proofSize,
},
gasLimit: gasRequired,
storageDepositLimit: undefined,
}
})
Expand All @@ -197,6 +205,14 @@ class SmartContract {
}
)
return C.Z.ls(finalizedIn, C.events(tx, finalizedIn))
.next(([finalizedIn, events]) => {
const contractEvents: any[] = events
.filter(
(e) => e.event?.type === "Contracts" && e.event?.value?.type === "ContractEmitted",
)
.map((e) => $events.decode(e.event?.value.data))
return [finalizedIn, events, contractEvents]
})
}

// TODO: codegen each contract message as a method
Expand All @@ -207,18 +223,33 @@ class SmartContract {

#getMessageCodecs(
message: C.M.ContractMetadata.Message,
): [C.$.Codec<unknown[]>, C.$.Codec<any>] {
): [C.$.Codec<unknown[]>, C.$.Codec<any>, C.$.Codec<any>] {
// TODO: cache on initialization
const argCodecs = [
// message selector
C.$.sizedUint8Array(U.hex.decode(message.selector).length),
// message args
...message.args.map((arg) => this.deriveCodec(arg.type.type)),
]
// TODO: cache on initialization
const $result = message.returnType !== null
? this.deriveCodec(message.returnType.type)
: C.$.constant(null)
// FIXME: this is not message specific
const $events = C.$.taggedUnion(
"type",
this.metadata.V3.spec.events
.map((e) => [
e.label,
[
"value",
C.$.tuple(...e.args
.map((a) => this.deriveCodec(a.type.type))),
],
]),
)
// @ts-ignore ...
return [C.$.tuple(...argCodecs), $result]
return [C.$.tuple(...argCodecs), $result, $events]
}

#preSubmitContractCallDryRunGasEstimate(
Expand All @@ -243,6 +274,10 @@ class SmartContract {
}
}

const prefix = U.throwIfError(await ss58Prefix.run())
const contractAddress = U.throwIfError(await instantiateContractTx().run())
console.log("Deployed Contract address", U.ss58.encode(prefix, contractAddress))

const flipperContract = new SmartContract(contract.metadata, contractAddress)
console.log(".get", await flipperContract.call(T.alice.publicKey, "get", []).run())
console.log(
Expand All @@ -266,12 +301,18 @@ console.log(
)
console.log(".get_count", await flipperContract.call(T.alice.publicKey, "get_count", []).run())
console.log(
".m1(2,true) (multiple args/results)",
await flipperContract.call(T.alice.publicKey, "m1", [2, true]).run(),
".inc_by_with_event(3) contract events",
U.throwIfError(
await flipperContract.tx(T.alice.publicKey, "inc_by_with_event", [3], T.alice.sign).run(),
)[2],
)
console.log(
".method_returning_tuple(2,true)",
await flipperContract.call(T.alice.publicKey, "method_returning_tuple", [2, true]).run(),
)
console.log(
".m2(3,false) (multiple args/results)",
await flipperContract.call(T.alice.publicKey, "m1", [3, false]).run(),
".method_returning_struct(3,false)",
await flipperContract.call(T.alice.publicKey, "method_returning_struct", [3, false]).run(),
)

await zombienet.close()
2 changes: 1 addition & 1 deletion examples/smart_contract/flipper.contract

Large diffs are not rendered by default.

Binary file modified examples/smart_contract/flipper.wasm
Binary file not shown.
141 changes: 133 additions & 8 deletions examples/smart_contract/metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"source": {
"hash": "0xa64fbd08bc40eb972efb18c71ac812bd8219e9fd16b975f9ec070ce96ab32156",
"hash": "0x1e3986af05daee058d114887d78d251e7bad3700b7e5aa01741572162d7b3d85",
"language": "ink! 3.4.0",
"compiler": "rustc 1.68.0-nightly"
},
Expand Down Expand Up @@ -46,7 +46,39 @@
}
],
"docs": [],
"events": [],
"events": [
{
"args": [
{
"docs": [],
"indexed": false,
"label": "from",
"type": {
"displayName": [
"Option"
],
"type": 5
}
},
{
"docs": [],
"indexed": false,
"label": "count",
"type": {
"displayName": [
"i32"
],
"type": 1
}
}
],
"docs": [
" Defines an event that is emitted",
" every time inc is invoked."
],
"label": "Incremented"
}
],
"messages": [
{
"args": [],
Expand Down Expand Up @@ -99,16 +131,16 @@
}
],
"docs": [
" multiple arg method1."
" multiple arg method returning a tuple."
],
"label": "m1",
"label": "method_returning_tuple",
"mutates": false,
"payable": false,
"returnType": {
"displayName": [],
"type": 3
},
"selector": "0xfa2cbe71"
"selector": "0xdb48790e"
},
{
"args": [
Expand All @@ -132,9 +164,9 @@
}
],
"docs": [
" multiple arg method2."
" multiple arg method returning a struct."
],
"label": "m2",
"label": "method_returning_struct",
"mutates": false,
"payable": false,
"returnType": {
Expand All @@ -143,7 +175,7 @@
],
"type": 4
},
"selector": "0x25f9a609"
"selector": "0x5cb3e2d0"
},
{
"args": [],
Expand Down Expand Up @@ -203,6 +235,27 @@
"payable": false,
"returnType": null,
"selector": "0xfe5bd8ea"
},
{
"args": [
{
"label": "n",
"type": {
"displayName": [
"i32"
],
"type": 1
}
}
],
"docs": [
" increment current count by n and emit an event"
],
"label": "inc_by_with_event",
"mutates": true,
"payable": false,
"returnType": null,
"selector": "0xd54ee71c"
}
]
},
Expand Down Expand Up @@ -291,6 +344,78 @@
"M2"
]
}
},
{
"id": 5,
"type": {
"def": {
"variant": {
"variants": [
{
"index": 0,
"name": "None"
},
{
"fields": [
{
"type": 6
}
],
"index": 1,
"name": "Some"
}
]
}
},
"params": [
{
"name": "T",
"type": 6
}
],
"path": [
"Option"
]
}
},
{
"id": 6,
"type": {
"def": {
"composite": {
"fields": [
{
"type": 7,
"typeName": "[u8; 32]"
}
]
}
},
"path": [
"ink_env",
"types",
"AccountId"
]
}
},
{
"id": 7,
"type": {
"def": {
"array": {
"len": 32,
"type": 8
}
}
}
},
{
"id": 8,
"type": {
"def": {
"primitive": "u8"
}
}
}
]
}
Expand Down

0 comments on commit e3c5785

Please sign in to comment.