diff --git a/.changeset/wet-eyes-arrive.md b/.changeset/wet-eyes-arrive.md new file mode 100644 index 00000000..6e30b809 --- /dev/null +++ b/.changeset/wet-eyes-arrive.md @@ -0,0 +1,5 @@ +--- +"@delvtech/evm-client": patch +--- + +Add ability to stub events for dynamic filter args diff --git a/packages/evm-client/src/contract/factories/CachedReadContract.test.ts b/packages/evm-client/src/contract/factories/CachedReadContract.test.ts index a4d6ac91..79be249a 100644 --- a/packages/evm-client/src/contract/factories/CachedReadContract.test.ts +++ b/packages/evm-client/src/contract/factories/CachedReadContract.test.ts @@ -45,7 +45,7 @@ describe('createCachedReadContract', () => { transactionHash: '0x123abc', }, ]; - contract.stubEvents('Transfer', stubbedEvents); + contract.stubEvents('Transfer', undefined, stubbedEvents); const events = await cachedContract.getEvents('Transfer'); expect(events).toBe(stubbedEvents); diff --git a/packages/evm-client/src/contract/stubs/ReadContractStub.test.ts b/packages/evm-client/src/contract/stubs/ReadContractStub.test.ts index 49ea9612..bcce9e7b 100644 --- a/packages/evm-client/src/contract/stubs/ReadContractStub.test.ts +++ b/packages/evm-client/src/contract/stubs/ReadContractStub.test.ts @@ -75,9 +75,11 @@ describe('ReadContractStub', () => { it('stubs the getEvents function', async () => { const contract = new ReadContractStub(ERC20ABI); + // throws an error if you forget to stub the event your requesting expect(contract.getEvents('Transfer')).rejects.toThrowError(); - const stubbedEvents: Event[] = [ + // Stub out the events when calling `getEvents` without any filter args + const stubbedAllEvents: Event[] = [ { eventName: 'Transfer', args: { @@ -89,13 +91,56 @@ describe('ReadContractStub', () => { data: '0x123abc', transactionHash: '0x123abc', }, + { + eventName: 'Transfer', + args: { + from: ALICE, + to: BOB, + value: 100n, + }, + blockNumber: 1n, + data: '0x123abc', + transactionHash: '0x123abc', + }, ]; - contract.stubEvents('Transfer', stubbedEvents); + contract.stubEvents('Transfer', undefined, stubbedAllEvents); + // Stub out the events when calling `getEvents` *with* filter args + const stubbedFilteredEvents: Event[] = [ + { + eventName: 'Transfer', + args: { + to: ALICE, + from: BOB, + value: 100n, + }, + blockNumber: 1n, + data: '0x123abc', + transactionHash: '0x123abc', + }, + ]; + contract.stubEvents( + 'Transfer', + { filter: { from: BOB } }, + stubbedFilteredEvents, + ); + + // getting events without any filter args should return the stub that was + // specified without any filter args const events = await contract.getEvents('Transfer'); - expect(events).toBe(stubbedEvents); - + expect(events).toBe(stubbedAllEvents); const stub = contract.getEventsStub('Transfer'); expect(stub?.callCount).toBe(1); + + // getting events with filter args should return the stub that was specified + // *with* filter args + const filteredEvents = await contract.getEvents('Transfer', { + filter: { from: BOB }, + }); + expect(filteredEvents).toBe(stubbedFilteredEvents); + const filteredStub = contract.getEventsStub('Transfer', { + filter: { from: BOB }, + }); + expect(filteredStub?.callCount).toBe(1); }); }); diff --git a/packages/evm-client/src/contract/stubs/ReadContractStub.ts b/packages/evm-client/src/contract/stubs/ReadContractStub.ts index 8a4e2464..0956668d 100644 --- a/packages/evm-client/src/contract/stubs/ReadContractStub.ts +++ b/packages/evm-client/src/contract/stubs/ReadContractStub.ts @@ -98,7 +98,7 @@ export class ReadContractStub async getEvents>( ...[eventName, options]: ContractGetEventsArgs ): Promise[]> { - const stub = this.getEventsStub(eventName); + const stub = this.getEventsStub(eventName, options); if (!stub) { throw new Error( `Called getEvents for ${eventName} on a stubbed contract without a return value. The function must be stubbed first:\n\tcontract.stubEvents("${eventName}", value)`, @@ -162,18 +162,17 @@ export class ReadContractStub * Stubs the return value for a given event name when `getEvents` is called * with that event name. This method overrides any previously stubbed values * for the same event. - * - * *Note: The stub doesn't account for dynamic values based on provided - * arguments/options.* */ stubEvents>( eventName: TEventName, + args: ContractGetEventsOptions | undefined, value: Event[], ): void { - if (this.eventsStubMap.has(eventName)) { - this.getEventsStub(eventName)!.resolves(value); + const stubKey = stringify({ eventName, args }); + if (this.eventsStubMap.has(stubKey)) { + this.getEventsStub(eventName, args)!.resolves(value as any); } else { - this.eventsStubMap.set(eventName, stub().resolves(value) as any); + this.eventsStubMap.set(stubKey, stub().resolves(value) as any); } } @@ -209,8 +208,10 @@ export class ReadContractStub */ getEventsStub>( eventName: TEventName, + args?: ContractGetEventsOptions | undefined, ): EventsStub | undefined { - return this.eventsStubMap.get(eventName) as + const stubKey = stringify({ eventName, args }); + return this.eventsStubMap.get(stubKey) as | EventsStub | undefined; } @@ -270,3 +271,12 @@ type SimulateWriteStub< ], Promise> >; + +function stringify(obj: Record) { + // simple non-recursive stringify replacer for bigints + function replacer(_: any, v: any) { + return typeof v === 'bigint' ? v.toString() : v; + } + + return JSON.stringify(obj, replacer); +}