Add _in_ operator support for contract and contract.filters (#3901).

This commit is contained in:
Richard Moore 2023-04-06 02:46:47 -04:00
parent 9060dede49
commit c58ab3a976
3 changed files with 76 additions and 75 deletions

@ -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() {
}
}
});

@ -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.

@ -236,81 +236,6 @@ function buildWrappedFallback(contract: BaseContract): WrappedFallback {
return <WrappedFallback>method;
}
/*
class WrappedFallback {
constructor (contract: BaseContract) {
defineProperties<WrappedFallback>(this, { _contract: contract });
const proxy = new Proxy(this, {
// Perform send when called
apply: async (target, thisArg, args: Array<any>) => {
return await target.send(...args);
},
});
return proxy;
}
async populateTransaction(overrides?: Omit<TransactionRequest, "to">): Promise<ContractTransaction> {
// If an overrides was passed in, copy it and normalize the values
const tx: ContractTransaction = <any>(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<TransactionRequest, "to">): Promise<string> {
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<TransactionRequest, "to">): Promise<ContractTransactionResponse> {
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>provider, tx);
}
async estimateGas(overrides?: Omit<TransactionRequest, "to">): Promise<bigint> {
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<A extends Array<any> = Array<any>, R = any, D extends R | ContractTransactionResponse = ContractTransactionResponse>(contract: BaseContract, key: string): BaseContractMethod<A, R, D> {
const getFragment = function(...args: ContractMethodArgs<A>): FunctionFragment {
@ -748,6 +673,14 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
if (result) { return result; }
throw new Error(`unknown contract event: ${ prop }`);
},
has: (target, prop) => {
// Pass important checks (like `then` for Promise) through
if (passProperties.indexOf(<string>prop) >= 0) {
return Reflect.has(target, prop);
}
return Reflect.has(target, prop) || this.interface.hasEvent(String(prop));
}
});
defineProperties<BaseContract>(this, { filters });
@ -769,6 +702,13 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
if (result) { return result; }
throw new Error(`unknown contract method: ${ prop }`);
},
has: (target, prop) => {
if (prop in target || passProperties.indexOf(<string>prop) >= 0) {
return Reflect.has(target, prop);
}
return target.interface.hasFunction(String(prop));
}
});