diff --git a/.changeset/good-starfishes-appear.md b/.changeset/good-starfishes-appear.md new file mode 100644 index 000000000..73cefb7a6 --- /dev/null +++ b/.changeset/good-starfishes-appear.md @@ -0,0 +1,5 @@ +--- +'@typechain/ethers-v5': minor +--- + +Export typed events diff --git a/packages/hardhat/test/fixture-projects/hardhat-project/typechain/ERC20.d.ts b/packages/hardhat/test/fixture-projects/hardhat-project/typechain/ERC20.d.ts index 1a9faee37..6b79d7b5e 100644 --- a/packages/hardhat/test/fixture-projects/hardhat-project/typechain/ERC20.d.ts +++ b/packages/hardhat/test/fixture-projects/hardhat-project/typechain/ERC20.d.ts @@ -100,6 +100,18 @@ interface ERC20Interface extends ethers.utils.Interface { getEvent(nameOrSignatureOrTopic: "Transfer"): EventFragment; } +export type ApprovalEvent = TypedEvent< + [string, string, BigNumber] & { + owner: string; + spender: string; + value: BigNumber; + } +>; + +export type TransferEvent = TypedEvent< + [string, string, BigNumber] & { from: string; to: string; value: BigNumber } +>; + export class ERC20 extends BaseContract { connect(signerOrProvider: Signer | Provider | string): this; attach(addressOrName: string): this; @@ -289,6 +301,15 @@ export class ERC20 extends BaseContract { }; filters: { + "Approval(address,address,uint256)"( + owner?: string | null, + spender?: string | null, + value?: null + ): TypedEventFilter< + [string, string, BigNumber], + { owner: string; spender: string; value: BigNumber } + >; + Approval( owner?: string | null, spender?: string | null, @@ -298,6 +319,15 @@ export class ERC20 extends BaseContract { { owner: string; spender: string; value: BigNumber } >; + "Transfer(address,address,uint256)"( + from?: string | null, + to?: string | null, + value?: null + ): TypedEventFilter< + [string, string, BigNumber], + { from: string; to: string; value: BigNumber } + >; + Transfer( from?: string | null, to?: string | null, diff --git a/packages/target-ethers-v5-test/test/Events.test.ts b/packages/target-ethers-v5-test/test/Events.test.ts index eaa49bc2a..096b4ad83 100644 --- a/packages/target-ethers-v5-test/test/Events.test.ts +++ b/packages/target-ethers-v5-test/test/Events.test.ts @@ -1,7 +1,7 @@ import { BigNumber, ethers } from 'ethers' import { typedAssert } from 'test-utils' -import { Events } from '../types/Events' +import { Event1Event, Events } from '../types/Events' import { createNewBlockchain, deployContract } from './common' describe('Events', () => { @@ -84,4 +84,40 @@ describe('Events', () => { await contract.emit_event1() await new Promise((r) => setTimeout(r, 1000)) }) + + it('typed event import', async () => { + const filter = contract.filters.Event1(null, null) + const results = (await contract.queryFilter(filter)) as any + + const results2 = results as Event1Event[] + results2.map((r) => { + typedAssert(r.args.value1, BigNumber.from(1)) + typedAssert(r.args.value2, BigNumber.from(2)) + typedAssert(r.args[0], BigNumber.from(1)) + typedAssert(r.args[1], BigNumber.from(2)) + }) + }) + + it('queryFilter overloaded event', async () => { + await contract.emit_event3_overloaded() + + { + const filterA = contract.filters['Event3(bool,uint256)']() + const results = await contract.queryFilter(filterA) + results.map((r) => { + typedAssert(r.args.value1, true) + typedAssert(r.args.value2, BigNumber.from(2)) + typedAssert(r.args[0], true) + typedAssert(r.args[1], BigNumber.from(2)) + }) + } + { + const filterB = contract.filters['Event3(uint256)']() + const results = await contract.queryFilter(filterB) + results.map((r) => { + typedAssert(r.args.value1, BigNumber.from(1)) + typedAssert(r.args[0], BigNumber.from(1)) + }) + } + }) }) diff --git a/packages/target-ethers-v5-test/types/Events.d.ts b/packages/target-ethers-v5-test/types/Events.d.ts index ace21ccfc..9ead05340 100644 --- a/packages/target-ethers-v5-test/types/Events.d.ts +++ b/packages/target-ethers-v5-test/types/Events.d.ts @@ -93,6 +93,30 @@ interface EventsInterface extends ethers.utils.Interface { getEvent(nameOrSignatureOrTopic: "NoArgsEvent"): EventFragment; } +export type AnonEvent1Event = TypedEvent<[BigNumber] & { value1: BigNumber }>; + +export type Event1Event = TypedEvent< + [BigNumber, BigNumber] & { value1: BigNumber; value2: BigNumber } +>; + +export type Event2Event = TypedEvent<[BigNumber] & { arg0: BigNumber }>; + +export type Event3_bool_uint256_Event = TypedEvent< + [boolean, BigNumber] & { value1: boolean; value2: BigNumber } +>; + +export type Event3_uint256_Event = TypedEvent< + [BigNumber] & { value1: BigNumber } +>; + +export type Event4Event = TypedEvent< + [[BigNumber, string] & { index: BigNumber; name: string }] & { + data: [BigNumber, string] & { index: BigNumber; name: string }; + } +>; + +export type NoArgsEventEvent = TypedEvent<[] & {}>; + export class Events extends BaseContract { connect(signerOrProvider: Signer | Provider | string): this; attach(addressOrName: string): this; @@ -201,10 +225,22 @@ export class Events extends BaseContract { }; filters: { + "AnonEvent1(uint256)"( + value1?: BigNumberish | null + ): TypedEventFilter<[BigNumber], { value1: BigNumber }>; + AnonEvent1( value1?: BigNumberish | null ): TypedEventFilter<[BigNumber], { value1: BigNumber }>; + "Event1(uint256,uint256)"( + value1?: BigNumberish | null, + value2?: null + ): TypedEventFilter< + [BigNumber, BigNumber], + { value1: BigNumber; value2: BigNumber } + >; + Event1( value1?: BigNumberish | null, value2?: null @@ -213,11 +249,15 @@ export class Events extends BaseContract { { value1: BigNumber; value2: BigNumber } >; + "Event2(uint256)"( + undefined?: null + ): TypedEventFilter<[BigNumber], { arg0: BigNumber }>; + Event2( undefined?: null ): TypedEventFilter<[BigNumber], { arg0: BigNumber }>; - Event3( + "Event3(bool,uint256)"( value1?: boolean | null, value2?: null ): TypedEventFilter< @@ -225,6 +265,17 @@ export class Events extends BaseContract { { value1: boolean; value2: BigNumber } >; + "Event3(uint256)"( + value1?: BigNumberish | null + ): TypedEventFilter<[BigNumber], { value1: BigNumber }>; + + "Event4(tuple)"( + data?: null + ): TypedEventFilter< + [[BigNumber, string] & { index: BigNumber; name: string }], + { data: [BigNumber, string] & { index: BigNumber; name: string } } + >; + Event4( data?: null ): TypedEventFilter< @@ -232,6 +283,8 @@ export class Events extends BaseContract { { data: [BigNumber, string] & { index: BigNumber; name: string } } >; + "NoArgsEvent()"(): TypedEventFilter<[], {}>; + NoArgsEvent(): TypedEventFilter<[], {}>; }; diff --git a/packages/target-ethers-v5/src/codegen/index.ts b/packages/target-ethers-v5/src/codegen/index.ts index ef78a7e1d..b842c8534 100644 --- a/packages/target-ethers-v5/src/codegen/index.ts +++ b/packages/target-ethers-v5/src/codegen/index.ts @@ -72,6 +72,8 @@ export function codegenContractTypings(contract: Contract, codegenConfig: Codege .join('\n')} } + ${values(contract.events).map(generateEventTypeExports).join('\n')} + export class ${contract.name} extends BaseContract { connect(signerOrProvider: Signer | Provider | string): this; attach(addressOrName: string): this; @@ -117,10 +119,7 @@ export function codegenContractTypings(contract: Contract, codegenConfig: Codege }; filters: { - ${values(contract.events) - .map((v) => v[0]) - .map(generateEvents) - .join('\n')} + ${values(contract.events).map(generateEventFilters).join('\n')} }; estimateGas: { @@ -301,13 +300,49 @@ function generateParamNames(params: Array): return params.map((param, index) => param.name || `arg${index}`).join(', ') } -function generateEvents(event: EventDeclaration) { +function generateEventFilters(events: EventDeclaration[]) { + if (events.length === 1) { + return generateEventFilter(events[0], true) + } else { + return events.map((e) => generateEventFilter(e, false)).join('\n') + } +} + +function generateEventFilter(event: EventDeclaration, includeNameFilter: boolean) { + const components = event.inputs.map((input, i) => ({ name: input.name ?? `arg${i.toString()}`, type: input.type })) + const arrayOutput = generateOutputComplexTypeAsArray(components) + const objectOutput = generateOutputComplexTypesAsObject(components) || '{}' + + let filter = ` + '${generateEventSignature(event)}'(${generateEventTypes( + event.inputs, + )}): TypedEventFilter<${arrayOutput}, ${objectOutput}>; + ` + + if (includeNameFilter) { + filter += ` + ${event.name}(${generateEventTypes(event.inputs)}): TypedEventFilter<${arrayOutput}, ${objectOutput}>; + ` + } + return filter +} + +function generateEventTypeExports(events: EventDeclaration[]) { + if (events.length === 1) { + return generateEventTypeExport(events[0], false) + } else { + return events.map((e) => generateEventTypeExport(e, true)).join('\n') + } +} +function generateEventTypeExport(event: EventDeclaration, includeArgTypes: boolean) { const components = event.inputs.map((input, i) => ({ name: input.name ?? `arg${i.toString()}`, type: input.type })) const arrayOutput = generateOutputComplexTypeAsArray(components) const objectOutput = generateOutputComplexTypesAsObject(components) || '{}' return ` - ${event.name}(${generateEventTypes(event.inputs)}): TypedEventFilter<${arrayOutput}, ${objectOutput}>; +export type ${event.name}${ + includeArgTypes ? event.inputs.map((input) => '_' + input.type.originalType).join('') + '_Event' : 'Event' + } = TypedEvent<${arrayOutput} & ${objectOutput}>; ` }