docs: added jsdocs to abi

This commit is contained in:
Richard Moore 2022-11-27 21:54:49 -05:00
parent 16f3c44daa
commit b7750cf098
17 changed files with 328 additions and 100 deletions

View File

@ -1,3 +1,16 @@
/**
* When sending values to or receiving values from a [[Contract]], the
* data is generally encoded using the [ABI standard](solc-abi-standard).
*
* The AbiCoder provides a utility to encode values to ABI data and
* decode values from ABI data.
*
* Most of the time, developers should favour the [[Contract]] class,
* which further abstracts a lot of the finer details of ABI data.
*
* @_section api/abi/abi-coder:ABI Encoding
*/
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI // See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
import { assertArgumentCount, assertArgument } from "../utils/index.js"; import { assertArgumentCount, assertArgument } from "../utils/index.js";
@ -22,10 +35,92 @@ import type {
CallExceptionAction, CallExceptionError, CallExceptionTransaction CallExceptionAction, CallExceptionError, CallExceptionTransaction
} from "../utils/index.js"; } from "../utils/index.js";
// https://docs.soliditylang.org/en/v0.8.17/control-structures.html
const PanicReasons: Map<number, string> = new Map();
PanicReasons.set(0x00, "GENERIC_PANIC");
PanicReasons.set(0x01, "ASSERT_FALSE");
PanicReasons.set(0x11, "OVERFLOW");
PanicReasons.set(0x12, "DIVIDE_BY_ZERO");
PanicReasons.set(0x21, "ENUM_RANGE_ERROR");
PanicReasons.set(0x22, "BAD_STORAGE_DATA");
PanicReasons.set(0x31, "STACK_UNDERFLOW");
PanicReasons.set(0x32, "ARRAY_RANGE_ERROR");
PanicReasons.set(0x41, "OUT_OF_MEMORY");
PanicReasons.set(0x51, "UNINITIALIZED_FUNCTION_CALL");
const paramTypeBytes = new RegExp(/^bytes([0-9]*)$/); const paramTypeBytes = new RegExp(/^bytes([0-9]*)$/);
const paramTypeNumber = new RegExp(/^(u?int)([0-9]*)$/); const paramTypeNumber = new RegExp(/^(u?int)([0-9]*)$/);
let defaultCoder: null | AbiCoder = null;
function getBuiltinCallException(action: CallExceptionAction, tx: { to?: null | string, from?: null | string, data?: string }, data: null | BytesLike, abiCoder: AbiCoder): CallExceptionError {
let message = "missing revert data";
let reason: null | string = null;
const invocation = null;
let revert: null | { signature: string, name: string, args: Array<any> } = null;
if (data) {
message = "execution reverted";
const bytes = getBytes(data);
data = hexlify(data);
if (bytes.length % 32 !== 4) {
message += " (could not decode reason; invalid data length)";
} else if (hexlify(bytes.slice(0, 4)) === "0x08c379a0") {
// Error(string)
try {
reason = abiCoder.decode([ "string" ], bytes.slice(4))[0]
revert = {
signature: "Error(string)",
name: "Error",
args: [ reason ]
};
message += `: ${ JSON.stringify(reason) }`;
} catch (error) {
console.log(error);
message += " (could not decode reason; invalid data)";
}
} else if (hexlify(bytes.slice(0, 4)) === "0x4e487b71") {
// Panic(uint256)
try {
const code = Number(abiCoder.decode([ "uint256" ], bytes.slice(4))[0]);
revert = {
signature: "Panic(uint256)",
name: "Panic",
args: [ code ]
};
reason = `Panic due to ${ PanicReasons.get(code) || "UNKNOWN" }(${ code })`;
message += `: ${ reason }`;
} catch (error) {
console.log(error);
message += " (could not decode panic reason)";
}
} else {
message += " (unknown custom error)";
}
}
const transaction: CallExceptionTransaction = {
to: (tx.to ? getAddress(tx.to): null),
data: (tx.data || "0x")
};
if (tx.from) { transaction.from = getAddress(tx.from); }
return makeError(message, "CALL_EXCEPTION", {
action, data, reason, transaction, invocation, revert
});
}
/**
* About AbiCoder
*/
export class AbiCoder { export class AbiCoder {
#getCoder(param: ParamType): Coder { #getCoder(param: ParamType): Coder {
@ -70,12 +165,23 @@ export class AbiCoder {
assertArgument(false, "invalid type", "type", param.type); assertArgument(false, "invalid type", "type", param.type);
} }
/**
* Get the default values for the given %%types%%.
*
* For example, a ``uint`` is by default ``0`` and ``bool``
* is by default ``false``.
*/
getDefaultValue(types: ReadonlyArray<string | ParamType>): Result { getDefaultValue(types: ReadonlyArray<string | ParamType>): Result {
const coders: Array<Coder> = types.map((type) => this.#getCoder(ParamType.from(type))); const coders: Array<Coder> = types.map((type) => this.#getCoder(ParamType.from(type)));
const coder = new TupleCoder(coders, "_"); const coder = new TupleCoder(coders, "_");
return coder.defaultValue(); return coder.defaultValue();
} }
/**
* Encode the %%values%% as the %%types%% into ABI data.
*
* @returns DataHexstring
*/
encode(types: ReadonlyArray<string | ParamType>, values: ReadonlyArray<any>): string { encode(types: ReadonlyArray<string | ParamType>, values: ReadonlyArray<any>): string {
assertArgumentCount(values.length, types.length, "types/values length mismatch"); assertArgumentCount(values.length, types.length, "types/values length mismatch");
@ -87,87 +193,37 @@ export class AbiCoder {
return writer.data; return writer.data;
} }
/**
* Decode the ABI %%data%% as the %%types%% into values.
*
* If %%loose%% decoding is enabled, then strict padding is
* not enforced. Some older versions of Solidity incorrectly
* padded event data emitted from ``external`` functions.
*/
decode(types: ReadonlyArray<string | ParamType>, data: BytesLike, loose?: boolean): Result { decode(types: ReadonlyArray<string | ParamType>, data: BytesLike, loose?: boolean): Result {
const coders: Array<Coder> = types.map((type) => this.#getCoder(ParamType.from(type))); const coders: Array<Coder> = types.map((type) => this.#getCoder(ParamType.from(type)));
const coder = new TupleCoder(coders, "_"); const coder = new TupleCoder(coders, "_");
return coder.decode(new Reader(data, loose)); return coder.decode(new Reader(data, loose));
} }
}
// https://docs.soliditylang.org/en/v0.8.17/control-structures.html /**
const PanicReasons: Map<number, string> = new Map(); * Returns the shared singleton instance of a default [[AbiCoder]].
PanicReasons.set(0x00, "GENERIC_PANIC"); *
PanicReasons.set(0x01, "ASSERT_FALSE"); * On the first call, the instance is created internally.
PanicReasons.set(0x11, "OVERFLOW"); */
PanicReasons.set(0x12, "DIVIDE_BY_ZERO"); static defaultAbiCoder(): AbiCoder {
PanicReasons.set(0x21, "ENUM_RANGE_ERROR"); if (defaultCoder == null) {
PanicReasons.set(0x22, "BAD_STORAGE_DATA"); defaultCoder = new AbiCoder();
PanicReasons.set(0x31, "STACK_UNDERFLOW");
PanicReasons.set(0x32, "ARRAY_RANGE_ERROR");
PanicReasons.set(0x41, "OUT_OF_MEMORY");
PanicReasons.set(0x51, "UNINITIALIZED_FUNCTION_CALL");
export function getBuiltinCallException(action: CallExceptionAction, tx: { to?: null | string, from?: null | string, data?: string }, data: null | BytesLike): CallExceptionError {
let message = "missing revert data";
let reason: null | string = null;
const invocation = null;
let revert: null | { signature: string, name: string, args: Array<any> } = null;
if (data) {
message = "execution reverted";
const bytes = getBytes(data);
data = hexlify(data);
if (bytes.length % 32 !== 4) {
message += " (could not decode reason; invalid data length)";
} else if (hexlify(bytes.slice(0, 4)) === "0x08c379a0") {
// Error(string)
try {
reason = defaultAbiCoder.decode([ "string" ], bytes.slice(4))[0]
revert = {
signature: "Error(string)",
name: "Error",
args: [ reason ]
};
message += `: ${ JSON.stringify(reason) }`;
} catch (error) {
console.log(error);
message += " (could not decode reason; invalid data)";
}
} else if (hexlify(bytes.slice(0, 4)) === "0x4e487b71") {
// Panic(uint256)
try {
const code = Number(defaultAbiCoder.decode([ "uint256" ], bytes.slice(4))[0]);
revert = {
signature: "Panic(uint256)",
name: "Panic",
args: [ code ]
};
reason = `Panic due to ${ PanicReasons.get(code) || "UNKNOWN" }(${ code })`;
message += `: ${ reason }`;
} catch (error) {
console.log(error);
message += " (could not decode panic reason)";
}
} else {
message += " (unknown custom error)";
} }
return defaultCoder;
} }
const transaction: CallExceptionTransaction = { /**
to: (tx.to ? getAddress(tx.to): null), * Returns an ethers-compatible [[CALL_EXCEPTION]] Error for the given
data: (tx.data || "0x") * result %%data%% for the [[CallExceptionAction]] %%action%% against
}; * the Transaction %%tx%%.
if (tx.from) { transaction.from = getAddress(tx.from); } */
static getBuiltinCallException(action: CallExceptionAction, tx: { to?: null | string, from?: null | string, data?: string }, data: null | BytesLike): CallExceptionError {
return makeError(message, "CALL_EXCEPTION", { return getBuiltinCallException(action, tx, data, AbiCoder.defaultAbiCoder());
action, data, reason, transaction, invocation, revert }
});
} }
export const defaultAbiCoder: AbiCoder = new AbiCoder();

View File

@ -1,3 +1,8 @@
/**
* About bytes32 strings...
*
* @_docloc: api/utils:Bytes32 Strings
*/
import { import {
getBytes, toUtf8Bytes, toUtf8String, zeroPadBytes getBytes, toUtf8Bytes, toUtf8String, zeroPadBytes
@ -5,7 +10,9 @@ import {
import type { BytesLike } from "../utils/index.js"; import type { BytesLike } from "../utils/index.js";
/**
* Encodes %%text%% as a Bytes32 string.
*/
export function encodeBytes32String(text: string): string { export function encodeBytes32String(text: string): string {
// Get the bytes // Get the bytes
@ -18,6 +25,9 @@ export function encodeBytes32String(text: string): string {
return zeroPadBytes(bytes, 32); return zeroPadBytes(bytes, 32);
} }
/**
* Encodes the Bytes32-encoded %%bytes%% into a string.
*/
export function decodeBytes32String(_bytes: BytesLike): string { export function decodeBytes32String(_bytes: BytesLike): string {
const data = getBytes(_bytes, "bytes"); const data = getBytes(_bytes, "bytes");

View File

@ -7,7 +7,7 @@ import {
import type { BigNumberish, BytesLike } from "../../utils/index.js"; import type { BigNumberish, BytesLike } from "../../utils/index.js";
export const WordSize = 32; export const WordSize: number = 32;
const Padding = new Uint8Array(WordSize); const Padding = new Uint8Array(WordSize);
// Properties used to immediate pass through to the underlying object // Properties used to immediate pass through to the underlying object
@ -16,11 +16,21 @@ const passProperties = [ "then" ];
const _guard = { }; const _guard = { };
/**
* A [[Result]] is a sub-class of Array, which allows accessing any
* of its values either positionally by its index or, if keys are
* provided by its name.
*
* @_docloc: api/abi
*/
export class Result extends Array<any> { export class Result extends Array<any> {
#indices: Map<string, Array<number>>; #indices: Map<string, Array<number>>;
[ K: string | number ]: any [ K: string | number ]: any
/**
* @private
*/
constructor(guard: any, items: Array<any>, keys?: Array<null | string>) { constructor(guard: any, items: Array<any>, keys?: Array<null | string>) {
assertPrivate(guard, _guard, "Result"); assertPrivate(guard, _guard, "Result");
super(...items); super(...items);
@ -85,6 +95,9 @@ export class Result extends Array<any> {
} }
*/ */
/**
* @_ignore
*/
slice(start?: number | undefined, end?: number | undefined): Array<any> { slice(start?: number | undefined, end?: number | undefined): Array<any> {
if (start == null) { start = 0; } if (start == null) { start = 0; }
if (end == null) { end = this.length; } if (end == null) { end = this.length; }
@ -108,6 +121,14 @@ export class Result extends Array<any> {
throw wrapped; throw wrapped;
} }
/**
* Returns the value for %%name%%.
*
* Since it is possible to have a key whose name conflicts with
* a method on a [[Result]] or its superclass Array, or any
* JavaScript keyword, this ensures all named values are still
* accessible by name.
*/
getValue(name: string): any { getValue(name: string): any {
const index = this.#indices.get(name); const index = this.#indices.get(name);
if (index != null && index.length === 1) { if (index != null && index.length === 1) {
@ -121,11 +142,28 @@ export class Result extends Array<any> {
throw new Error(`no named parameter: ${ JSON.stringify(name) }`); throw new Error(`no named parameter: ${ JSON.stringify(name) }`);
} }
static fromItems(items: Array<any>, keys?: Array<null | string>) { /**
* Creates a new [[Result]] for %%items%% with each entry
* also accessible by its corresponding name in %%keys%%.
*/
static fromItems(items: Array<any>, keys?: Array<null | string>): Result {
return new Result(_guard, items, keys); return new Result(_guard, items, keys);
} }
} }
/**
* Returns all errors found in a [[Result]].
*
* Since certain errors encountered when creating a [[Result]] do
* not impact the ability to continue parsing data, they are
* deferred until they are actually accessed. Hence a faulty string
* in an Event that is never used does not impact the program flow.
*
* However, sometimes it may be useful to access, identify or
* validate correctness of a [[Result]].
*
* @_docloc api/abi
*/
export function checkResultErrors(result: Result): Array<{ path: Array<string | number>, error: Error }> { export function checkResultErrors(result: Result): Array<{ path: Array<string | number>, error: Error }> {
// Find the first error (if any) // Find the first error (if any)
const errors: Array<{ path: Array<string | number>, error: Error }> = [ ]; const errors: Array<{ path: Array<string | number>, error: Error }> = [ ];
@ -162,7 +200,9 @@ function getValue(value: BigNumberish): Uint8Array {
return bytes; return bytes;
} }
/**
* @_ignore
*/
export abstract class Coder { export abstract class Coder {
// The coder name: // The coder name:
@ -198,6 +238,9 @@ export abstract class Coder {
abstract defaultValue(): any; abstract defaultValue(): any;
} }
/**
* @_ignore
*/
export class Writer { export class Writer {
// An array of WordSize lengthed objects to concatenation // An array of WordSize lengthed objects to concatenation
#data: Array<Uint8Array>; #data: Array<Uint8Array>;
@ -250,6 +293,9 @@ export class Writer {
} }
} }
/**
* @_ignore
*/
export class Reader { export class Reader {
// Allows incomplete unpadded data to be read; otherwise an error // Allows incomplete unpadded data to be read; otherwise an error
// is raised if attempting to overrun the buffer. This is required // is raised if attempting to overrun the buffer. This is required

View File

@ -7,6 +7,9 @@ import { Coder } from "./abstract-coder.js";
import type { Reader, Writer } from "./abstract-coder.js"; import type { Reader, Writer } from "./abstract-coder.js";
/**
* @_ignore
*/
export class AddressCoder extends Coder { export class AddressCoder extends Coder {
constructor(localName: string) { constructor(localName: string) {

View File

@ -2,7 +2,11 @@ import { Coder } from "./abstract-coder.js";
import type { Reader, Writer } from "./abstract-coder.js"; import type { Reader, Writer } from "./abstract-coder.js";
// Clones the functionality of an existing Coder, but without a localName /**
* Clones the functionality of an existing Coder, but without a localName
*
* @_ignore
*/
export class AnonymousCoder extends Coder { export class AnonymousCoder extends Coder {
private coder: Coder; private coder: Coder;

View File

@ -9,7 +9,9 @@ import { AnonymousCoder } from "./anonymous.js";
import type { Reader } from "./abstract-coder.js"; import type { Reader } from "./abstract-coder.js";
/**
* @_ignore
*/
export function pack(writer: Writer, coders: ReadonlyArray<Coder>, values: Array<any> | { [ name: string ]: any }): number { export function pack(writer: Writer, coders: ReadonlyArray<Coder>, values: Array<any> | { [ name: string ]: any }): number {
let arrayValues: Array<any> = [ ]; let arrayValues: Array<any> = [ ];
@ -71,6 +73,9 @@ export function pack(writer: Writer, coders: ReadonlyArray<Coder>, values: Array
return length; return length;
} }
/**
* @_ignore
*/
export function unpack(reader: Reader, coders: ReadonlyArray<Coder>): Result { export function unpack(reader: Reader, coders: ReadonlyArray<Coder>): Result {
let values: Array<any> = []; let values: Array<any> = [];
let keys: Array<null | string> = [ ]; let keys: Array<null | string> = [ ];
@ -125,7 +130,9 @@ export function unpack(reader: Reader, coders: ReadonlyArray<Coder>): Result {
return Result.fromItems(values, keys); return Result.fromItems(values, keys);
} }
/**
* @_ignore
*/
export class ArrayCoder extends Coder { export class ArrayCoder extends Coder {
readonly coder!: Coder; readonly coder!: Coder;
readonly length!: number; readonly length!: number;

View File

@ -3,7 +3,9 @@ import { Coder } from "./abstract-coder.js";
import type { Reader, Writer } from "./abstract-coder.js"; import type { Reader, Writer } from "./abstract-coder.js";
/**
* @_ignore
*/
export class BooleanCoder extends Coder { export class BooleanCoder extends Coder {
constructor(localName: string) { constructor(localName: string) {

View File

@ -5,6 +5,9 @@ import { Coder } from "./abstract-coder.js";
import type { Reader, Writer } from "./abstract-coder.js"; import type { Reader, Writer } from "./abstract-coder.js";
/**
* @_ignore
*/
export class DynamicBytesCoder extends Coder { export class DynamicBytesCoder extends Coder {
constructor(type: string, localName: string) { constructor(type: string, localName: string) {
super(type, type, localName, true); super(type, type, localName, true);
@ -26,6 +29,9 @@ export class DynamicBytesCoder extends Coder {
} }
} }
/**
* @_ignore
*/
export class BytesCoder extends DynamicBytesCoder { export class BytesCoder extends DynamicBytesCoder {
constructor(localName: string) { constructor(localName: string) {
super("bytes", localName); super("bytes", localName);

View File

@ -9,6 +9,9 @@ import type { BytesLike } from "../../utils/index.js";
import type { Reader, Writer } from "./abstract-coder.js"; import type { Reader, Writer } from "./abstract-coder.js";
/**
* @_ignore
*/
export class FixedBytesCoder extends Coder { export class FixedBytesCoder extends Coder {
readonly size!: number; readonly size!: number;

View File

@ -3,6 +3,9 @@ import type { Reader, Writer } from "./abstract-coder.js";
const Empty = new Uint8Array([ ]); const Empty = new Uint8Array([ ]);
/**
* @_ignore
*/
export class NullCoder extends Coder { export class NullCoder extends Coder {
constructor(localName: string) { constructor(localName: string) {

View File

@ -14,6 +14,9 @@ const BN_0 = BigInt(0);
const BN_1 = BigInt(1); const BN_1 = BigInt(1);
const BN_MAX_UINT256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); const BN_MAX_UINT256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
/**
* @_ignore
*/
export class NumberCoder extends Coder { export class NumberCoder extends Coder {
readonly size!: number; readonly size!: number;
readonly signed!: boolean; readonly signed!: boolean;

View File

@ -6,6 +6,9 @@ import { DynamicBytesCoder } from "./bytes.js";
import type { Reader, Writer } from "./abstract-coder.js"; import type { Reader, Writer } from "./abstract-coder.js";
/**
* @_ignore
*/
export class StringCoder extends DynamicBytesCoder { export class StringCoder extends DynamicBytesCoder {
constructor(localName: string) { constructor(localName: string) {

View File

@ -7,6 +7,9 @@ import { pack, unpack } from "./array.js";
import type { Reader, Writer } from "./abstract-coder.js"; import type { Reader, Writer } from "./abstract-coder.js";
/**
* @_ignore
*/
export class TupleCoder extends Coder { export class TupleCoder extends Coder {
readonly coders!: ReadonlyArray<Coder>; readonly coders!: ReadonlyArray<Coder>;

View File

@ -1,3 +1,9 @@
/**
* About frgaments...
*
* @_subsection api/abi/abi-coder:Fragments
*/
import { import {
defineProperties, getBigInt, getNumber, defineProperties, getBigInt, getNumber,
assert, assertPrivate, assertArgument assert, assertPrivate, assertArgument
@ -460,6 +466,9 @@ export class ParamType {
readonly arrayChildren!: null | ParamType; readonly arrayChildren!: null | ParamType;
/**
* @private
*/
constructor(guard: any, name: string, type: string, baseType: string, indexed: null | boolean, components: null | ReadonlyArray<ParamType>, arrayLength: null | number, arrayChildren: null | ParamType) { constructor(guard: any, name: string, type: string, baseType: string, indexed: null | boolean, components: null | ReadonlyArray<ParamType>, arrayLength: null | number, arrayChildren: null | ParamType) {
assertPrivate(guard, _guard, "ParamType"); assertPrivate(guard, _guard, "ParamType");
Object.defineProperty(this, internal, { value: ParamTypeInternal }); Object.defineProperty(this, internal, { value: ParamTypeInternal });
@ -489,7 +498,8 @@ export class ParamType {
// - sighash: "(uint256,address)" // - sighash: "(uint256,address)"
// - minimal: "tuple(uint256,address) indexed" // - minimal: "tuple(uint256,address) indexed"
// - full: "tuple(uint256 foo, address bar) indexed baz" // - full: "tuple(uint256 foo, address bar) indexed baz"
format(format: FormatType = "sighash"): string { format(format?: FormatType): string {
if (format == null) { format = "sighash"; }
if (format === "json") { if (format === "json") {
let result: any = { let result: any = {
type: ((this.baseType === "tuple") ? "tuple": this.type), type: ((this.baseType === "tuple") ? "tuple": this.type),
@ -629,7 +639,7 @@ export class ParamType {
} }
} }
async walkAsync(value: any, process: (type: string, value: any) => any | Promise<any>): Promise<any> { async walkAsync(value: any, process: FragmentWalkAsyncFunc): Promise<any> {
const promises: Array<Promise<void>> = [ ]; const promises: Array<Promise<void>> = [ ];
const result: [ any ] = [ value ]; const result: [ any ] = [ value ];
this.#walkAsync(promises, value, process, (value: any) => { this.#walkAsync(promises, value, process, (value: any) => {
@ -733,6 +743,9 @@ export abstract class Fragment {
readonly type!: FragmentType; readonly type!: FragmentType;
readonly inputs!: ReadonlyArray<ParamType>; readonly inputs!: ReadonlyArray<ParamType>;
/**
* @private
*/
constructor(guard: any, type: FragmentType, inputs: ReadonlyArray<ParamType>) { constructor(guard: any, type: FragmentType, inputs: ReadonlyArray<ParamType>) {
assertPrivate(guard, _guard, "Fragment"); assertPrivate(guard, _guard, "Fragment");
inputs = Object.freeze(inputs.slice()); inputs = Object.freeze(inputs.slice());
@ -802,6 +815,9 @@ export abstract class Fragment {
export abstract class NamedFragment extends Fragment { export abstract class NamedFragment extends Fragment {
readonly name!: string; readonly name!: string;
/**
* @private
*/
constructor(guard: any, type: FragmentType, name: string, inputs: ReadonlyArray<ParamType>) { constructor(guard: any, type: FragmentType, name: string, inputs: ReadonlyArray<ParamType>) {
super(guard, type, inputs); super(guard, type, inputs);
assertArgument(typeof(name) === "string" && name.match(regexIdentifier), assertArgument(typeof(name) === "string" && name.match(regexIdentifier),
@ -816,6 +832,9 @@ function joinParams(format: FormatType, params: ReadonlyArray<ParamType>): strin
} }
export class ErrorFragment extends NamedFragment { export class ErrorFragment extends NamedFragment {
/**
* @private
*/
constructor(guard: any, name: string, inputs: ReadonlyArray<ParamType>) { constructor(guard: any, name: string, inputs: ReadonlyArray<ParamType>) {
super(guard, "error", name, inputs); super(guard, "error", name, inputs);
Object.defineProperty(this, internal, { value: ErrorFragmentInternal }); Object.defineProperty(this, internal, { value: ErrorFragmentInternal });
@ -825,7 +844,8 @@ export class ErrorFragment extends NamedFragment {
return id(this.format("sighash")).substring(0, 10); return id(this.format("sighash")).substring(0, 10);
} }
format(format: FormatType = "sighash"): string { format(format?: FormatType): string {
if (format == null) { format = "sighash"; }
if (format === "json") { if (format === "json") {
return JSON.stringify({ return JSON.stringify({
type: "error", type: "error",
@ -867,6 +887,9 @@ export class ErrorFragment extends NamedFragment {
export class EventFragment extends NamedFragment { export class EventFragment extends NamedFragment {
readonly anonymous!: boolean; readonly anonymous!: boolean;
/**
* @private
*/
constructor(guard: any, name: string, inputs: ReadonlyArray<ParamType>, anonymous: boolean) { constructor(guard: any, name: string, inputs: ReadonlyArray<ParamType>, anonymous: boolean) {
super(guard, "event", name, inputs); super(guard, "event", name, inputs);
Object.defineProperty(this, internal, { value: EventFragmentInternal }); Object.defineProperty(this, internal, { value: EventFragmentInternal });
@ -877,7 +900,8 @@ export class EventFragment extends NamedFragment {
return id(this.format("sighash")); return id(this.format("sighash"));
} }
format(format: FormatType = "sighash"): string { format(format?: FormatType): string {
if (format == null) { format = "sighash"; }
if (format === "json") { if (format === "json") {
return JSON.stringify({ return JSON.stringify({
type: "event", type: "event",
@ -923,14 +947,17 @@ export class ConstructorFragment extends Fragment {
readonly payable!: boolean; readonly payable!: boolean;
readonly gas!: null | bigint; readonly gas!: null | bigint;
/**
* @private
*/
constructor(guard: any, type: FragmentType, inputs: ReadonlyArray<ParamType>, payable: boolean, gas: null | bigint) { constructor(guard: any, type: FragmentType, inputs: ReadonlyArray<ParamType>, payable: boolean, gas: null | bigint) {
super(guard, type, inputs); super(guard, type, inputs);
Object.defineProperty(this, internal, { value: ConstructorFragmentInternal }); Object.defineProperty(this, internal, { value: ConstructorFragmentInternal });
defineProperties<ConstructorFragment>(this, { payable, gas }); defineProperties<ConstructorFragment>(this, { payable, gas });
} }
format(format: FormatType = "sighash"): string { format(format?: FormatType): string {
assert(format !== "sighash", "cannot format a constructor for sighash", assert(format != null && format !== "sighash", "cannot format a constructor for sighash",
"UNSUPPORTED_OPERATION", { operation: "format(sighash)" }); "UNSUPPORTED_OPERATION", { operation: "format(sighash)" });
if (format === "json") { if (format === "json") {
@ -983,6 +1010,9 @@ export class FunctionFragment extends NamedFragment {
readonly payable!: boolean; readonly payable!: boolean;
readonly gas!: null | bigint; readonly gas!: null | bigint;
/**
* @private
*/
constructor(guard: any, name: string, stateMutability: string, inputs: ReadonlyArray<ParamType>, outputs: ReadonlyArray<ParamType>, gas: null | bigint) { constructor(guard: any, name: string, stateMutability: string, inputs: ReadonlyArray<ParamType>, outputs: ReadonlyArray<ParamType>, gas: null | bigint) {
super(guard, "function", name, inputs); super(guard, "function", name, inputs);
Object.defineProperty(this, internal, { value: FunctionFragmentInternal }); Object.defineProperty(this, internal, { value: FunctionFragmentInternal });
@ -996,7 +1026,8 @@ export class FunctionFragment extends NamedFragment {
return id(this.format("sighash")).substring(0, 10); return id(this.format("sighash")).substring(0, 10);
} }
format(format: FormatType = "sighash"): string { format(format?: FormatType): string {
if (format == null) { format = "sighash"; }
if (format === "json") { if (format === "json") {
return JSON.stringify({ return JSON.stringify({
type: "function", type: "function",
@ -1068,6 +1099,10 @@ export class FunctionFragment extends NamedFragment {
} }
export class StructFragment extends NamedFragment { export class StructFragment extends NamedFragment {
/**
* @private
*/
constructor(guard: any, name: string, inputs: ReadonlyArray<ParamType>) { constructor(guard: any, name: string, inputs: ReadonlyArray<ParamType>) {
super(guard, "struct", name, inputs); super(guard, "struct", name, inputs);
Object.defineProperty(this, internal, { value: StructFragmentInternal }); Object.defineProperty(this, internal, { value: StructFragmentInternal });

View File

@ -1,12 +1,12 @@
/**
* Explain about ABI here...
*
* @_section api/abi:Application Binary Interface [abi]
*/
////// //////
export { export { AbiCoder } from "./abi-coder.js";
AbiCoder,
defaultAbiCoder,
getBuiltinCallException
} from "./abi-coder.js";
export { decodeBytes32String, encodeBytes32String } from "./bytes32.js"; export { decodeBytes32String, encodeBytes32String } from "./bytes32.js";

View File

@ -1,3 +1,9 @@
/**
* About Interface
*
* @_subsection api/abi:Interfaces [interfaces]
*/
import { keccak256 } from "../crypto/index.js" import { keccak256 } from "../crypto/index.js"
import { id } from "../hash/index.js" import { id } from "../hash/index.js"
import { import {
@ -6,7 +12,7 @@ import {
assert assert
} from "../utils/index.js"; } from "../utils/index.js";
import { AbiCoder, defaultAbiCoder, getBuiltinCallException } from "./abi-coder.js"; import { AbiCoder } from "./abi-coder.js";
import { checkResultErrors, Result } from "./coders/abstract-coder.js"; import { checkResultErrors, Result } from "./coders/abstract-coder.js";
import { ConstructorFragment, ErrorFragment, EventFragment, Fragment, FunctionFragment, ParamType } from "./fragments.js"; import { ConstructorFragment, ErrorFragment, EventFragment, Fragment, FunctionFragment, ParamType } from "./fragments.js";
import { Typed } from "./typed.js"; import { Typed } from "./typed.js";
@ -144,9 +150,23 @@ function checkNames(fragment: Fragment, type: "input" | "output", params: Array<
//export type AbiCoder = any; //export type AbiCoder = any;
//const defaultAbiCoder: AbiCoder = { }; //const defaultAbiCoder: AbiCoder = { };
/**
* @TODO
*/
export type InterfaceAbi = string | ReadonlyArray<Fragment | JsonFragment | string>; export type InterfaceAbi = string | ReadonlyArray<Fragment | JsonFragment | string>;
/**
* An Interface abstracts many of the low-level details for
* encoding and decoding the data on the blockchain.
*
* An ABI provides information on how to encode data to send to
* a Contract, how to decode the results and events and how to
* interpret revert errors.
*
* The ABI can be specified by [any supported format](InterfaceAbi).
*/
export class Interface { export class Interface {
/** /**
* All the Contract ABI members (i.e. methods, events, errors, etc). * All the Contract ABI members (i.e. methods, events, errors, etc).
*/ */
@ -164,6 +184,9 @@ export class Interface {
#abiCoder: AbiCoder; #abiCoder: AbiCoder;
/**
* Create a new Interface for the %%fragments%%.
*/
constructor(fragments: InterfaceAbi) { constructor(fragments: InterfaceAbi) {
let abi: ReadonlyArray<Fragment | JsonFragment | string> = [ ]; let abi: ReadonlyArray<Fragment | JsonFragment | string> = [ ];
if (typeof(fragments) === "string") { if (typeof(fragments) === "string") {
@ -267,7 +290,7 @@ export class Interface {
* data. * data.
*/ */
getAbiCoder(): AbiCoder { getAbiCoder(): AbiCoder {
return defaultAbiCoder; return AbiCoder.defaultAbiCoder();
} }
// Find a function definition by any means necessary (unless it is ambiguous) // Find a function definition by any means necessary (unless it is ambiguous)
@ -661,7 +684,7 @@ export class Interface {
makeError(_data: BytesLike, tx: CallExceptionTransaction): CallExceptionError { makeError(_data: BytesLike, tx: CallExceptionTransaction): CallExceptionError {
const data = getBytes(_data, "data"); const data = getBytes(_data, "data");
const error = getBuiltinCallException("call", tx, data); const error = AbiCoder.getBuiltinCallException("call", tx, data);
// Not a built-in error; try finding a custom error // Not a built-in error; try finding a custom error
if (!error.message.match(/could not decode/)) { if (!error.message.match(/could not decode/)) {
@ -980,7 +1003,7 @@ export class Interface {
* The %%value%% may be provided as an existing [[Interface]] object, * The %%value%% may be provided as an existing [[Interface]] object,
* a JSON-encoded ABI or any Human-Readable ABI format. * a JSON-encoded ABI or any Human-Readable ABI format.
*/ */
static from(value: ReadonlyArray<Fragment | string | JsonFragment> | string | Interface): Interface { static from(value: InterfaceAbi | Interface): Interface {
// Already an Interface, which is immutable // Already an Interface, which is immutable
if (value instanceof Interface) { return value; } if (value instanceof Interface) { return value; }

View File

@ -1,3 +1,9 @@
/**
* About typed...
*
* @_subsection: api/abi:Typed Values
*/
import { assertPrivate, defineProperties } from "../utils/index.js"; import { assertPrivate, defineProperties } from "../utils/index.js";
import type { Addressable } from "../address/index.js"; import type { Addressable } from "../address/index.js";
@ -24,22 +30,26 @@ function b(value: BytesLike, size?: number): Typed {
} }
export interface TypedNumber extends Typed { export interface TypedNumber extends Typed {
value: number;
defaultValue(): number; defaultValue(): number;
minValue(): number; minValue(): number;
maxValue(): number; maxValue(): number;
} }
export interface TypedBigInt extends Typed { export interface TypedBigInt extends Typed {
value: bigint;
defaultValue(): bigint; defaultValue(): bigint;
minValue(): bigint; minValue(): bigint;
maxValue(): bigint; maxValue(): bigint;
} }
export interface TypedData extends Typed { export interface TypedData extends Typed {
value: string;
defaultValue(): string; defaultValue(): string;
} }
export interface TypedString extends Typed { export interface TypedString extends Typed {
value: string;
defaultValue(): string; defaultValue(): string;
} }
@ -53,7 +63,8 @@ export class Typed {
readonly _typedSymbol!: Symbol; readonly _typedSymbol!: Symbol;
constructor(gaurd: any, type: string, value: any, options: any = null) { constructor(gaurd: any, type: string, value: any, options?: any) {
if (options == null) { options = null; }
assertPrivate(_gaurd, gaurd, "Typed"); assertPrivate(_gaurd, gaurd, "Typed");
defineProperties<Typed>(this, { _typedSymbol, type, value }); defineProperties<Typed>(this, { _typedSymbol, type, value });
this.#options = options; this.#options = options;
@ -238,10 +249,20 @@ export class Typed {
return new Typed(_gaurd, "overrides", Object.assign({ }, v)); return new Typed(_gaurd, "overrides", Object.assign({ }, v));
} }
/**
* Returns true only if %%value%% is a [[Typed]] instance.
*/
static isTyped(value: any): value is Typed { static isTyped(value: any): value is Typed {
return (value && value._typedSymbol === _typedSymbol); return (value && value._typedSymbol === _typedSymbol);
} }
/**
* If the value is a [[Typed]] instance, validates the underlying value
* and returns it, otherwise returns value directly.
*
* This is useful for functions that with to accept either a [[Typed]]
* object or values.
*/
static dereference<T>(value: Typed | T, type: string): T { static dereference<T>(value: Typed | T, type: string): T {
if (Typed.isTyped(value)) { if (Typed.isTyped(value)) {
if (value.type !== type) { if (value.type !== type) {