From 74f7967be6238db3ccdd8661f66ac76bc696f4ea Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Thu, 15 Sep 2022 22:19:05 -0400 Subject: [PATCH] Refactor Fragment selector and topichash calculation (#3353). --- src.ts/abi/fragments.ts | 20 ++++++++++++++++---- src.ts/abi/index.ts | 21 +++++++++------------ src.ts/abi/interface.ts | 32 +++++++++++++++++--------------- src.ts/abi/typed.ts | 2 +- 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src.ts/abi/fragments.ts b/src.ts/abi/fragments.ts index 96c0e88cc..79f368509 100644 --- a/src.ts/abi/fragments.ts +++ b/src.ts/abi/fragments.ts @@ -2,7 +2,7 @@ import { defineProperties, getBigInt, getNumber, assertPrivate, throwArgumentError, throwError } from "../utils/index.js"; - +import { id } from "../hash/index.js"; export interface JsonFragmentType { readonly name?: string; @@ -80,7 +80,7 @@ const regexType = new RegExp("^(address|bool|bytes([0-9]*)|string|u?int([0-9]*)) /** * @ignore: */ -export type Token = Readonly<{ +type Token = Readonly<{ // Type of token (e.g. TYPE, KEYWORD, NUMBER, etc) type: string; @@ -105,7 +105,7 @@ export type Token = Readonly<{ value: number; }>; -export class TokenString { +class TokenString { #offset: number; #tokens: ReadonlyArray; @@ -212,7 +212,7 @@ export class TokenString { type Writeable = { -readonly [P in keyof T]: T[P] }; -export function lex(text: string): TokenString { +function lex(text: string): TokenString { const tokens: Array = [ ]; const throwError = (message: string) => { @@ -845,6 +845,10 @@ export class ErrorFragment extends NamedFragment { super(guard, "error", name, inputs); } + get selector(): string { + return id(this.format("sighash")).substring(0, 10); + } + format(format: FormatType = "sighash"): string { if (format === "json") { return JSON.stringify({ @@ -882,6 +886,10 @@ export class EventFragment extends NamedFragment { defineProperties(this, { anonymous }); } + get topicHash(): string { + return id(this.format("sighash")); + } + format(format: FormatType = "sighash"): string { if (format === "json") { return JSON.stringify({ @@ -981,6 +989,10 @@ export class FunctionFragment extends NamedFragment { defineProperties(this, { constant, gas, outputs, payable, stateMutability }); } + get selector(): string { + return id(this.format("sighash")).substring(0, 10); + } + format(format: FormatType = "sighash"): string { if (format === "json") { return JSON.stringify({ diff --git a/src.ts/abi/index.ts b/src.ts/abi/index.ts index 333cd0c12..24694f625 100644 --- a/src.ts/abi/index.ts +++ b/src.ts/abi/index.ts @@ -1,4 +1,7 @@ + + +////// export { AbiCoder, defaultAbiCoder @@ -7,31 +10,25 @@ export { export { formatBytes32String, parseBytes32String } from "./bytes32.js"; export { - ConstructorFragment, - ErrorFragment, - EventFragment, - Fragment, - FunctionFragment, - ParamType + ConstructorFragment, ErrorFragment, EventFragment, Fragment, + FunctionFragment, NamedFragment, ParamType, StructFragment, } from "./fragments.js"; export { checkResultErrors, Indexed, Interface, - LogDescription, - Result, - TransactionDescription + ErrorDescription, LogDescription, TransactionDescription, + Result } from "./interface.js"; export { Typed } from "./typed.js"; export type { - JsonFragment, - JsonFragmentType, + JsonFragment, JsonFragmentType, + FormatType, FragmentType, FragmentWalkAsyncFunc, FragmentWalkFunc } from "./fragments.js"; - export type { InterfaceAbi, } from "./interface.js"; diff --git a/src.ts/abi/interface.ts b/src.ts/abi/interface.ts index 0e714fbb4..1258b00da 100644 --- a/src.ts/abi/interface.ts +++ b/src.ts/abi/interface.ts @@ -267,7 +267,7 @@ export class Interface { if (isHexString(key)) { const selector = key.toLowerCase(); for (const fragment of this.#functions.values()) { - if (selector === this.getSelector(fragment)) { return fragment; } + if (selector === fragment.selector) { return fragment; } } throwArgumentError("no matching function", "selector", key); } @@ -378,7 +378,7 @@ export class Interface { if (isHexString(key)) { const eventTopic = key.toLowerCase(); for (const fragment of this.#events.values()) { - if (eventTopic === this.getEventTopic(fragment)) { return fragment; } + if (eventTopic === fragment.topicHash) { return fragment; } } throwArgumentError("no matching event", "eventTopic", key); } @@ -472,7 +472,7 @@ export class Interface { } for (const fragment of this.#errors.values()) { - if (selector === this.getSelector(fragment)) { return fragment; } + if (selector === fragment.selector) { return fragment; } } throwArgumentError("no matching error", "selector", key); } @@ -508,8 +508,8 @@ export class Interface { } // Get the 4-byte selector used by Solidity to identify a function - getSelector(fragment: ErrorFragment | FunctionFragment): string { /* + getSelector(fragment: ErrorFragment | FunctionFragment): string { if (typeof(fragment) === "string") { const matches: Array = [ ]; @@ -524,16 +524,18 @@ export class Interface { fragment = matches[0]; } - */ return dataSlice(id(fragment.format()), 0, 4); } + */ // Get the 32-byte topic hash used by Solidity to identify an event + /* getEventTopic(fragment: EventFragment): string { //if (typeof(fragment) === "string") { fragment = this.getEvent(eventFragment); } return id(fragment.format()); } + */ _decodeParams(params: ReadonlyArray, data: BytesLike): Result { @@ -564,7 +566,7 @@ export class Interface { decodeErrorResult(fragment: ErrorFragment | string, data: BytesLike): Result { if (typeof(fragment) === "string") { fragment = this.getError(fragment); } - if (dataSlice(data, 0, 4) !== this.getSelector(fragment)) { + if (dataSlice(data, 0, 4) !== fragment.selector) { throwArgumentError(`data signature does not match error ${ fragment.name }.`, "data", data); } @@ -583,7 +585,7 @@ export class Interface { const fragment = (typeof(key) === "string") ? this.getError(key): key; return concat([ - this.getSelector(fragment), + fragment.selector, this._encodeParams(fragment.inputs, values || [ ]) ]); } @@ -599,7 +601,7 @@ export class Interface { decodeFunctionData(key: FunctionFragment | string, data: BytesLike): Result { const fragment = (typeof(key) === "string") ? this.getFunction(key): key; - if (dataSlice(data, 0, 4) !== this.getSelector(fragment)) { + if (dataSlice(data, 0, 4) !== fragment.selector) { throwArgumentError(`data signature does not match function ${ fragment.name }.`, "data", data); } @@ -615,7 +617,7 @@ export class Interface { const fragment = (typeof(key) === "string") ? this.getFunction(key): key; return concat([ - this.getSelector(fragment), + fragment.selector, this._encodeParams(fragment.inputs, values || [ ]) ]); } @@ -763,7 +765,7 @@ export class Interface { } const topics: Array> = []; - if (!eventFragment.anonymous) { topics.push(this.getEventTopic(eventFragment)); } + if (!eventFragment.anonymous) { topics.push(eventFragment.topicHash); } // @TODO: Use the coders for this; to properly support tuples, etc. const encodeTopic = (param: ParamType, value: any): string => { @@ -828,7 +830,7 @@ export class Interface { const dataValues: Array = [ ]; if (!eventFragment.anonymous) { - topics.push(this.getEventTopic(eventFragment)); + topics.push(eventFragment.topicHash); } if (values.length !== eventFragment.inputs.length) { @@ -867,7 +869,7 @@ export class Interface { } if (topics != null && !eventFragment.anonymous) { - const eventTopic = this.getEventTopic(eventFragment); + const eventTopic = eventFragment.topicHash; if (!isHexString(topics[0], 32) || topics[0].toLowerCase() !== eventTopic) { throwArgumentError("fragment/topic mismatch", "topics[0]", topics[0]); } @@ -946,7 +948,7 @@ export class Interface { if (!fragment) { return null; } const args = this.#abiCoder.decode(fragment.inputs, data.slice(4)); - return new TransactionDescription(fragment, this.getSelector(fragment), args, value); + return new TransactionDescription(fragment, fragment.selector, args, value); } parseCallResult(data: BytesLike): Result { @@ -969,7 +971,7 @@ export class Interface { // not mean we have the full ABI; maybe just a fragment? - return new LogDescription(fragment, this.getEventTopic(fragment), this.decodeEventLog(fragment, log.data, log.topics)); + return new LogDescription(fragment, fragment.topicHash, this.decodeEventLog(fragment, log.data, log.topics)); } /** @@ -986,7 +988,7 @@ export class Interface { if (!fragment) { return null; } const args = this.#abiCoder.decode(fragment.inputs, dataSlice(hexData, 4)); - return new ErrorDescription(fragment, this.getSelector(fragment), args); + return new ErrorDescription(fragment, fragment.selector, args); } /** diff --git a/src.ts/abi/typed.ts b/src.ts/abi/typed.ts index a41dd7f5a..e75067246 100644 --- a/src.ts/abi/typed.ts +++ b/src.ts/abi/typed.ts @@ -1,4 +1,4 @@ -import { defineProperties } from "../utils/properties.js"; +import { defineProperties } from "../utils/index.js"; import type { Addressable } from "../address/index.js"; import type { BigNumberish, BytesLike } from "../utils/index.js";