Refactor Fragment selector and topichash calculation (#3353).
This commit is contained in:
parent
1e99d82259
commit
74f7967be6
@ -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<Token>;
|
||||
|
||||
@ -212,7 +212,7 @@ export class TokenString {
|
||||
|
||||
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 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<EventFragment>(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<FunctionFragment>(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({
|
||||
|
@ -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";
|
||||
|
@ -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<Fragment> = [ ];
|
||||
|
||||
@ -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<ParamType>, 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<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.
|
||||
const encodeTopic = (param: ParamType, value: any): string => {
|
||||
@ -828,7 +830,7 @@ export class Interface {
|
||||
const dataValues: Array<string> = [ ];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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";
|
||||
|
Loading…
Reference in New Issue
Block a user