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
import { assertArgumentCount, assertArgument } from "../utils/index.js";
@ -22,10 +35,92 @@ import type {
CallExceptionAction, CallExceptionError, CallExceptionTransaction
} 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 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 {
#getCoder(param: ParamType): Coder {
@ -70,12 +165,23 @@ export class AbiCoder {
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 {
const coders: Array<Coder> = types.map((type) => this.#getCoder(ParamType.from(type)));
const coder = new TupleCoder(coders, "_");
return coder.defaultValue();
}
/**
* Encode the %%values%% as the %%types%% into ABI data.
*
* @returns DataHexstring
*/
encode(types: ReadonlyArray<string | ParamType>, values: ReadonlyArray<any>): string {
assertArgumentCount(values.length, types.length, "types/values length mismatch");
@ -87,87 +193,37 @@ export class AbiCoder {
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 {
const coders: Array<Coder> = types.map((type) => this.#getCoder(ParamType.from(type)));
const coder = new TupleCoder(coders, "_");
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();
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");
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)";
/**
* Returns the shared singleton instance of a default [[AbiCoder]].
*
* On the first call, the instance is created internally.
*/
static defaultAbiCoder(): AbiCoder {
if (defaultCoder == null) {
defaultCoder = new AbiCoder();
}
return defaultCoder;
}
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
});
/**
* Returns an ethers-compatible [[CALL_EXCEPTION]] Error for the given
* result %%data%% for the [[CallExceptionAction]] %%action%% against
* the Transaction %%tx%%.
*/
static getBuiltinCallException(action: CallExceptionAction, tx: { to?: null | string, from?: null | string, data?: string }, data: null | BytesLike): CallExceptionError {
return getBuiltinCallException(action, tx, data, AbiCoder.defaultAbiCoder());
}
}
export const defaultAbiCoder: AbiCoder = new AbiCoder();

View File

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

View File

@ -7,7 +7,7 @@ import {
import type { BigNumberish, BytesLike } from "../../utils/index.js";
export const WordSize = 32;
export const WordSize: number = 32;
const Padding = new Uint8Array(WordSize);
// Properties used to immediate pass through to the underlying object
@ -16,11 +16,21 @@ const passProperties = [ "then" ];
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> {
#indices: Map<string, Array<number>>;
[ K: string | number ]: any
/**
* @private
*/
constructor(guard: any, items: Array<any>, keys?: Array<null | string>) {
assertPrivate(guard, _guard, "Result");
super(...items);
@ -85,6 +95,9 @@ export class Result extends Array<any> {
}
*/
/**
* @_ignore
*/
slice(start?: number | undefined, end?: number | undefined): Array<any> {
if (start == null) { start = 0; }
if (end == null) { end = this.length; }
@ -108,6 +121,14 @@ export class Result extends Array<any> {
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 {
const index = this.#indices.get(name);
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) }`);
}
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);
}
}
/**
* 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 }> {
// Find the first error (if any)
const errors: Array<{ path: Array<string | number>, error: Error }> = [ ];
@ -162,7 +200,9 @@ function getValue(value: BigNumberish): Uint8Array {
return bytes;
}
/**
* @_ignore
*/
export abstract class Coder {
// The coder name:
@ -198,6 +238,9 @@ export abstract class Coder {
abstract defaultValue(): any;
}
/**
* @_ignore
*/
export class Writer {
// An array of WordSize lengthed objects to concatenation
#data: Array<Uint8Array>;
@ -250,6 +293,9 @@ export class Writer {
}
}
/**
* @_ignore
*/
export class Reader {
// Allows incomplete unpadded data to be read; otherwise an error
// 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";
/**
* @_ignore
*/
export class AddressCoder extends Coder {
constructor(localName: string) {

View File

@ -2,7 +2,11 @@ import { Coder } 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 {
private coder: Coder;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
/**
* Explain about ABI here...
*
* @_section api/abi:Application Binary Interface [abi]
*/
//////
export {
AbiCoder,
defaultAbiCoder,
getBuiltinCallException
} from "./abi-coder.js";
export { AbiCoder } from "./abi-coder.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 { id } from "../hash/index.js"
import {
@ -6,7 +12,7 @@ import {
assert
} 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 { ConstructorFragment, ErrorFragment, EventFragment, Fragment, FunctionFragment, ParamType } from "./fragments.js";
import { Typed } from "./typed.js";
@ -144,9 +150,23 @@ function checkNames(fragment: Fragment, type: "input" | "output", params: Array<
//export type AbiCoder = any;
//const defaultAbiCoder: AbiCoder = { };
/**
* @TODO
*/
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 {
/**
* All the Contract ABI members (i.e. methods, events, errors, etc).
*/
@ -164,6 +184,9 @@ export class Interface {
#abiCoder: AbiCoder;
/**
* Create a new Interface for the %%fragments%%.
*/
constructor(fragments: InterfaceAbi) {
let abi: ReadonlyArray<Fragment | JsonFragment | string> = [ ];
if (typeof(fragments) === "string") {
@ -267,7 +290,7 @@ export class Interface {
* data.
*/
getAbiCoder(): AbiCoder {
return defaultAbiCoder;
return AbiCoder.defaultAbiCoder();
}
// 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 {
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
if (!error.message.match(/could not decode/)) {
@ -980,7 +1003,7 @@ export class Interface {
* The %%value%% may be provided as an existing [[Interface]] object,
* 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
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 type { Addressable } from "../address/index.js";
@ -24,22 +30,26 @@ function b(value: BytesLike, size?: number): Typed {
}
export interface TypedNumber extends Typed {
value: number;
defaultValue(): number;
minValue(): number;
maxValue(): number;
}
export interface TypedBigInt extends Typed {
value: bigint;
defaultValue(): bigint;
minValue(): bigint;
maxValue(): bigint;
}
export interface TypedData extends Typed {
value: string;
defaultValue(): string;
}
export interface TypedString extends Typed {
value: string;
defaultValue(): string;
}
@ -53,7 +63,8 @@ export class Typed {
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");
defineProperties<Typed>(this, { _typedSymbol, type, value });
this.#options = options;
@ -238,10 +249,20 @@ export class Typed {
return new Typed(_gaurd, "overrides", Object.assign({ }, v));
}
/**
* Returns true only if %%value%% is a [[Typed]] instance.
*/
static isTyped(value: any): value is Typed {
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 {
if (Typed.isTyped(value)) {
if (value.type !== type) {