Refactor Fragment selector and topichash calculation (#3353).
This commit is contained in:
parent
1e99d82259
commit
74f7967be6
@ -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";
|
||||||
|
Loading…
Reference in New Issue
Block a user