From fdda604d41837f1e04c3cae2da0b335ddc397c43 Mon Sep 17 00:00:00 2001 From: acheron <98934430+acheroncrypto@users.noreply.github.com> Date: Wed, 13 Sep 2023 00:12:41 +0200 Subject: [PATCH] ts: Add strong type support for `addEventListener` (#2627) --- CHANGELOG.md | 1 + tests/events/Anchor.toml | 2 +- tests/events/tests/{events.js => events.ts} | 72 +++++++++------------ tests/events/tsconfig.json | 9 +++ ts/packages/anchor/src/program/index.ts | 11 +++- 5 files changed, 48 insertions(+), 47 deletions(-) rename tests/events/tests/{events.js => events.ts} (61%) create mode 100644 tests/events/tsconfig.json diff --git a/CHANGELOG.md b/CHANGELOG.md index dbd160618..021442eaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The minor version will be incremented upon a breaking change and the patch versi - ts: Add support for unnamed(tuple) enum in accounts ([#2601](https://github.com/coral-xyz/anchor/pull/2601)). - cli: Add program template with multiple files for instructions, state... ([#2602](https://github.com/coral-xyz/anchor/pull/2602)). - lang: `Box` the inner enums of `anchor_lang::error::Error` to optimize `anchor_lang::Result` ([#2600](https://github.com/coral-xyz/anchor/pull/2600)). +- ts: Add strong type support for `Program.addEventListener` method ([#2627](https://github.com/coral-xyz/anchor/pull/2627)). ### Fixes diff --git a/tests/events/Anchor.toml b/tests/events/Anchor.toml index 8a154d145..18b7cf65a 100644 --- a/tests/events/Anchor.toml +++ b/tests/events/Anchor.toml @@ -6,4 +6,4 @@ wallet = "~/.config/solana/id.json" events = "2dhGsWUzy5YKUsjZdLHLmkNpUDAXkNa9MYWsPc4Ziqzy" [scripts] -test = "yarn run mocha -t 1000000 tests/" +test = "yarn run ts-mocha -t 1000000 -p ./tsconfig.json tests/**/*.ts" diff --git a/tests/events/tests/events.js b/tests/events/tests/events.ts similarity index 61% rename from tests/events/tests/events.js rename to tests/events/tests/events.ts index 28a3599f9..d342948d8 100644 --- a/tests/events/tests/events.js +++ b/tests/events/tests/events.ts @@ -1,68 +1,54 @@ -const anchor = require("@coral-xyz/anchor"); -const { assert } = require("chai"); +import * as anchor from "@coral-xyz/anchor"; +import { assert } from "chai"; + +import { Events } from "../target/types/events"; describe("Events", () => { // Configure the client to use the local cluster. anchor.setProvider(anchor.AnchorProvider.env()); - const program = anchor.workspace.Events; + const program = anchor.workspace.Events as anchor.Program; + + type Event = anchor.IdlEvents; + const getEvent = async ( + eventName: E, + methodName: keyof typeof program["methods"] + ) => { + let listenerId: number; + const event = await new Promise((res) => { + listenerId = program.addEventListener(eventName, (event) => { + res(event); + }); + program.methods[methodName]().rpc(); + }); + await program.removeEventListener(listenerId); + + return event; + }; describe("Normal event", () => { it("Single event works", async () => { - let listener = null; + const event = await getEvent("MyEvent", "initialize"); - let [event, slot] = await new Promise((resolve, _reject) => { - listener = program.addEventListener("MyEvent", (event, slot) => { - resolve([event, slot]); - }); - program.rpc.initialize(); - }); - await program.removeEventListener(listener); - - assert.isAbove(slot, 0); assert.strictEqual(event.data.toNumber(), 5); assert.strictEqual(event.label, "hello"); }); it("Multiple events work", async () => { - let listenerOne = null; - let listenerTwo = null; + const eventOne = await getEvent("MyEvent", "initialize"); + const eventTwo = await getEvent("MyOtherEvent", "testEvent"); - let [eventOne, slotOne] = await new Promise((resolve, _reject) => { - listenerOne = program.addEventListener("MyEvent", (event, slot) => { - resolve([event, slot]); - }); - program.rpc.initialize(); - }); - - let [eventTwo, slotTwo] = await new Promise((resolve, _reject) => { - listenerTwo = program.addEventListener( - "MyOtherEvent", - (event, slot) => { - resolve([event, slot]); - } - ); - program.rpc.testEvent(); - }); - - await program.removeEventListener(listenerOne); - await program.removeEventListener(listenerTwo); - - assert.isAbove(slotOne, 0); assert.strictEqual(eventOne.data.toNumber(), 5); assert.strictEqual(eventOne.label, "hello"); - assert.isAbove(slotTwo, 0); assert.strictEqual(eventTwo.data.toNumber(), 6); assert.strictEqual(eventTwo.label, "bye"); }); }); - describe("Self-CPI event", () => { + describe("CPI event", () => { it("Works without accounts being specified", async () => { const tx = await program.methods.testEventCpi().transaction(); - const config = { - commitment: "confirmed", - }; + const config = { commitment: "confirmed" } as const; const txHash = await program.provider.sendAndConfirm(tx, [], config); const txResult = await program.provider.connection.getTransaction( txHash, @@ -77,10 +63,10 @@ describe("Events", () => { assert.strictEqual(event.name, "MyOtherEvent"); assert.strictEqual(event.data.label, "cpi"); - assert.strictEqual(event.data.data.toNumber(), 7); + assert.strictEqual((event.data.data as anchor.BN).toNumber(), 7); }); - it("Malicious invocation throws", async () => { + it("Throws on unauthorized invocation", async () => { const tx = new anchor.web3.Transaction(); tx.add( new anchor.web3.TransactionInstruction({ diff --git a/tests/events/tsconfig.json b/tests/events/tsconfig.json new file mode 100644 index 000000000..774260253 --- /dev/null +++ b/tests/events/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "types": ["mocha", "chai"], + "lib": ["es2015"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true + } +} diff --git a/ts/packages/anchor/src/program/index.ts b/ts/packages/anchor/src/program/index.ts index 58d962f29..d9722a0ae 100644 --- a/ts/packages/anchor/src/program/index.ts +++ b/ts/packages/anchor/src/program/index.ts @@ -11,6 +11,7 @@ import NamespaceFactory, { SimulateNamespace, MethodsNamespace, ViewNamespace, + IdlEvents, } from "./namespace/index.js"; import { utf8 } from "../utils/bytes/index.js"; import { EventManager } from "./event.js"; @@ -357,9 +358,13 @@ export class Program { * @param callback The function to invoke whenever the event is emitted from * program logs. */ - public addEventListener( - eventName: string, - callback: (event: any, slot: number, signature: string) => void + public addEventListener>( + eventName: E, + callback: ( + event: IdlEvents[E], + slot: number, + signature: string + ) => void ): number { return this._events.addEventListener(eventName, callback); }