Refactor Fragment selector and topichash calculation (#3353).

This commit is contained in:
Richard Moore 2022-09-15 22:19:05 -04:00
parent 1e99d82259
commit 74f7967be6
4 changed files with 43 additions and 32 deletions

@ -2,7 +2,7 @@ import {
defineProperties, getBigInt, getNumber, defineProperties, getBigInt, getNumber,
assertPrivate, throwArgumentError, throwError assertPrivate, throwArgumentError, throwError
} from "../utils/index.js"; } from "../utils/index.js";
import { id } from "../hash/index.js";
export interface JsonFragmentType { export interface JsonFragmentType {
readonly name?: string; readonly name?: string;
@ -80,7 +80,7 @@ const regexType = new RegExp("^(address|bool|bytes([0-9]*)|string|u?int([0-9]*))
/** /**
* @ignore: * @ignore:
*/ */
export type Token = Readonly<{ type Token = Readonly<{
// Type of token (e.g. TYPE, KEYWORD, NUMBER, etc) // Type of token (e.g. TYPE, KEYWORD, NUMBER, etc)
type: string; type: string;
@ -105,7 +105,7 @@ export type Token = Readonly<{
value: number; value: number;
}>; }>;
export class TokenString { class TokenString {
#offset: number; #offset: number;
#tokens: ReadonlyArray<Token>; #tokens: ReadonlyArray<Token>;
@ -212,7 +212,7 @@ export class TokenString {
type Writeable<T> = { -readonly [P in keyof T]: T[P] }; type Writeable<T> = { -readonly [P in keyof T]: T[P] };
export function lex(text: string): TokenString { function lex(text: string): TokenString {
const tokens: Array<Token> = [ ]; const tokens: Array<Token> = [ ];
const throwError = (message: string) => { const throwError = (message: string) => {
@ -845,6 +845,10 @@ export class ErrorFragment extends NamedFragment {
super(guard, "error", name, inputs); super(guard, "error", name, inputs);
} }
get selector(): string {
return id(this.format("sighash")).substring(0, 10);
}
format(format: FormatType = "sighash"): string { format(format: FormatType = "sighash"): string {
if (format === "json") { if (format === "json") {
return JSON.stringify({ return JSON.stringify({
@ -882,6 +886,10 @@ export class EventFragment extends NamedFragment {
defineProperties<EventFragment>(this, { anonymous }); defineProperties<EventFragment>(this, { anonymous });
} }
get topicHash(): string {
return id(this.format("sighash"));
}
format(format: FormatType = "sighash"): string { format(format: FormatType = "sighash"): string {
if (format === "json") { if (format === "json") {
return JSON.stringify({ return JSON.stringify({
@ -981,6 +989,10 @@ export class FunctionFragment extends NamedFragment {
defineProperties<FunctionFragment>(this, { constant, gas, outputs, payable, stateMutability }); defineProperties<FunctionFragment>(this, { constant, gas, outputs, payable, stateMutability });
} }
get selector(): string {
return id(this.format("sighash")).substring(0, 10);
}
format(format: FormatType = "sighash"): string { format(format: FormatType = "sighash"): string {
if (format === "json") { if (format === "json") {
return JSON.stringify({ return JSON.stringify({

@ -1,4 +1,7 @@
//////
export { export {
AbiCoder, AbiCoder,
defaultAbiCoder defaultAbiCoder
@ -7,31 +10,25 @@ export {
export { formatBytes32String, parseBytes32String } from "./bytes32.js"; export { formatBytes32String, parseBytes32String } from "./bytes32.js";
export { export {
ConstructorFragment, ConstructorFragment, ErrorFragment, EventFragment, Fragment,
ErrorFragment, FunctionFragment, NamedFragment, ParamType, StructFragment,
EventFragment,
Fragment,
FunctionFragment,
ParamType
} from "./fragments.js"; } from "./fragments.js";
export { export {
checkResultErrors, checkResultErrors,
Indexed, Indexed,
Interface, Interface,
LogDescription, ErrorDescription, LogDescription, TransactionDescription,
Result, Result
TransactionDescription
} from "./interface.js"; } from "./interface.js";
export { Typed } from "./typed.js"; export { Typed } from "./typed.js";
export type { export type {
JsonFragment, JsonFragment, JsonFragmentType,
JsonFragmentType, FormatType, FragmentType, FragmentWalkAsyncFunc, FragmentWalkFunc
} from "./fragments.js"; } from "./fragments.js";
export type { export type {
InterfaceAbi, InterfaceAbi,
} from "./interface.js"; } from "./interface.js";

@ -267,7 +267,7 @@ export class Interface {
if (isHexString(key)) { if (isHexString(key)) {
const selector = key.toLowerCase(); const selector = key.toLowerCase();
for (const fragment of this.#functions.values()) { 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); throwArgumentError("no matching function", "selector", key);
} }
@ -378,7 +378,7 @@ export class Interface {
if (isHexString(key)) { if (isHexString(key)) {
const eventTopic = key.toLowerCase(); const eventTopic = key.toLowerCase();
for (const fragment of this.#events.values()) { 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); throwArgumentError("no matching event", "eventTopic", key);
} }
@ -472,7 +472,7 @@ export class Interface {
} }
for (const fragment of this.#errors.values()) { 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); 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 // 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") { if (typeof(fragment) === "string") {
const matches: Array<Fragment> = [ ]; const matches: Array<Fragment> = [ ];
@ -524,16 +524,18 @@ export class Interface {
fragment = matches[0]; fragment = matches[0];
} }
*/
return dataSlice(id(fragment.format()), 0, 4); return dataSlice(id(fragment.format()), 0, 4);
} }
*/
// Get the 32-byte topic hash used by Solidity to identify an event // Get the 32-byte topic hash used by Solidity to identify an event
/*
getEventTopic(fragment: EventFragment): string { getEventTopic(fragment: EventFragment): string {
//if (typeof(fragment) === "string") { fragment = this.getEvent(eventFragment); } //if (typeof(fragment) === "string") { fragment = this.getEvent(eventFragment); }
return id(fragment.format()); return id(fragment.format());
} }
*/
_decodeParams(params: ReadonlyArray<ParamType>, data: BytesLike): Result { _decodeParams(params: ReadonlyArray<ParamType>, data: BytesLike): Result {
@ -564,7 +566,7 @@ export class Interface {
decodeErrorResult(fragment: ErrorFragment | string, data: BytesLike): Result { decodeErrorResult(fragment: ErrorFragment | string, data: BytesLike): Result {
if (typeof(fragment) === "string") { fragment = this.getError(fragment); } 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); 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; const fragment = (typeof(key) === "string") ? this.getError(key): key;
return concat([ return concat([
this.getSelector(fragment), fragment.selector,
this._encodeParams(fragment.inputs, values || [ ]) this._encodeParams(fragment.inputs, values || [ ])
]); ]);
} }
@ -599,7 +601,7 @@ export class Interface {
decodeFunctionData(key: FunctionFragment | string, data: BytesLike): Result { decodeFunctionData(key: FunctionFragment | string, data: BytesLike): Result {
const fragment = (typeof(key) === "string") ? this.getFunction(key): key; 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); 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; const fragment = (typeof(key) === "string") ? this.getFunction(key): key;
return concat([ return concat([
this.getSelector(fragment), fragment.selector,
this._encodeParams(fragment.inputs, values || [ ]) this._encodeParams(fragment.inputs, values || [ ])
]); ]);
} }
@ -763,7 +765,7 @@ export class Interface {
} }
const topics: Array<null | string | Array<string>> = []; const topics: Array<null | string | Array<string>> = [];
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. // @TODO: Use the coders for this; to properly support tuples, etc.
const encodeTopic = (param: ParamType, value: any): string => { const encodeTopic = (param: ParamType, value: any): string => {
@ -828,7 +830,7 @@ export class Interface {
const dataValues: Array<string> = [ ]; const dataValues: Array<string> = [ ];
if (!eventFragment.anonymous) { if (!eventFragment.anonymous) {
topics.push(this.getEventTopic(eventFragment)); topics.push(eventFragment.topicHash);
} }
if (values.length !== eventFragment.inputs.length) { if (values.length !== eventFragment.inputs.length) {
@ -867,7 +869,7 @@ export class Interface {
} }
if (topics != null && !eventFragment.anonymous) { if (topics != null && !eventFragment.anonymous) {
const eventTopic = this.getEventTopic(eventFragment); const eventTopic = eventFragment.topicHash;
if (!isHexString(topics[0], 32) || topics[0].toLowerCase() !== eventTopic) { if (!isHexString(topics[0], 32) || topics[0].toLowerCase() !== eventTopic) {
throwArgumentError("fragment/topic mismatch", "topics[0]", topics[0]); throwArgumentError("fragment/topic mismatch", "topics[0]", topics[0]);
} }
@ -946,7 +948,7 @@ export class Interface {
if (!fragment) { return null; } if (!fragment) { return null; }
const args = this.#abiCoder.decode(fragment.inputs, data.slice(4)); 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 { parseCallResult(data: BytesLike): Result {
@ -969,7 +971,7 @@ export class Interface {
// not mean we have the full ABI; maybe just a fragment? // 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; } if (!fragment) { return null; }
const args = this.#abiCoder.decode(fragment.inputs, dataSlice(hexData, 4)); 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);
} }
/** /**

@ -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 { Addressable } from "../address/index.js";
import type { BigNumberish, BytesLike } from "../utils/index.js"; import type { BigNumberish, BytesLike } from "../utils/index.js";