Interface returns null instead of throws on missing fragment.
This commit is contained in:
parent
c2a2edc9d8
commit
32f605eb7b
@ -292,7 +292,7 @@ export class Interface {
|
||||
}
|
||||
|
||||
// Find a function definition by any means necessary (unless it is ambiguous)
|
||||
#getFunction(key: string, values: null | Array<any | Typed>, forceUnique: boolean): FunctionFragment {
|
||||
#getFunction(key: string, values: null | Array<any | Typed>, forceUnique: boolean): null | FunctionFragment {
|
||||
|
||||
// Selector
|
||||
if (isHexString(key)) {
|
||||
@ -300,7 +300,7 @@ export class Interface {
|
||||
for (const fragment of this.#functions.values()) {
|
||||
if (selector === fragment.selector) { return fragment; }
|
||||
}
|
||||
assertArgument(false, "no matching function", "selector", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
// It is a bare name, look up the function (will return null if ambiguous)
|
||||
@ -361,11 +361,11 @@ export class Interface {
|
||||
}
|
||||
}
|
||||
|
||||
assertArgument(matching.length !== 0, "no matching function", "name", key);
|
||||
if (matching.length === 0) { return null; }
|
||||
|
||||
if (matching.length > 1 && forceUnique) {
|
||||
const matchStr = matching.map((m) => JSON.stringify(m.format())).join(", ");
|
||||
assertArgument(false, `multiple matching functions (i.e. ${ matchStr })`, "name", key);
|
||||
assertArgument(false, `ambiguous function description (i.e. matches ${ matchStr })`, "key", key);
|
||||
}
|
||||
|
||||
return matching[0];
|
||||
@ -375,7 +375,7 @@ export class Interface {
|
||||
const result = this.#functions.get(FunctionFragment.from(key).format());
|
||||
if (result) { return result; }
|
||||
|
||||
assertArgument(false, "no matching function", "signature", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -383,7 +383,9 @@ export class Interface {
|
||||
* function name or function signature that belongs to the ABI.
|
||||
*/
|
||||
getFunctionName(key: string): string {
|
||||
return (this.#getFunction(key, null, false)).name;
|
||||
const fragment = this.#getFunction(key, null, false);
|
||||
assertArgument(fragment, "no matching function", "key", key);
|
||||
return fragment.name;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -396,8 +398,8 @@ export class Interface {
|
||||
* If the %%key%% and %%values%% do not refine to a single function in
|
||||
* the ABI, this will throw.
|
||||
*/
|
||||
getFunction(key: string, values?: Array<any | Typed>): FunctionFragment {
|
||||
return this.#getFunction(key, values || null, true)
|
||||
getFunction(key: string, values?: Array<any | Typed>): null | FunctionFragment {
|
||||
return this.#getFunction(key, values || null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -414,7 +416,7 @@ export class Interface {
|
||||
|
||||
|
||||
// Find an event definition by any means necessary (unless it is ambiguous)
|
||||
#getEvent(key: string, values: null | Array<null | any | Typed>, forceUnique: boolean): EventFragment {
|
||||
#getEvent(key: string, values: null | Array<null | any | Typed>, forceUnique: boolean): null | EventFragment {
|
||||
|
||||
// EventTopic
|
||||
if (isHexString(key)) {
|
||||
@ -422,7 +424,7 @@ export class Interface {
|
||||
for (const fragment of this.#events.values()) {
|
||||
if (eventTopic === fragment.topicHash) { return fragment; }
|
||||
}
|
||||
assertArgument(false, "no matching event", "eventTopic", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
// It is a bare name, look up the function (will return null if ambiguous)
|
||||
@ -456,10 +458,11 @@ export class Interface {
|
||||
}
|
||||
}
|
||||
|
||||
assertArgument(matching.length > 0, "no matching event", "name", key);
|
||||
if (matching.length === 0) { return null; }
|
||||
|
||||
if (matching.length > 1 && forceUnique) {
|
||||
// @TODO: refine by Typed
|
||||
assertArgument(false, "multiple matching events", "name", key);
|
||||
const matchStr = matching.map((m) => JSON.stringify(m.format())).join(", ");
|
||||
assertArgument(false, `ambiguous event description (i.e. matches ${ matchStr })`, "key", key);
|
||||
}
|
||||
|
||||
return matching[0];
|
||||
@ -469,7 +472,7 @@ export class Interface {
|
||||
const result = this.#events.get(EventFragment.from(key).format());
|
||||
if (result) { return result; }
|
||||
|
||||
assertArgument(false, "no matching event", "signature", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -477,7 +480,10 @@ export class Interface {
|
||||
* event name or event signature that belongs to the ABI.
|
||||
*/
|
||||
getEventName(key: string): string {
|
||||
return (this.#getEvent(key, null, false)).name;
|
||||
const fragment = this.#getEvent(key, null, false);
|
||||
assertArgument(fragment, "no matching event", "key", key);
|
||||
|
||||
return fragment.name;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -490,7 +496,7 @@ export class Interface {
|
||||
* If the %%key%% and %%values%% do not refine to a single event in
|
||||
* the ABI, this will throw.
|
||||
*/
|
||||
getEvent(key: string, values?: Array<any | Typed>): EventFragment {
|
||||
getEvent(key: string, values?: Array<any | Typed>): null | EventFragment {
|
||||
return this.#getEvent(key, values || null, true)
|
||||
}
|
||||
|
||||
@ -516,7 +522,7 @@ export class Interface {
|
||||
* If the %%key%% and %%values%% do not refine to a single error in
|
||||
* the ABI, this will throw.
|
||||
*/
|
||||
getError(key: string, values?: Array<any | Typed>): ErrorFragment {
|
||||
getError(key: string, values?: Array<any | Typed>): null | ErrorFragment {
|
||||
if (isHexString(key)) {
|
||||
const selector = key.toLowerCase();
|
||||
|
||||
@ -527,7 +533,8 @@ export class Interface {
|
||||
for (const fragment of this.#errors.values()) {
|
||||
if (selector === fragment.selector) { return fragment; }
|
||||
}
|
||||
assertArgument(false, "no matching error", "selector", key);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// It is a bare name, look up the function (will return null if ambiguous)
|
||||
@ -540,10 +547,10 @@ export class Interface {
|
||||
if (matching.length === 0) {
|
||||
if (key === "Error") { return ErrorFragment.from("error Error(string)"); }
|
||||
if (key === "Panic") { return ErrorFragment.from("error Panic(uint256)"); }
|
||||
assertArgument(false, "no matching error", "name", key);
|
||||
return null;
|
||||
} else if (matching.length > 1) {
|
||||
// @TODO: refine by Typed
|
||||
assertArgument(false, "multiple matching errors", "name", key);
|
||||
const matchStr = matching.map((m) => JSON.stringify(m.format())).join(", ");
|
||||
assertArgument(false, `ambiguous error description (i.e. ${ matchStr })`, "name", key);
|
||||
}
|
||||
|
||||
return matching[0];
|
||||
@ -557,7 +564,7 @@ export class Interface {
|
||||
const result = this.#errors.get(key);
|
||||
if (result) { return result; }
|
||||
|
||||
assertArgument(false, "no matching error", "signature", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -629,7 +636,11 @@ export class Interface {
|
||||
* corresponding error.
|
||||
*/
|
||||
decodeErrorResult(fragment: ErrorFragment | string, data: BytesLike): Result {
|
||||
if (typeof(fragment) === "string") { fragment = this.getError(fragment); }
|
||||
if (typeof(fragment) === "string") {
|
||||
const f = this.getError(fragment);
|
||||
assertArgument(f, "unknown error", "fragment", fragment);
|
||||
fragment = f;
|
||||
}
|
||||
|
||||
assertArgument(dataSlice(data, 0, 4) === fragment.selector,
|
||||
`data signature does not match error ${ fragment.name }.`, "data", data);
|
||||
@ -640,13 +651,17 @@ export class Interface {
|
||||
/**
|
||||
* Encodes the transaction revert data for a call result that
|
||||
* reverted from the the Contract with the sepcified %%error%%
|
||||
* (see [[getError]] for valid values for %%key%%) with the %%values%%.
|
||||
* (see [[getError]] for valid values for %%fragment%%) with the %%values%%.
|
||||
*
|
||||
* This is generally not used by most developers, unless trying to mock
|
||||
* a result from a Contract.
|
||||
*/
|
||||
encodeErrorResult(key: ErrorFragment | string, values?: ReadonlyArray<any>): string {
|
||||
const fragment = (typeof(key) === "string") ? this.getError(key): key;
|
||||
encodeErrorResult(fragment: ErrorFragment | string, values?: ReadonlyArray<any>): string {
|
||||
if (typeof(fragment) === "string") {
|
||||
const f = this.getError(fragment);
|
||||
assertArgument(f, "unknown error", "fragment", fragment);
|
||||
fragment = f;
|
||||
}
|
||||
|
||||
return concat([
|
||||
fragment.selector,
|
||||
@ -657,13 +672,17 @@ export class Interface {
|
||||
/**
|
||||
* Decodes the %%data%% from a transaction ``tx.data`` for
|
||||
* the function specified (see [[getFunction]] for valid values
|
||||
* for %%key%%).
|
||||
* for %%fragment%%).
|
||||
*
|
||||
* Most developers should prefer the [[parseTransaction]] method
|
||||
* instead, which will automatically detect the fragment.
|
||||
*/
|
||||
decodeFunctionData(key: FunctionFragment | string, data: BytesLike): Result {
|
||||
const fragment = (typeof(key) === "string") ? this.getFunction(key): key;
|
||||
decodeFunctionData(fragment: FunctionFragment | string, data: BytesLike): Result {
|
||||
if (typeof(fragment) === "string") {
|
||||
const f = this.getFunction(fragment);
|
||||
assertArgument(f, "unknown function", "fragment", fragment);
|
||||
fragment = f;
|
||||
}
|
||||
|
||||
assertArgument(dataSlice(data, 0, 4) === fragment.selector,
|
||||
`data signature does not match function ${ fragment.name }.`, "data", data);
|
||||
@ -673,11 +692,15 @@ export class Interface {
|
||||
|
||||
/**
|
||||
* Encodes the ``tx.data`` for a transaction that calls the function
|
||||
* specified (see [[getFunction]] for valid values for %%key%%) with
|
||||
* specified (see [[getFunction]] for valid values for %%fragment%%) with
|
||||
* the %%values%%.
|
||||
*/
|
||||
encodeFunctionData(key: FunctionFragment | string, values?: ReadonlyArray<any>): string {
|
||||
const fragment = (typeof(key) === "string") ? this.getFunction(key): key;
|
||||
encodeFunctionData(fragment: FunctionFragment | string, values?: ReadonlyArray<any>): string {
|
||||
if (typeof(fragment) === "string") {
|
||||
const f = this.getFunction(fragment);
|
||||
assertArgument(f, "unknown function", "fragment", fragment);
|
||||
fragment = f;
|
||||
}
|
||||
|
||||
return concat([
|
||||
fragment.selector,
|
||||
@ -695,7 +718,11 @@ export class Interface {
|
||||
* corresponding error.
|
||||
*/
|
||||
decodeFunctionResult(fragment: FunctionFragment | string, data: BytesLike): Result {
|
||||
if (typeof(fragment) === "string") { fragment = this.getFunction(fragment); }
|
||||
if (typeof(fragment) === "string") {
|
||||
const f = this.getFunction(fragment);
|
||||
assertArgument(f, "unknown function", "fragment", fragment);
|
||||
fragment = f;
|
||||
}
|
||||
|
||||
let message = "invalid length for result data";
|
||||
|
||||
@ -725,8 +752,8 @@ export class Interface {
|
||||
const selector = hexlify(data.slice(0, 4));
|
||||
|
||||
error.message = "execution reverted (unknown custom error)";
|
||||
try {
|
||||
const ef = this.getError(selector);
|
||||
if (ef) {
|
||||
try {
|
||||
error.revert = {
|
||||
name: ef.name,
|
||||
@ -738,8 +765,6 @@ export class Interface {
|
||||
} catch (e) {
|
||||
error.message = `execution reverted (coult not decode custom error)`
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error); // @TODO: remove
|
||||
}
|
||||
}
|
||||
|
||||
@ -759,13 +784,17 @@ export class Interface {
|
||||
/**
|
||||
* Encodes the result data (e.g. from an ``eth_call``) for the
|
||||
* specified function (see [[getFunction]] for valid values
|
||||
* for %%key%%) with %%values%%.
|
||||
* for %%fragment%%) with %%values%%.
|
||||
*
|
||||
* This is generally not used by most developers, unless trying to mock
|
||||
* a result from a Contract.
|
||||
*/
|
||||
encodeFunctionResult(key: FunctionFragment | string, values?: ReadonlyArray<any>): string {
|
||||
const fragment = (typeof(key) === "string") ? this.getFunction(key): key;
|
||||
encodeFunctionResult(fragment: FunctionFragment | string, values?: ReadonlyArray<any>): string {
|
||||
if (typeof(fragment) === "string") {
|
||||
const f = this.getFunction(fragment);
|
||||
assertArgument(f, "unknown function", "fragment", fragment);
|
||||
fragment = f;
|
||||
}
|
||||
return hexlify(this.#abiCoder.encode(fragment.outputs, values || [ ]));
|
||||
}
|
||||
/*
|
||||
@ -799,16 +828,18 @@ export class Interface {
|
||||
}
|
||||
*/
|
||||
// Create the filter for the event with search criteria (e.g. for eth_filterLog)
|
||||
encodeFilterTopics(eventFragment: EventFragment | string, values: ReadonlyArray<any>): Array<null | string | Array<string>> {
|
||||
if (typeof(eventFragment) === "string") {
|
||||
eventFragment = this.getEvent(eventFragment);
|
||||
encodeFilterTopics(fragment: EventFragment | string, values: ReadonlyArray<any>): Array<null | string | Array<string>> {
|
||||
if (typeof(fragment) === "string") {
|
||||
const f = this.getEvent(fragment);
|
||||
assertArgument(f, "unknown event", "eventFragment", fragment);
|
||||
fragment = f;
|
||||
}
|
||||
|
||||
assert(values.length <= eventFragment.inputs.length, `too many arguments for ${ eventFragment.format() }`,
|
||||
"UNEXPECTED_ARGUMENT", { count: values.length, expectedCount: eventFragment.inputs.length })
|
||||
assert(values.length <= fragment.inputs.length, `too many arguments for ${ fragment.format() }`,
|
||||
"UNEXPECTED_ARGUMENT", { count: values.length, expectedCount: fragment.inputs.length })
|
||||
|
||||
const topics: Array<null | string | Array<string>> = [];
|
||||
if (!eventFragment.anonymous) { topics.push(eventFragment.topicHash); }
|
||||
if (!fragment.anonymous) { topics.push(fragment.topicHash); }
|
||||
|
||||
// @TODO: Use the coders for this; to properly support tuples, etc.
|
||||
const encodeTopic = (param: ParamType, value: any): string => {
|
||||
@ -834,7 +865,7 @@ export class Interface {
|
||||
|
||||
values.forEach((value, index) => {
|
||||
|
||||
const param = (<EventFragment>eventFragment).inputs[index];
|
||||
const param = (<EventFragment>fragment).inputs[index];
|
||||
|
||||
if (!param.indexed) {
|
||||
assertArgument(value == null,
|
||||
@ -861,9 +892,11 @@ export class Interface {
|
||||
return topics;
|
||||
}
|
||||
|
||||
encodeEventLog(eventFragment: EventFragment | string, values: ReadonlyArray<any>): { data: string, topics: Array<string> } {
|
||||
if (typeof(eventFragment) === "string") {
|
||||
eventFragment = this.getEvent(eventFragment);
|
||||
encodeEventLog(fragment: EventFragment | string, values: ReadonlyArray<any>): { data: string, topics: Array<string> } {
|
||||
if (typeof(fragment) === "string") {
|
||||
const f = this.getEvent(fragment);
|
||||
assertArgument(f, "unknown event", "eventFragment", fragment);
|
||||
fragment = f;
|
||||
}
|
||||
|
||||
const topics: Array<string> = [ ];
|
||||
@ -871,14 +904,14 @@ export class Interface {
|
||||
const dataTypes: Array<ParamType> = [ ];
|
||||
const dataValues: Array<string> = [ ];
|
||||
|
||||
if (!eventFragment.anonymous) {
|
||||
topics.push(eventFragment.topicHash);
|
||||
if (!fragment.anonymous) {
|
||||
topics.push(fragment.topicHash);
|
||||
}
|
||||
|
||||
assertArgument(values.length === eventFragment.inputs.length,
|
||||
assertArgument(values.length === fragment.inputs.length,
|
||||
"event arguments/values mismatch", "values", values);
|
||||
|
||||
eventFragment.inputs.forEach((param, index) => {
|
||||
fragment.inputs.forEach((param, index) => {
|
||||
const value = values[index];
|
||||
if (param.indexed) {
|
||||
if (param.type === "string") {
|
||||
@ -904,13 +937,15 @@ export class Interface {
|
||||
}
|
||||
|
||||
// Decode a filter for the event and the search criteria
|
||||
decodeEventLog(eventFragment: EventFragment | string, data: BytesLike, topics?: ReadonlyArray<string>): Result {
|
||||
if (typeof(eventFragment) === "string") {
|
||||
eventFragment = this.getEvent(eventFragment);
|
||||
decodeEventLog(fragment: EventFragment | string, data: BytesLike, topics?: ReadonlyArray<string>): Result {
|
||||
if (typeof(fragment) === "string") {
|
||||
const f = this.getEvent(fragment);
|
||||
assertArgument(f, "unknown event", "eventFragment", fragment);
|
||||
fragment = f;
|
||||
}
|
||||
|
||||
if (topics != null && !eventFragment.anonymous) {
|
||||
const eventTopic = eventFragment.topicHash;
|
||||
if (topics != null && !fragment.anonymous) {
|
||||
const eventTopic = fragment.topicHash;
|
||||
assertArgument(isHexString(topics[0], 32) && topics[0].toLowerCase() === eventTopic,
|
||||
"fragment/topic mismatch", "topics[0]", topics[0]);
|
||||
topics = topics.slice(1);
|
||||
@ -920,7 +955,7 @@ export class Interface {
|
||||
const nonIndexed: Array<ParamType> = [];
|
||||
const dynamic: Array<boolean> = [];
|
||||
|
||||
eventFragment.inputs.forEach((param, index) => {
|
||||
fragment.inputs.forEach((param, index) => {
|
||||
if (param.indexed) {
|
||||
if (param.type === "string" || param.type === "bytes" || param.baseType === "tuple" || param.baseType === "array") {
|
||||
indexed.push(ParamType.from({ type: "bytes32", name: param.name }));
|
||||
@ -942,7 +977,7 @@ export class Interface {
|
||||
const values: Array<any> = [ ];
|
||||
const keys: Array<null | string> = [ ];
|
||||
let nonIndexedIndex = 0, indexedIndex = 0;
|
||||
eventFragment.inputs.forEach((param, index) => {
|
||||
fragment.inputs.forEach((param, index) => {
|
||||
let value = null;
|
||||
if (param.indexed) {
|
||||
if (resultIndexed == null) {
|
||||
|
@ -195,11 +195,19 @@ class WrappedMethod<A extends Array<any> = Array<any>, R = any, D extends R | Co
|
||||
|
||||
// Only works on non-ambiguous keys (refined fragment is always non-ambiguous)
|
||||
get fragment(): FunctionFragment {
|
||||
return this._contract.interface.getFunction(this._key);
|
||||
const fragment = this._contract.interface.getFunction(this._key);
|
||||
assert(fragment, "no matching fragment", "UNSUPPORTED_OPERATION", {
|
||||
operation: "fragment"
|
||||
});
|
||||
return fragment;
|
||||
}
|
||||
|
||||
getFragment(...args: ContractMethodArgs<A>): FunctionFragment {
|
||||
return this._contract.interface.getFunction(this._key, args);
|
||||
const fragment = this._contract.interface.getFunction(this._key, args);
|
||||
assert(fragment, "no matching fragment", "UNSUPPORTED_OPERATION", {
|
||||
operation: "fragment"
|
||||
});
|
||||
return fragment;
|
||||
}
|
||||
|
||||
async populateTransaction(...args: ContractMethodArgs<A>): Promise<ContractTransaction> {
|
||||
@ -299,11 +307,23 @@ class WrappedEvent<A extends Array<any> = Array<any>> extends _WrappedEventBase(
|
||||
|
||||
// Only works on non-ambiguous keys
|
||||
get fragment(): EventFragment {
|
||||
return this._contract.interface.getEvent(this._key);
|
||||
const fragment = this._contract.interface.getEvent(this._key);
|
||||
|
||||
assert(fragment, "no matching fragment", "UNSUPPORTED_OPERATION", {
|
||||
operation: "fragment"
|
||||
});
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
getFragment(...args: ContractEventArgs<A>): EventFragment {
|
||||
return this._contract.interface.getEvent(this._key, args);
|
||||
const fragment = this._contract.interface.getEvent(this._key, args);
|
||||
|
||||
assert(fragment, "no matching fragment", "UNSUPPORTED_OPERATION", {
|
||||
operation: "fragment"
|
||||
});
|
||||
|
||||
return fragment;
|
||||
}
|
||||
};
|
||||
|
||||
@ -355,7 +375,9 @@ async function getSubInfo(contract: BaseContract, event: ContractEventName): Pro
|
||||
if (Array.isArray(event)) {
|
||||
const topicHashify = function(name: string): string {
|
||||
if (isHexString(name, 32)) { return name; }
|
||||
return contract.interface.getEvent(name).topicHash;
|
||||
const fragment = contract.interface.getEvent(name);
|
||||
assertArgument(fragment, "unknown fragment", "name", name);
|
||||
return fragment.topicHash;
|
||||
}
|
||||
|
||||
// Array of Topics and Names; e.g. `[ "0x1234...89ab", "Transfer(address)" ]`
|
||||
@ -375,6 +397,7 @@ async function getSubInfo(contract: BaseContract, event: ContractEventName): Pro
|
||||
} else {
|
||||
// Name or Signature; e.g. `"Transfer", `"Transfer(address)"`
|
||||
fragment = contract.interface.getEvent(event);
|
||||
assertArgument(fragment, "unknown fragment", "event", event);
|
||||
topics = [ fragment.topicHash ];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user