diff --git a/.gitignore b/.gitignore index de4d1f0..3c3629e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -dist node_modules diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..061b345 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,5 @@ +export * from './tools/event-filters'; +export * from './tools/event-listener'; +export * from './tools/event-wrapper'; +export * from './tools/time'; +export * from './tools/transaction'; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..7dddfef --- /dev/null +++ b/dist/index.js @@ -0,0 +1,22 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./tools/event-filters"), exports); +__exportStar(require("./tools/event-listener"), exports); +__exportStar(require("./tools/event-wrapper"), exports); +__exportStar(require("./tools/time"), exports); +__exportStar(require("./tools/transaction"), exports); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000..53df930 --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,wDAAqC;AACrC,yDAAsC;AACtC,wDAAqC;AACrC,+CAA4B;AAC5B,sDAAmC"} \ No newline at end of file diff --git a/dist/tools/event-filters.d.ts b/dist/tools/event-filters.d.ts new file mode 100644 index 0000000..863bece --- /dev/null +++ b/dist/tools/event-filters.d.ts @@ -0,0 +1,67 @@ +import { ContractReceipt, utils } from 'ethers'; +import { Log } from '@ethersproject/abstract-provider'; +import { TypedEvent, TypedEventFilter } from './event-types'; +export declare type EventDataDecoder = (log: Log) => utils.Result; +export interface ExtendedEventFilter extends TypedEventFilter> { + nonIndexed?: unknown[]; + decodeEventData: EventDataDecoder; +} +/** + * Parses logs for the specific event type + * + * @param logs to be parsed + * @param filter to pick and decode log entries + */ +export declare function filterEventFromLog(logs: Array, filter: ExtendedEventFilter): T[]; +declare type UnwrapEventFilter = T extends ExtendedEventFilter ? R : never; +declare type UnwrapEventFilters = T extends [ + infer Head extends ExtendedEventFilter, + ...infer Tail extends [...ExtendedEventFilter[]] +] ? [UnwrapEventFilter, ...UnwrapEventFilters] : []; +/** + * Parses logs of the receipt by the given filters. + * This function matches the provided sequence of filters agains logs. + * A matched log entry is removed from further matching. + * + * Throws an error when: + * - a filter N matches a log entry with lower index than a filter N-1 + * - not all filters have a match + * + * NB! This function have a special handling for `indexed` event arguments + * of dynamic types (`string`, `bytes`, `arrays`) - these types can be used + * for filtering, but decoded fields will not have values, but special + * Indexed objects with hash. + * + * @param receipt to provide logs for parsing + * @param filters a set of filters to match and parse log entries + * @return a set of parsed log entries matched by the filters + */ +export declare function expectEvents(receipt: ContractReceipt, ...filters: T): UnwrapEventFilters; +/** + * Parses logs of the receipt by the given filters. + * This function matches the provided sequence of filters agains logs. + * This function also returns emmitters of the matched events, so it is + * usable with filters where an emitter is not specified. + * + * When forwardOnly is false only a matched log entry is removed from further matching; + * othterwise, all log entries before the matched entry are also excluded. + * Use forwardOnly = false for a distinct set of events to make sure that ordering is correct. + * Use forwardOnly = true to extract a few events of the same type when some of events are exact and some are not. + * + * NB! This function have a special handling for `indexed` event arguments + * of dynamic types (`string`, `bytes`, `arrays`) - these types can be used + * for filtering, but decoded fields will not have values, but special + * Indexed objects with hash. + * + * Throws an error when: + * - a filter N matches a log entry with lower index than a filter N-1 + * - not all filters have a match + * + * @param receipt to provide logs for parsing + * @param forwardOnly prevents backward logs matching when is true + * @param filters a set of filters to match and parse log entries + * @return a set of emmitters and parsed log entries matched by the filters + */ +export declare function expectEmittersAndEvents(receipt: ContractReceipt, forwardOnly: boolean, ...filters: T): [string[], UnwrapEventFilters]; +export declare function newExtendedEventFilter(eventName: string, emitter: string, decoder: utils.Interface, filter: Partial): ExtendedEventFilter; +export {}; diff --git a/dist/tools/event-filters.js b/dist/tools/event-filters.js new file mode 100644 index 0000000..5030e5e --- /dev/null +++ b/dist/tools/event-filters.js @@ -0,0 +1,265 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.newExtendedEventFilter = exports.expectEmittersAndEvents = exports.expectEvents = exports.filterEventFromLog = void 0; +const ethers_1 = require("ethers"); +const chai_1 = require("chai"); +function decodeEventLogs(logs, decoderLookup) { + const found = []; + for (const entry of logs) { + const decodeFn = decoderLookup(entry.address.toUpperCase(), entry.topics[0]); + // eslint-disable-next-line no-undefined + if (decodeFn !== undefined) { + found.push(decodeFn(entry)); + } + } + return found; +} +function filtersToDecoders(filters) { + const result = new Map(); + for (const filter of filters) { + if (filter.address && filter.topics) { + const eventType = filter.topics[0]; + if (typeof eventType === 'string') { + const addr = filter.address.toUpperCase(); + let subMap = result.get(addr); + // eslint-disable-next-line no-undefined + if (subMap === undefined) { + subMap = new Map(); + result.set(addr, subMap); + } + subMap.set(eventType, filter.decodeEventData); + } + } + } + return result; +} +/** + * Parses logs for the specific event type + * + * @param logs to be parsed + * @param filter to pick and decode log entries + */ +function filterEventFromLog(logs, filter) { + const decoders = filtersToDecoders([filter]); + return decodeEventLogs(logs, (emitter, topic) => decoders.get(emitter)?.get(topic)); +} +exports.filterEventFromLog = filterEventFromLog; +/** + * Parses logs of the receipt by the given filters. + * This function matches the provided sequence of filters agains logs. + * A matched log entry is removed from further matching. + * + * Throws an error when: + * - a filter N matches a log entry with lower index than a filter N-1 + * - not all filters have a match + * + * NB! This function have a special handling for `indexed` event arguments + * of dynamic types (`string`, `bytes`, `arrays`) - these types can be used + * for filtering, but decoded fields will not have values, but special + * Indexed objects with hash. + * + * @param receipt to provide logs for parsing + * @param filters a set of filters to match and parse log entries + * @return a set of parsed log entries matched by the filters + */ +function expectEvents(receipt, ...filters) { + const [, result] = _orderedFilter(receipt.logs, filters, false); + return result; +} +exports.expectEvents = expectEvents; +/** + * Parses logs of the receipt by the given filters. + * This function matches the provided sequence of filters agains logs. + * This function also returns emmitters of the matched events, so it is + * usable with filters where an emitter is not specified. + * + * When forwardOnly is false only a matched log entry is removed from further matching; + * othterwise, all log entries before the matched entry are also excluded. + * Use forwardOnly = false for a distinct set of events to make sure that ordering is correct. + * Use forwardOnly = true to extract a few events of the same type when some of events are exact and some are not. + * + * NB! This function have a special handling for `indexed` event arguments + * of dynamic types (`string`, `bytes`, `arrays`) - these types can be used + * for filtering, but decoded fields will not have values, but special + * Indexed objects with hash. + * + * Throws an error when: + * - a filter N matches a log entry with lower index than a filter N-1 + * - not all filters have a match + * + * @param receipt to provide logs for parsing + * @param forwardOnly prevents backward logs matching when is true + * @param filters a set of filters to match and parse log entries + * @return a set of emmitters and parsed log entries matched by the filters + */ +function expectEmittersAndEvents(receipt, forwardOnly, ...filters) { + const [emitters, result] = _orderedFilter(receipt.logs, filters, forwardOnly); + return [emitters, result]; +} +exports.expectEmittersAndEvents = expectEmittersAndEvents; +function _orderedFilter(actuals, expecteds, forwardOnly) { + const result = []; + const resultAddr = []; + const matched = new Array(actuals.length); + let prevActualIndex = -1; + for (let i = 0; i < expecteds.length; i++) { + for (let j = forwardOnly ? prevActualIndex + 1 : 0; j < actuals.length; j++) { + if (matched[j]) { + // eslint-disable-next-line no-continue + continue; + } + const actual = actuals[j]; + const expected = expecteds[i]; + if (_matchTopics(actual, expected)) { + const decoded = expected.decodeEventData(actual); + if ( + // eslint-disable-next-line no-undefined + expected.nonIndexed === undefined || + _matchProperties(decoded, expected.nonIndexed)) { + (0, chai_1.expect)(j, 'Wrong order of events').gt(prevActualIndex); + prevActualIndex = j; + matched[j] = true; + result.push(decoded); + resultAddr.push(actual.address); + break; + } + } + } + } + (0, chai_1.expect)(result.length, 'Not all expected events were found').eq(expecteds.length); + return [resultAddr, result]; +} +function _matchTopics(actual, expected) { + if ( + // eslint-disable-next-line no-undefined + expected.address !== undefined && + actual.address.toUpperCase() !== expected.address.toUpperCase()) { + return false; + } + let i = -1; + for (const expectedTopic of expected.topics ?? []) { + i++; + if (i >= actual.topics.length) { + return false; + } + if (expectedTopic !== null && expectedTopic !== actual.topics[i]) { + return false; + } + } + return true; +} +function _matchProperties(actual, expected) { + return !expected.some((value, index) => { + if ((value ?? null) === null) { + return false; + } + const eq = isDeepEqual(value, actual[index]); + return !eq; + }); +} +function isDeepEqual(v0, v1) { + if (typeof v0 !== typeof v1) { + return false; + } + if (typeof v0 !== 'object') { + return v0 === v1; + } + if (Array.isArray(v0)) { + if (!Array.isArray(v1)) { + return false; + } + const a0 = v0; + const a1 = v1; + return (a0.length === a1.length && + !a0.some((value, i) => !isDeepEqual(value, a1[i]))); + } + const k0 = Object.getOwnPropertyNames(v0); + const k1 = Object.getOwnPropertyNames(v1); + if (k0.length !== k1.length) { + return false; + } + const s1 = new Set(k1); + for (const key of k0) { + if (!s1.has(key)) { + return false; + } + if (!isDeepEqual(v0[key], v1[key])) { + return false; + } + } + return true; +} +function newExtendedEventFilter(eventName, emitter, decoder, filter) { + let address; + if (emitter !== '*') { + (0, chai_1.expect)(ethers_1.utils.isAddress(emitter), 'Invalid address').is.true; + address = emitter; + } + const fragment = decoder.getEvent(eventName); + const [args, nonIndexed] = _buildFilterArgs(fragment, filter); + return { + address, + topics: decoder.encodeFilterTopics(fragment, args), + nonIndexed: nonIndexed, + decodeEventData(log) { + return decoder.decodeEventLog(fragment, log.data, log.topics); + } + }; +} +exports.newExtendedEventFilter = newExtendedEventFilter; +const _buildFilterArgs = (fragment, properties) => { + const indexed = []; + const nonIndexed = []; + let hasNonIndexed = false; + let maxIndexed = -1; + let namedCount = 0; + // eslint-disable-next-line @typescript-eslint/no-for-in-array + for (const key in properties) { + const v = parseInt(key, 10); + if (isNaN(v)) { + namedCount++; + } + else if (v > maxIndexed) { + maxIndexed = v; + } + } + (0, chai_1.expect)(maxIndexed, 'Inconsistend set of indexed properties').lt(fragment.inputs.length); + if (namedCount > 0 || maxIndexed >= 0) { + fragment.inputs.forEach((param, index) => { + let namedValue = properties[param.name]; + let value = index <= maxIndexed + ? properties[index] ?? null + : null; + // eslint-disable-next-line no-undefined + if (namedValue === undefined) { + namedValue = null; + } + else { + namedCount--; + } + if (namedValue !== null) { + if (value === null) { + value = namedValue; + } + else { + // check for consistency of the input + (0, chai_1.expect)(namedValue).eq(value); + } + } + if (param.indexed) { + indexed.push(value); + nonIndexed.push(null); + } + else { + indexed.push(null); + nonIndexed.push(value); + if (value !== null) { + hasNonIndexed = true; + } + } + }); + (0, chai_1.expect)(namedCount, 'Inconsistend set of named properties').eq(0); + } + return hasNonIndexed ? [indexed, nonIndexed] : [indexed]; +}; +//# sourceMappingURL=event-filters.js.map \ No newline at end of file diff --git a/dist/tools/event-filters.js.map b/dist/tools/event-filters.js.map new file mode 100644 index 0000000..17ba4b5 --- /dev/null +++ b/dist/tools/event-filters.js.map @@ -0,0 +1 @@ +{"version":3,"file":"event-filters.js","sourceRoot":"","sources":["../../tools/event-filters.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/tools/event-listener.d.ts b/dist/tools/event-listener.d.ts new file mode 100644 index 0000000..b5d82bf --- /dev/null +++ b/dist/tools/event-listener.d.ts @@ -0,0 +1,15 @@ +import { BaseContract, Event } from 'ethers'; +/** + * Converts the unvalidated event, into a typed version, verifying the shape. + */ +export interface EventConverter { + (parameters: Event): T; +} +/** + * Listeners for a single type of contract event. + */ +export declare class EventListener { + _events: T[]; + constructor(contract: BaseContract, eventName: string, convert: EventConverter); + events(): T[]; +} diff --git a/dist/tools/event-listener.js b/dist/tools/event-listener.js new file mode 100644 index 0000000..96aa79d --- /dev/null +++ b/dist/tools/event-listener.js @@ -0,0 +1,34 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EventListener = void 0; +const chai_1 = require("chai"); +/** + * Listeners for a single type of contract event. + */ +class EventListener { + constructor(contract, eventName, convert) { + this._events = []; + captureEvents(contract, eventName, (event) => { + this._events.push(convert(event)); + }); + } + events() { + return this._events; + } +} +exports.EventListener = EventListener; +function captureEvents(contract, eventName, react) { + contract.on(eventName, (...args) => { + (0, chai_1.expect)(args.length, 'The event details are missing').is.greaterThanOrEqual(1); + /* + * Array is organised with each parameter being an entry, + * last entry being the entire transaction receipt. + */ + const lastEntry = args.length - 1; + const event = args[lastEntry]; + (0, chai_1.expect)(event.blockNumber, 'The event should have a block number').is.not + .undefined; + react(event); + }); +} +//# sourceMappingURL=event-listener.js.map \ No newline at end of file diff --git a/dist/tools/event-listener.js.map b/dist/tools/event-listener.js.map new file mode 100644 index 0000000..243cdb1 --- /dev/null +++ b/dist/tools/event-listener.js.map @@ -0,0 +1 @@ +{"version":3,"file":"event-listener.js","sourceRoot":"","sources":["../../tools/event-listener.ts"],"names":[],"mappings":";;;AACA,+BAA2B;AAS3B;;GAEG;AACH,MAAa,aAAa;IAGtB,YACI,QAAsB,EACtB,SAAiB,EACjB,OAA0B;QAL9B,YAAO,GAAQ,EAAE,CAAA;QAOb,aAAa,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YACzC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;QACrC,CAAC,CAAC,CAAA;IACN,CAAC;IAEM,MAAM;QACT,OAAO,IAAI,CAAC,OAAO,CAAA;IACvB,CAAC;CACJ;AAhBD,sCAgBC;AAMD,SAAS,aAAa,CAClB,QAAkB,EAClB,SAAiB,EACjB,KAAoB;IAEpB,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,IAAoB,EAAE,EAAE;QAC/C,IAAA,aAAM,EACF,IAAI,CAAC,MAAM,EACX,+BAA+B,CAClC,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAA;QAE1B;;;WAGG;QACH,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAU,CAAA;QAEtC,IAAA,aAAM,EAAC,KAAK,CAAC,WAAW,EAAE,sCAAsC,CAAC,CAAC,EAAE,CAAC,GAAG;aACnE,SAAS,CAAA;QAEd,KAAK,CAAC,KAAK,CAAC,CAAA;IAChB,CAAC,CAAC,CAAA;AACN,CAAC"} \ No newline at end of file diff --git a/dist/tools/event-types.d.ts b/dist/tools/event-types.d.ts new file mode 100644 index 0000000..207cd87 --- /dev/null +++ b/dist/tools/event-types.d.ts @@ -0,0 +1,10 @@ +import type { Event, EventFilter } from "ethers"; +export interface TypedEvent = any, TArgsObject = any> extends Event { + args: TArgsArray & TArgsObject; +} +export interface TypedEventFilter<_TEvent extends TypedEvent> extends EventFilter { +} +export declare type PromiseOrValue = T | Promise; +export declare type EventFilters = { + [name: string]: (...args: Array) => EventFilter; +}; diff --git a/dist/tools/event-types.js b/dist/tools/event-types.js new file mode 100644 index 0000000..2921a45 --- /dev/null +++ b/dist/tools/event-types.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=event-types.js.map \ No newline at end of file diff --git a/dist/tools/event-types.js.map b/dist/tools/event-types.js.map new file mode 100644 index 0000000..abe5c10 --- /dev/null +++ b/dist/tools/event-types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"event-types.js","sourceRoot":"","sources":["../../tools/event-types.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/tools/event-wrapper.d.ts b/dist/tools/event-wrapper.d.ts new file mode 100644 index 0000000..83dc6cb --- /dev/null +++ b/dist/tools/event-wrapper.d.ts @@ -0,0 +1,47 @@ +import { Contract, ContractReceipt } from 'ethers'; +import { ExtendedEventFilter } from './event-filters'; +import { EventListener } from './event-listener'; +import { ContractReceiptSource } from './transaction'; +declare type PartialTuple = T extends [infer Head, ...infer Tail] ? [(Head | null)?, ...PartialTuple] : []; +declare type EventIn = (PartialTuple & O) | A | O; +declare type PartialEventIn = (PartialTuple & Partial) | PartialTuple | Partial; +export interface EventFactoryWithTuple extends EventFactoryOmni { +} +export interface EventFactory extends EventFactoryOmni { + readonly withTuple: EventFactoryWithTuple; +} +export interface EventFactoryOmni { + expectOne(receipt: ContractReceipt, expected?: EventIn): R; + /** + * Parses logs of the receipt by the given filters. + * This function matches the provided sequence of filters agains logs. + * + * When forwardOnly is false only a matched log entry is removed from further matching; + * othterwise, all log entries before the matched entry are also excluded. + * Use forwardOnly = false for a distinct set of events to make sure that ordering is correct. + * Use forwardOnly = true to extract a few events of the same type when some of events are exact and some are not. + * + * NB! This function have a special handling for `indexed` event arguments + * of dynamic types (`string`, `bytes`, `arrays`) - these types can be used + * for filtering, but decoded fields will not have values, but special + * Indexed objects with hash. + * + * Throws an error when: + * - a filter N matches a log entry with lower index than a filter N-1 + * - not all filters have a match + * + * @param receipt to provide logs for parsing + * @param expecteds a set of filters to match and parse log entries + * @param forwardOnly prevents backward logs matching when is true + * @return a set of parsed log entries matched by filters + */ + expectOrdered(receipt: ContractReceipt, expecteds: PartialEventIn[], forwardOnly?: boolean): R[]; + all(receipt: ContractReceipt, fn?: (args: R[]) => Result): Result; + waitAll(source: ContractReceiptSource, fn: (args: R[]) => void): Promise; + toString(): string; + name(): string; + newListener(): EventListener; + newFilter(args?: PartialEventIn, emitterAddress?: string | '*'): ExtendedEventFilter; +} +export declare const wrapEventType: (customName: string, emitter: Contract) => EventFactory; +export {}; diff --git a/dist/tools/event-wrapper.js b/dist/tools/event-wrapper.js new file mode 100644 index 0000000..9ebe881 --- /dev/null +++ b/dist/tools/event-wrapper.js @@ -0,0 +1,107 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.wrapEventType = void 0; +const chai_1 = require("chai"); +const ethers_1 = require("ethers"); +const event_filters_1 = require("./event-filters"); +const event_listener_1 = require("./event-listener"); +const transaction_1 = require("./transaction"); +function findEventArgs(name, receipt, emitter) { + const found = []; + const addr = emitter.address.toUpperCase(); + const parser = emitter.interface; + const fragment = parser.getEvent(name); + const id = ethers_1.utils.id(fragment.format()); + for (const entry of receipt.logs) { + if (entry.topics[0] === id && entry.address.toUpperCase() === addr) { + const parsed = parser.decodeEventLog(fragment, entry.data, entry.topics); + found.push(parsed); + } + } + (0, chai_1.expect)(found.length, `Failed to find any event matching name: ${name}`).is.greaterThan(0); + return found; +} +const wrapEventType = (customName, emitter) => new (class { + constructor() { + this.withTuple = this; + } + expectOne(receipt, expected) { + const args = findEventArgs(this.toString(), receipt, emitter); + (0, chai_1.expect)(args.length, `Expecting a single event ${this.toString()}`).equals(1); + return this.verifyArgs(args[0], expected); + } + expectOrdered(receipt, expecteds, forwardOnly) { + const filters = expecteds.map((expected) => this.newFilter(expected)); + const [, events] = (0, event_filters_1.expectEmittersAndEvents)(receipt, forwardOnly ?? false, ...filters); + return events; + } + all(receipt, fn) { + const args = findEventArgs(this.toString(), receipt, emitter); + // eslint-disable-next-line no-undefined + if (fn === undefined) { + args.forEach((arg) => { + this.verifyArgs(arg); + }); + return args; + } + return fn(args); + } + async waitAll(source, fn) { + const receipt = await (0, transaction_1.successfulTransaction)(source); + this.all(receipt, fn); + return receipt; + } + toString() { + return this.name(); + } + name() { + return customName; + } + verifyArgs(args, expected) { + const n = this.toString(); + if ((expected ?? null) !== null) { + _verifyByProperties(expected, n, args); + } + _verifyByFragment(emitter.interface.getEvent(n), n, args); + return args; + } + newListener() { + const n = this.toString(); + const fragment = emitter.interface.getEvent(n); + return new event_listener_1.EventListener(emitter, n, (event) => { + const args = event.args ?? {}; + _verifyByFragment(fragment, n, args); + return args; + }); + } + newFilter(filter, emitterAddress) { + const n = this.toString(); + return (0, event_filters_1.newExtendedEventFilter)(n, emitterAddress ?? emitter.address, emitter.interface, filter); + } +})(); +exports.wrapEventType = wrapEventType; +const _verifyByFragment = (fragment, name, args) => { + fragment.inputs.forEach((param, index) => { + (0, chai_1.expect)(args[index], `Property ${name}[${index}] is undefined`).is.not + .undefined; + if (param.name) { + (0, chai_1.expect)(args[param.name], `Property ${name}.${param.name} is undefined`).is.not.undefined; + } + }); +}; +const _verifyByProperties = (expected, name, args) => { + if (Array.isArray(expected)) { + ; + expected.forEach((value, index) => { + if ((value ?? null) !== null) { + (0, chai_1.expect)(args[index], `Mismatched value of property ${name}[${index}]`).eq(value); + } + }); + } + Object.entries(expected).forEach(([propName, value]) => { + if ((value ?? null) !== null) { + (0, chai_1.expect)(args[propName], `Mismatched value of property ${name}.${propName}`).eq(value); + } + }); +}; +//# sourceMappingURL=event-wrapper.js.map \ No newline at end of file diff --git a/dist/tools/event-wrapper.js.map b/dist/tools/event-wrapper.js.map new file mode 100644 index 0000000..a23e496 --- /dev/null +++ b/dist/tools/event-wrapper.js.map @@ -0,0 +1 @@ +{"version":3,"file":"event-wrapper.js","sourceRoot":"","sources":["../../tools/event-wrapper.ts"],"names":[],"mappings":";;;AAAA,+BAA2B;AAC3B,mCAAuD;AACvD,mDAIwB;AACxB,qDAA8C;AAC9C,+CAA0E;AAE1E,SAAS,aAAa,CAClB,IAAY,EACZ,OAAwB,EACxB,OAAiB;IAEjB,MAAM,KAAK,GAAmB,EAAE,CAAA;IAEhC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,CAAA;IAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAA;IAEhC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IACtC,MAAM,EAAE,GAAG,cAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAEtC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,IAAI,EAAE;QAC9B,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,IAAI,EAAE;YAChE,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAChC,QAAQ,EACR,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,MAAM,CACf,CAAA;YACD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;SACrB;KACJ;IAED,IAAA,aAAM,EACF,KAAK,CAAC,MAAM,EACZ,2CAA2C,IAAI,EAAE,CACpD,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAA;IAEnB,OAAO,KAAK,CAAA;AAChB,CAAC;AAqEM,MAAM,aAAa,GAAG,CACzB,UAAkB,EAClB,OAAiB,EACC,EAAE,CACpB,IAAI,CAAC;IAAA;QACD,cAAS,GAAG,IAA8C,CAAA;IAoG9D,CAAC;IAlGG,SAAS,CACL,OAAwB,EACxB,QAAwB;QAExB,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;QAE7D,IAAA,aAAM,EACF,IAAI,CAAC,MAAM,EACX,4BAA4B,IAAI,CAAC,QAAQ,EAAE,EAAE,CAChD,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QACX,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAgC,CAAC,CAAA;IACrE,CAAC;IAED,aAAa,CACT,OAAwB,EACxB,SAAiC,EACjC,WAAqB;QAErB,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CACvC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAC3B,CAAA;QACD,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,IAAA,uCAAuB,EACtC,OAAO,EACP,WAAW,IAAI,KAAK,EACpB,GAAG,OAAO,CACb,CAAA;QACD,OAAO,MAAM,CAAA;IACjB,CAAC;IAED,GAAG,CACC,OAAwB,EACxB,EAA0B;QAE1B,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;QAE7D,wCAAwC;QACxC,IAAI,EAAE,KAAK,SAAS,EAAE;YAClB,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAQ,EAAE;gBACvB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;YACxB,CAAC,CAAC,CAAA;YACF,OAAO,IAAyB,CAAA;SACnC;QAED,OAAO,EAAE,CAAC,IAAsB,CAAC,CAAA;IACrC,CAAC;IAED,KAAK,CAAC,OAAO,CACT,MAA6B,EAC7B,EAAwB;QAExB,MAAM,OAAO,GAAG,MAAM,IAAA,mCAAqB,EAAC,MAAM,CAAC,CAAA;QACnD,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;QACrB,OAAO,OAAO,CAAA;IAClB,CAAC;IAED,QAAQ;QACJ,OAAO,IAAI,CAAC,IAAI,EAAE,CAAA;IACtB,CAAC;IAED,IAAI;QACA,OAAO,UAAU,CAAA;IACrB,CAAC;IAEO,UAAU,CACd,IAAkB,EAClB,QAA+B;QAE/B,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;QACzB,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE;YAC7B,mBAAmB,CAAC,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,CAAA;SACzC;QACD,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAA;QACzD,OAAO,IAAoB,CAAA;IAC/B,CAAC;IAED,WAAW;QACP,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;QAEzB,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;QAC9C,OAAO,IAAI,8BAAa,CAAI,OAAO,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE;YAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAK,EAAmB,CAAA;YAC/C,iBAAiB,CAAC,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,CAAA;YACpC,OAAO,IAAoB,CAAA;QAC/B,CAAC,CAAC,CAAA;IACN,CAAC;IAED,SAAS,CACL,MAA6B,EAC7B,cAAuB;QAEvB,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;QACzB,OAAO,IAAA,sCAAsB,EACzB,CAAC,EACD,cAAc,IAAI,OAAO,CAAC,OAAO,EACjC,OAAO,CAAC,SAAS,EACjB,MAAsB,CACzB,CAAA;IACL,CAAC;CACJ,CAAC,EAAE,CAAA;AAzGK,QAAA,aAAa,iBAyGlB;AAER,MAAM,iBAAiB,GAAG,CACtB,QAA6B,EAC7B,IAAY,EACZ,IAAkB,EACpB,EAAE;IACA,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACrC,IAAA,aAAM,EAAC,IAAI,CAAC,KAAK,CAAC,EAAE,YAAY,IAAI,IAAI,KAAK,gBAAgB,CAAC,CAAC,EAAE,CAAC,GAAG;aAChE,SAAS,CAAA;QAEd,IAAI,KAAK,CAAC,IAAI,EAAE;YACZ,IAAA,aAAM,EACF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAChB,YAAY,IAAI,IAAI,KAAK,CAAC,IAAI,eAAe,CAChD,CAAC,EAAE,CAAC,GAAG,CAAC,SAAS,CAAA;SACrB;IACL,CAAC,CAAC,CAAA;AACN,CAAC,CAAA;AAED,MAAM,mBAAmB,GAAG,CACxB,QAAW,EACX,IAAY,EACZ,IAAkB,EACpB,EAAE;IACA,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;QACzB,CAAC;QAAC,QAAsB,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YAC9C,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE;gBAC1B,IAAA,aAAM,EACF,IAAI,CAAC,KAAK,CAAC,EACX,gCAAgC,IAAI,IAAI,KAAK,GAAG,CACnD,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;aACd;QACL,CAAC,CAAC,CAAA;KACL;IACD,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE;QACnD,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE;YAC1B,IAAA,aAAM,EACF,IAAI,CAAC,QAAQ,CAAC,EACd,gCAAgC,IAAI,IAAI,QAAQ,EAAE,CACrD,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;SACd;IACL,CAAC,CAAC,CAAA;AACN,CAAC,CAAA"} \ No newline at end of file diff --git a/dist/tools/time.d.ts b/dist/tools/time.d.ts new file mode 100644 index 0000000..fb62818 --- /dev/null +++ b/dist/tools/time.d.ts @@ -0,0 +1,13 @@ +/** + * Whether the side effects being awaited have occurred. + */ +export interface SideEffectOccurrence { + (): boolean; +} +/** + * Delays processing, with an early exit condition. + * + * @param earlyStop awaiting the side effect. + * @param maximumDelayMs most amount of time to await side effect. + */ +export declare function occurrenceAtMost(earlyStop: SideEffectOccurrence, maximumDelayMs: number): Promise; diff --git a/dist/tools/time.js b/dist/tools/time.js new file mode 100644 index 0000000..54edf23 --- /dev/null +++ b/dist/tools/time.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.occurrenceAtMost = void 0; +const PAUSE_TIME_INCREMENT_MS = 100; +/** + * Delays processing, with an early exit condition. + * + * @param earlyStop awaiting the side effect. + * @param maximumDelayMs most amount of time to await side effect. + */ +async function occurrenceAtMost(earlyStop, maximumDelayMs) { + let passedMs = 0; + while (!earlyStop() && passedMs < maximumDelayMs) { + await sleep(PAUSE_TIME_INCREMENT_MS); + passedMs += PAUSE_TIME_INCREMENT_MS; + } +} +exports.occurrenceAtMost = occurrenceAtMost; +function sleep(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} +//# sourceMappingURL=time.js.map \ No newline at end of file diff --git a/dist/tools/time.js.map b/dist/tools/time.js.map new file mode 100644 index 0000000..8fd5aae --- /dev/null +++ b/dist/tools/time.js.map @@ -0,0 +1 @@ +{"version":3,"file":"time.js","sourceRoot":"","sources":["../../tools/time.ts"],"names":[],"mappings":";;;AAAA,MAAM,uBAAuB,GAAG,GAAG,CAAA;AASnC;;;;;GAKG;AACI,KAAK,UAAU,gBAAgB,CAClC,SAA+B,EAC/B,cAAsB;IAEtB,IAAI,QAAQ,GAAG,CAAC,CAAA;IAEhB,OAAO,CAAC,SAAS,EAAE,IAAI,QAAQ,GAAG,cAAc,EAAE;QAC9C,MAAM,KAAK,CAAC,uBAAuB,CAAC,CAAA;QACpC,QAAQ,IAAI,uBAAuB,CAAA;KACtC;AACL,CAAC;AAVD,4CAUC;AAED,SAAS,KAAK,CAAC,EAAU;IACrB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC3B,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;IAC3B,CAAC,CAAC,CAAA;AACN,CAAC"} \ No newline at end of file diff --git a/dist/tools/transaction.d.ts b/dist/tools/transaction.d.ts new file mode 100644 index 0000000..a13a6bf --- /dev/null +++ b/dist/tools/transaction.d.ts @@ -0,0 +1,10 @@ +import { ContractTransaction } from 'ethers'; +import { ContractReceipt } from '@ethersproject/contracts/src.ts/index'; +export declare type ContractReceiptSource = ContractReceipt | Promise | ContractTransaction | Promise; +export declare function contractReceiptOf(av: ContractReceiptSource, confirmations?: number): Promise; +/** + * The expectation is successful transaction (with receipt). + * + * @param transaction waits for the receipt, verifying it is a success. + */ +export declare function successfulTransaction(transaction: ContractReceiptSource): Promise; diff --git a/dist/tools/transaction.js b/dist/tools/transaction.js new file mode 100644 index 0000000..bebec07 --- /dev/null +++ b/dist/tools/transaction.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.successfulTransaction = exports.contractReceiptOf = void 0; +const chai_1 = require("chai"); +async function contractReceiptOf(av, confirmations) { + const v = await av; + return 'gasUsed' in v ? v : v.wait(confirmations); +} +exports.contractReceiptOf = contractReceiptOf; +/** + * The expectation is successful transaction (with receipt). + * + * @param transaction waits for the receipt, verifying it is a success. + */ +async function successfulTransaction(transaction) { + const receipt = await contractReceiptOf(transaction, 1); + // Transaction status code https://eips.ethereum.org/EIPS/eip-1066 + const SUCCESS = 1; + (0, chai_1.expect)(receipt).is.not.undefined; + (0, chai_1.expect)(receipt.status).equals(SUCCESS); + return receipt; +} +exports.successfulTransaction = successfulTransaction; +//# sourceMappingURL=transaction.js.map \ No newline at end of file diff --git a/dist/tools/transaction.js.map b/dist/tools/transaction.js.map new file mode 100644 index 0000000..48247d2 --- /dev/null +++ b/dist/tools/transaction.js.map @@ -0,0 +1 @@ +{"version":3,"file":"transaction.js","sourceRoot":"","sources":["../../tools/transaction.ts"],"names":[],"mappings":";;;AAAA,+BAA2B;AAUpB,KAAK,UAAU,iBAAiB,CACnC,EAAyB,EACzB,aAAsB;IAEtB,MAAM,CAAC,GAAG,MAAM,EAAE,CAAA;IAClB,OAAO,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;AACrD,CAAC;AAND,8CAMC;AAED;;;;GAIG;AACI,KAAK,UAAU,qBAAqB,CACvC,WAAkC;IAElC,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;IAEvD,kEAAkE;IAClE,MAAM,OAAO,GAAG,CAAC,CAAA;IAEjB,IAAA,aAAM,EAAC,OAAO,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,SAAS,CAAA;IAChC,IAAA,aAAM,EAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAEtC,OAAO,OAAO,CAAA;AAClB,CAAC;AAZD,sDAYC"} \ No newline at end of file diff --git a/package.json b/package.json index 1dbf182..3f2a0be 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "files": ["dist/*", "dist/**/*"], "main": "dist/index.js", + "types": "dist/index.d.ts", "peerDependencies": { "ethers": "5.6.9", "@types/chai": "4.3.1", diff --git a/tsconfig.json b/tsconfig.json index f167e34..5652c0c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "allowSyntheticDefaultImports": true, + "declaration": true, "esModuleInterop": true, "experimentalDecorators": true, "forceConsistentCasingInFileNames": true, @@ -21,7 +22,7 @@ } ] }, - + "files": ["./index.ts"], "include": ["./tools/**/*.ts"], "exclude": ["node_modules"]