From 7be3c2130b5fb5ea504c5d51cbbaebebab19f96c Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Thu, 6 Apr 2023 02:46:47 -0400 Subject: [PATCH] Add _in_ operator support for contract and contract.filters (#3901). --- src.ts/_tests/test-contract.ts | 39 +++++++++++++++ src.ts/abi/interface.ts | 22 +++++++++ src.ts/contract/contract.ts | 90 ++++++---------------------------- 3 files changed, 76 insertions(+), 75 deletions(-) diff --git a/src.ts/_tests/test-contract.ts b/src.ts/_tests/test-contract.ts index 6f894abe1d..517118b027 100644 --- a/src.ts/_tests/test-contract.ts +++ b/src.ts/_tests/test-contract.ts @@ -177,6 +177,44 @@ describe("Test Contract", function() { await specificEvent; await allEvents; }); + + it("tests the _in_ operator for functions", function() { + const contract = new Contract(addr, abi); + + assert.equal("testCallAdd" in contract, true, "has(testCallAdd)"); + assert.equal("nonExist" in contract, false, "has(nonExist)"); + + { + const sig = "function testCallAdd(uint256 a, uint256 b) pure returns (uint256 result)"; + assert.equal(sig in contract, true, `has(${ sig })`); + assert.equal("function nonExist()" in contract, false, "has(function nonExist())"); + } + + assert.equal("0xf24684e5" in contract, true, "has(0xf24684e5)"); + assert.equal("0xbad01234" in contract, false, "has(0xbad01234)"); + }); + + it("tests the _in_ operator for events", function() { + const contract = new Contract(addr, abi); + + assert.equal("EventUint256" in contract.filters, true, "has(EventUint256)"); + assert.equal("NonExist" in contract.filters, false, "has(NonExist)"); + + { + const sig = "event EventUint256(uint256 indexed value)"; + assert.equal(sig in contract.filters, true, `has(${ sig })`); + assert.equal("event NonExist()" in contract.filters, false, "has(event NonExist())"); + } + + { + const hash = "0x85c55bbb820e6d71c71f4894e57751de334b38c421f9c170b0e66d32eafea337"; + const badHash = "0xbad01234567890ffbad01234567890ffbad01234567890ffbad01234567890ff"; + assert.equal(hash in contract.filters, true, `has(${ hash })`); + assert.equal(badHash in contract.filters, false, `has(${ badHash })`); + } + + }); + }); describe("Test Typed Contract Interaction", function() { @@ -464,3 +502,4 @@ describe("Test Contract Fallback", function() { } } }); + diff --git a/src.ts/abi/interface.ts b/src.ts/abi/interface.ts index f266e9f253..89f8ae42d9 100644 --- a/src.ts/abi/interface.ts +++ b/src.ts/abi/interface.ts @@ -417,6 +417,17 @@ export class Interface { return fragment.name; } + /** + * Returns true if %%key%% (a function selector, function name or + * function signature) is present in the ABI. + * + * In the case of a function name, the name may be ambiguous, so + * accessing the [[FunctionFragment]] may require refinement. + */ + hasFunction(key: string): boolean { + return !!this.#getFunction(key, null, false); + } + /** * Get the [[FunctionFragment]] for %%key%%, which may be a function * selector, function name or function signature that belongs to the ABI. @@ -515,6 +526,17 @@ export class Interface { return fragment.name; } + /** + * Returns true if %%key%% (an event topic hash, event name or + * event signature) is present in the ABI. + * + * In the case of an event name, the name may be ambiguous, so + * accessing the [[EventFragment]] may require refinement. + */ + hasEvent(key: string): boolean { + return !!this.#getEvent(key, null, false); + } + /** * Get the [[EventFragment]] for %%key%%, which may be a topic hash, * event name or event signature that belongs to the ABI. diff --git a/src.ts/contract/contract.ts b/src.ts/contract/contract.ts index 4d20c07823..9d8adaf1f1 100644 --- a/src.ts/contract/contract.ts +++ b/src.ts/contract/contract.ts @@ -236,81 +236,6 @@ function buildWrappedFallback(contract: BaseContract): WrappedFallback { return method; } -/* -class WrappedFallback { - - constructor (contract: BaseContract) { - defineProperties(this, { _contract: contract }); - - const proxy = new Proxy(this, { - // Perform send when called - apply: async (target, thisArg, args: Array) => { - return await target.send(...args); - }, - }); - - return proxy; - } - - async populateTransaction(overrides?: Omit): Promise { - // If an overrides was passed in, copy it and normalize the values - - const tx: ContractTransaction = (await copyOverrides<"data">(overrides, [ "data" ])); - tx.to = await this._contract.getAddress(); - - const iface = this._contract.interface; - - // Only allow payable contracts to set non-zero value - const payable = iface.receive || (iface.fallback && iface.fallback.payable); - assertArgument(payable || (tx.value || BN_0) === BN_0, - "cannot send value to non-payable contract", "overrides.value", tx.value); - - // Only allow fallback contracts to set non-empty data - assertArgument(iface.fallback || (tx.data || "0x") === "0x", - "cannot send data to receive-only contract", "overrides.data", tx.data); - - return tx; - } - - async staticCall(overrides?: Omit): Promise { - const runner = getRunner(this._contract.runner, "call"); - assert(canCall(runner), "contract runner does not support calling", - "UNSUPPORTED_OPERATION", { operation: "call" }); - - const tx = await this.populateTransaction(overrides); - - try { - return await runner.call(tx); - } catch (error: any) { - if (isCallException(error) && error.data) { - throw this._contract.interface.makeError(error.data, tx); - } - throw error; - } - } - - async send(overrides?: Omit): Promise { - const runner = this._contract.runner; - assert(canSend(runner), "contract runner does not support sending transactions", - "UNSUPPORTED_OPERATION", { operation: "sendTransaction" }); - - const tx = await runner.sendTransaction(await this.populateTransaction(overrides)); - const provider = getProvider(this._contract.runner); - // @TODO: the provider can be null; make a custom dummy provider that will throw a - // meaningful error - return new ContractTransactionResponse(this._contract.interface, provider, tx); - } - - async estimateGas(overrides?: Omit): Promise { - const runner = getRunner(this._contract.runner, "estimateGas"); - assert(canEstimate(runner), "contract runner does not support gas estimation", - "UNSUPPORTED_OPERATION", { operation: "estimateGas" }); - - return await runner.estimateGas(await this.populateTransaction(overrides)); - } -} -*/ - function buildWrappedMethod = Array, R = any, D extends R | ContractTransactionResponse = ContractTransactionResponse>(contract: BaseContract, key: string): BaseContractMethod { const getFragment = function(...args: ContractMethodArgs): FunctionFragment { @@ -748,6 +673,14 @@ export class BaseContract implements Addressable, EventEmitterable { + // Pass important checks (like `then` for Promise) through + if (passProperties.indexOf(prop) >= 0) { + return Reflect.has(target, prop); + } + + return Reflect.has(target, prop) || this.interface.hasEvent(String(prop)); } }); defineProperties(this, { filters }); @@ -769,6 +702,13 @@ export class BaseContract implements Addressable, EventEmitterable { + if (prop in target || passProperties.indexOf(prop) >= 0) { + return Reflect.has(target, prop); + } + + return target.interface.hasFunction(String(prop)); } });