diff --git a/src.ts/abi/abi-coder.ts b/src.ts/abi/abi-coder.ts index 89213139b..3e56bd074 100644 --- a/src.ts/abi/abi-coder.ts +++ b/src.ts/abi/abi-coder.ts @@ -1,6 +1,6 @@ // See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI -import { assertArgumentCount, throwArgumentError } from "../utils/index.js"; +import { assertArgumentCount, assertArgument } from "../utils/index.js"; import { Coder, Reader, Result, Writer } from "./coders/abstract-coder.js"; import { AddressCoder } from "./coders/address.js"; @@ -54,9 +54,8 @@ export class AbiCoder { let match = param.type.match(paramTypeNumber); if (match) { let size = parseInt(match[2] || "256"); - if (size === 0 || size > 256 || (size % 8) !== 0) { - throwArgumentError("invalid " + match[1] + " bit length", "param", param); - } + assertArgument(size !== 0 && size <= 256 && (size % 8) === 0, + "invalid " + match[1] + " bit length", "param", param); return new NumberCoder(size / 8, (match[1] === "int"), param.name); } @@ -64,13 +63,11 @@ export class AbiCoder { match = param.type.match(paramTypeBytes); if (match) { let size = parseInt(match[1]); - if (size === 0 || size > 32) { - throwArgumentError("invalid bytes length", "param", param); - } + assertArgument(size !== 0 && size <= 32, "invalid bytes length", "param", param); return new FixedBytesCoder(size, param.name); } - return throwArgumentError("invalid type", "type", param.type); + assertArgument(false, "invalid type", "type", param.type); } getDefaultValue(types: ReadonlyArray): Result { diff --git a/src.ts/abi/coders/abstract-coder.ts b/src.ts/abi/coders/abstract-coder.ts index 79a9c3b2e..c271e19e7 100644 --- a/src.ts/abi/coders/abstract-coder.ts +++ b/src.ts/abi/coders/abstract-coder.ts @@ -2,7 +2,7 @@ import { defineProperties, concat, getBytesCopy, getNumber, hexlify, toArray, toBigInt, toNumber, - assertPrivate, throwArgumentError, throwError + assertPrivate, assertArgument, throwError } from "../../utils/index.js"; import type { BigNumberish, BytesLike } from "../../utils/index.js"; @@ -194,7 +194,7 @@ export abstract class Coder { } _throwError(message: string, value: any): never { - return throwArgumentError(message, this.localName, value); + assertArgument(false, message, this.localName, value); } abstract encode(writer: Writer, value: any): number; diff --git a/src.ts/abi/coders/array.ts b/src.ts/abi/coders/array.ts index 524f9aca0..922b4e728 100644 --- a/src.ts/abi/coders/array.ts +++ b/src.ts/abi/coders/array.ts @@ -1,5 +1,5 @@ import { - defineProperties, isError, assertArgumentCount, throwArgumentError, throwError + defineProperties, isError, assertArgument, assertArgumentCount, throwError } from "../../utils/index.js"; import { Typed } from "../typed.js"; @@ -43,12 +43,10 @@ export function pack(writer: Writer, coders: ReadonlyArray, values: Array }); } else { - throwArgumentError("invalid tuple value", "tuple", values); + assertArgument(false, "invalid tuple value", "tuple", values); } - if (coders.length !== arrayValues.length) { - throwArgumentError("types/value length mismatch", "tuple", values); - } + assertArgument(coders.length === arrayValues.length, "types/value length mismatch", "tuple", values); let staticWriter = new Writer(); let dynamicWriter = new Writer(); diff --git a/src.ts/abi/fragments.ts b/src.ts/abi/fragments.ts index 164962e76..2e5e2aeeb 100644 --- a/src.ts/abi/fragments.ts +++ b/src.ts/abi/fragments.ts @@ -1,6 +1,6 @@ import { defineProperties, getBigInt, getNumber, - assertPrivate, throwArgumentError, throwError + assertPrivate, assertArgument, throwError } from "../utils/index.js"; import { id } from "../hash/index.js"; @@ -401,25 +401,19 @@ const regexArrayType = new RegExp(/^(.*)\[([0-9]*)\]$/); function verifyBasicType(type: string): string { const match = type.match(regexType); - if (!match) { - return throwArgumentError("invalid type", "type", type); - } + assertArgument(match, "invalid type", "type", type); if (type === "uint") { return "uint256"; } if (type === "int") { return "int256"; } if (match[2]) { // bytesXX const length = parseInt(match[2]); - if (length === 0 || length > 32) { - throwArgumentError("invalid bytes length", "type", type); - } + assertArgument(length !== 0 && length <= 32, "invalid bytes length", "type", type); } else if (match[3]) { // intXX or uintXX const size = parseInt(match[3] as string); - if (size === 0 || size > 256 || size % 8) { - throwArgumentError("invalid numeric width", "type", type); - } + assertArgument(size !== 0 && size <= 256 && (size % 8) === 0, "invalid numeric width", "type", type); } return type; @@ -694,15 +688,12 @@ export class ParamType { } const name = obj.name; - if (name && (typeof(name) !== "string" || !name.match(regexIdentifier))) { - throwArgumentError("invalid name", "obj.name", name); - } + assertArgument(!name || (typeof(name) === "string" && name.match(regexIdentifier)), + "invalid name", "obj.name", name); let indexed = obj.indexed; if (indexed != null) { - if (!allowIndexed) { - throwArgumentError("parameter cannot be indexed", "obj.indexed", obj.indexed); - } + assertArgument(allowIndexed, "parameter cannot be indexed", "obj.indexed", obj.indexed); indexed = !!indexed; } @@ -813,9 +804,8 @@ export abstract class NamedFragment extends Fragment { constructor(guard: any, type: FragmentType, name: string, inputs: ReadonlyArray) { super(guard, type, inputs); - if (typeof(name) !== "string" || !name.match(regexIdentifier)) { - throwArgumentError("invalid identifier", "name", name); - } + assertArgument(typeof(name) === "string" && name.match(regexIdentifier), + "invalid identifier", "name", name); inputs = Object.freeze(inputs.slice()); defineProperties(this, { name }); } diff --git a/src.ts/abi/interface.ts b/src.ts/abi/interface.ts index 4e3aa9787..0ec607a4a 100644 --- a/src.ts/abi/interface.ts +++ b/src.ts/abi/interface.ts @@ -2,7 +2,7 @@ import { keccak256 } from "../crypto/index.js" import { id } from "../hash/index.js" import { concat, dataSlice, getBigInt, getBytes, getBytesCopy, - hexlify, zeroPadValue, isHexString, defineProperties, throwArgumentError, toHex, + hexlify, zeroPadValue, isHexString, defineProperties, assertArgument, toHex, throwError } from "../utils/index.js"; @@ -279,7 +279,7 @@ export class Interface { for (const fragment of this.#functions.values()) { if (selector === fragment.selector) { return fragment; } } - throwArgumentError("no matching function", "selector", key); + assertArgument(false, "no matching function", "selector", key); } // It is a bare name, look up the function (will return null if ambiguous) @@ -340,12 +340,11 @@ export class Interface { } } - if (matching.length === 0) { - throwArgumentError("no matching function", "name", key); + assertArgument(matching.length !== 0, "no matching function", "name", key); - } else if (matching.length > 1 && forceUnique) { + if (matching.length > 1 && forceUnique) { const matchStr = matching.map((m) => JSON.stringify(m.format())).join(", "); - throwArgumentError(`multiple matching functions (i.e. ${ matchStr })`, "name", key); + assertArgument(false, `multiple matching functions (i.e. ${ matchStr })`, "name", key); } return matching[0]; @@ -355,7 +354,7 @@ export class Interface { const result = this.#functions.get(FunctionFragment.from(key).format()); if (result) { return result; } - return throwArgumentError("no matching function", "signature", key); + assertArgument(false, "no matching function", "signature", key); } /** @@ -390,7 +389,7 @@ export class Interface { for (const fragment of this.#events.values()) { if (eventTopic === fragment.topicHash) { return fragment; } } - throwArgumentError("no matching event", "eventTopic", key); + assertArgument(false, "no matching event", "eventTopic", key); } // It is a bare name, look up the function (will return null if ambiguous) @@ -424,11 +423,10 @@ export class Interface { } } - if (matching.length === 0) { - throwArgumentError("no matching event", "name", key); - } else if (matching.length > 1 && forceUnique) { + assertArgument(matching.length > 0, "no matching event", "name", key); + if (matching.length > 1 && forceUnique) { // @TODO: refine by Typed - throwArgumentError("multiple matching events", "name", key); + assertArgument(false, "multiple matching events", "name", key); } return matching[0]; @@ -438,7 +436,7 @@ export class Interface { const result = this.#events.get(EventFragment.from(key).format()); if (result) { return result; } - return throwArgumentError("no matching event", "signature", key); + assertArgument(false, "no matching event", "signature", key); } /** @@ -484,7 +482,7 @@ export class Interface { for (const fragment of this.#errors.values()) { if (selector === fragment.selector) { return fragment; } } - throwArgumentError("no matching error", "selector", key); + assertArgument(false, "no matching error", "selector", key); } // It is a bare name, look up the function (will return null if ambiguous) @@ -497,10 +495,10 @@ export class Interface { if (matching.length === 0) { if (key === "Error") { return ErrorFragment.from("error Error(string)"); } if (key === "Panic") { return ErrorFragment.from("error Panic(uint256)"); } - throwArgumentError("no matching error", "name", key); + assertArgument(false, "no matching error", "name", key); } else if (matching.length > 1) { // @TODO: refine by Typed - throwArgumentError("multiple matching errors", "name", key); + assertArgument(false, "multiple matching errors", "name", key); } return matching[0]; @@ -514,7 +512,7 @@ export class Interface { const result = this.#errors.get(key); if (result) { return result; } - return throwArgumentError("no matching error", "signature", key); + assertArgument(false, "no matching error", "signature", key); } // Get the 4-byte selector used by Solidity to identify a function @@ -576,9 +574,8 @@ export class Interface { decodeErrorResult(fragment: ErrorFragment | string, data: BytesLike): Result { if (typeof(fragment) === "string") { fragment = this.getError(fragment); } - if (dataSlice(data, 0, 4) !== fragment.selector) { - throwArgumentError(`data signature does not match error ${ fragment.name }.`, "data", data); - } + assertArgument(dataSlice(data, 0, 4) === fragment.selector, + `data signature does not match error ${ fragment.name }.`, "data", data); return this._decodeParams(fragment.inputs, dataSlice(data, 4)); } @@ -611,9 +608,8 @@ export class Interface { decodeFunctionData(key: FunctionFragment | string, data: BytesLike): Result { const fragment = (typeof(key) === "string") ? this.getFunction(key): key; - if (dataSlice(data, 0, 4) !== fragment.selector) { - throwArgumentError(`data signature does not match function ${ fragment.name }.`, "data", data); - } + assertArgument(dataSlice(data, 0, 4) === fragment.selector, + `data signature does not match function ${ fragment.name }.`, "data", data); return this._decodeParams(fragment.inputs, dataSlice(data, 4)); } @@ -788,16 +784,15 @@ export class Interface { const param = (eventFragment).inputs[index]; if (!param.indexed) { - if (value != null) { - throwArgumentError("cannot filter non-indexed parameters; must be null", ("contract." + param.name), value); - } + assertArgument(value == null, + "cannot filter non-indexed parameters; must be null", ("contract." + param.name), value); return; } if (value == null) { topics.push(null); } else if (param.baseType === "array" || param.baseType === "tuple") { - throwArgumentError("filtering with tuples or arrays not supported", ("contract." + param.name), value); + assertArgument(false, "filtering with tuples or arrays not supported", ("contract." + param.name), value); } else if (Array.isArray(value)) { topics.push(value.map((value) => encodeTopic(param, value))); } else { @@ -827,9 +822,8 @@ export class Interface { topics.push(eventFragment.topicHash); } - if (values.length !== eventFragment.inputs.length) { - throwArgumentError("event arguments/values mismatch", "values", values); - } + assertArgument(values.length !== eventFragment.inputs.length, + "event arguments/values mismatch", "values", values); eventFragment.inputs.forEach((param, index) => { const value = values[index]; @@ -864,9 +858,8 @@ export class Interface { if (topics != null && !eventFragment.anonymous) { const eventTopic = eventFragment.topicHash; - if (!isHexString(topics[0], 32) || topics[0].toLowerCase() !== eventTopic) { - throwArgumentError("fragment/topic mismatch", "topics[0]", topics[0]); - } + assertArgument(isHexString(topics[0], 32) && topics[0].toLowerCase() === eventTopic, + "fragment/topic mismatch", "topics[0]", topics[0]); topics = topics.slice(1); } diff --git a/src.ts/address/address.ts b/src.ts/address/address.ts index e29c46946..b28f63d37 100644 --- a/src.ts/address/address.ts +++ b/src.ts/address/address.ts @@ -1,5 +1,5 @@ import { keccak256 } from "../crypto/index.js"; -import { getBytes, throwArgumentError } from "../utils/index.js"; +import { getBytes, assertArgument } from "../utils/index.js"; const BN_0 = BigInt(0); @@ -83,9 +83,7 @@ function fromBase36(value: string): bigint { export function getAddress(address: string): string { - if (typeof(address) !== "string") { - throwArgumentError("invalid address", "address", address); - } + assertArgument(typeof(address) === "string", "invalid address", "address", address); if (address.match(/^(0x)?[0-9a-fA-F]{40}$/)) { @@ -95,9 +93,8 @@ export function getAddress(address: string): string { const result = getChecksumAddress(address); // It is a checksummed address with a bad checksum - if (address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) && result !== address) { - throwArgumentError("bad address checksum", "address", address); - } + assertArgument(!address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) || result === address, + "bad address checksum", "address", address); return result; } @@ -105,16 +102,14 @@ export function getAddress(address: string): string { // Maybe ICAP? (we only support direct mode) if (address.match(/^XE[0-9]{2}[0-9A-Za-z]{30,31}$/)) { // It is an ICAP address with a bad checksum - if (address.substring(2, 4) !== ibanChecksum(address)) { - throwArgumentError("bad icap checksum", "address", address); - } + assertArgument(address.substring(2, 4) === ibanChecksum(address), "bad icap checksum", "address", address); let result = fromBase36(address.substring(4)).toString(16); while (result.length < 40) { result = "0" + result; } return getChecksumAddress("0x" + result); } - return throwArgumentError("invalid address", "address", address); + assertArgument(false, "invalid address", "address", address); } export function getIcapAddress(address: string): string { diff --git a/src.ts/address/checks.ts b/src.ts/address/checks.ts index 962ad9151..75223298f 100644 --- a/src.ts/address/checks.ts +++ b/src.ts/address/checks.ts @@ -1,4 +1,4 @@ -import { throwArgumentError, throwError } from "../utils/index.js"; +import { assertArgument, throwError } from "../utils/index.js"; import { getAddress } from "./address.js"; @@ -23,7 +23,7 @@ async function checkAddress(target: any, promise: Promise): Promi if (typeof(target) === "string") { return throwError("unconfigured name", "UNCONFIGURED_NAME", { value: target }); } - return throwArgumentError("invalid AddressLike value; did not resolve to a value address", "target", target); + assertArgument(false, "invalid AddressLike value; did not resolve to a value address", "target", target); } return getAddress(result); } @@ -50,5 +50,5 @@ export function resolveAddress(target: AddressLike, resolver?: null | NameResolv return checkAddress(target, target); } - return throwArgumentError("unsupported addressable value", "target", target); + assertArgument(false, "unsupported addressable value", "target", target); } diff --git a/src.ts/address/contract-address.ts b/src.ts/address/contract-address.ts index 1c84ee453..79576939f 100644 --- a/src.ts/address/contract-address.ts +++ b/src.ts/address/contract-address.ts @@ -1,6 +1,6 @@ import { keccak256 } from "../crypto/index.js"; import { - concat, dataSlice, getBigInt, getBytes, encodeRlp, throwArgumentError + concat, dataSlice, getBigInt, getBytes, encodeRlp, assertArgument } from "../utils/index.js"; import { getAddress } from "./address.js"; @@ -30,13 +30,9 @@ export function getCreate2Address(_from: string, _salt: BytesLike, _initCodeHash const salt = getBytes(_salt, "salt"); const initCodeHash = getBytes(_initCodeHash, "initCodeHash"); - if (salt.length !== 32) { - throwArgumentError("salt must be 32 bytes", "salt", _salt); - } + assertArgument(salt.length === 32, "salt must be 32 bytes", "salt", _salt); - if (initCodeHash.length !== 32) { - throwArgumentError("initCodeHash must be 32 bytes", "initCodeHash", _initCodeHash); - } + assertArgument(initCodeHash.length === 32, "initCodeHash must be 32 bytes", "initCodeHash", _initCodeHash); return getAddress(dataSlice(keccak256(concat([ "0xff", from, salt, initCodeHash ])), 12)) } diff --git a/src.ts/contract/contract.ts b/src.ts/contract/contract.ts index 9e985b561..f9fb6ab4e 100644 --- a/src.ts/contract/contract.ts +++ b/src.ts/contract/contract.ts @@ -3,7 +3,7 @@ import { resolveAddress } from "../address/index.js"; import { copyRequest, Log, TransactionResponse } from "../providers/index.js"; import { defineProperties, isCallException, isHexString, resolveProperties, - makeError, throwArgumentError, throwError + makeError, assertArgument, throwError } from "../utils/index.js"; import { @@ -133,9 +133,9 @@ export async function copyOverrides(arg: any): Promiseoverrides).to) { if (overrides.to) { - throwArgumentError("cannot override to", "overrides.to", overrides.to); + assertArgument(false, "cannot override to", "overrides.to", overrides.to); } else if (overrides.data) { - throwArgumentError("cannot override data", "overrides.data", overrides.data); + assertArgument(false, "cannot override data", "overrides.data", overrides.data); } // Resolve any from diff --git a/src.ts/contract/factory.ts b/src.ts/contract/factory.ts index 6fa20b0d4..6b28b4dbe 100644 --- a/src.ts/contract/factory.ts +++ b/src.ts/contract/factory.ts @@ -3,7 +3,7 @@ import { Interface } from "../abi/index.js"; import { getCreateAddress } from "../address/index.js"; import { concat, defineProperties, getBytes, hexlify, - throwArgumentError, throwError + assertArgument, throwError } from "../utils/index.js"; import { BaseContract, copyOverrides, resolveArgs } from "./contract.js"; @@ -80,9 +80,7 @@ export class ContractFactory = Array, I = BaseContract } static fromSolidity = Array, I = ContractInterface>(output: any, runner?: ContractRunner): ContractFactory { - if (output == null) { - throwArgumentError("bad compiler output", "output", output); - } + assertArgument(output != null, "bad compiler output", "output", output); if (typeof(output) === "string") { output = JSON.parse(output); } diff --git a/src.ts/crypto/crypto-browser.ts b/src.ts/crypto/crypto-browser.ts index 1138b508e..063907c5f 100644 --- a/src.ts/crypto/crypto-browser.ts +++ b/src.ts/crypto/crypto-browser.ts @@ -5,7 +5,7 @@ import { pbkdf2 } from "@noble/hashes/pbkdf2"; import { sha256 } from "@noble/hashes/sha256"; import { sha512 } from "@noble/hashes/sha512"; -import { throwArgumentError, throwError } from "../utils/index.js"; +import { assertArgument, throwError } from "../utils/index.js"; declare global { @@ -37,22 +37,18 @@ export function createHash(algo: string): CryptoHasher { case "sha256": return sha256.create(); case "sha512": return sha512.create(); } - return throwArgumentError("invalid hashing algorithm name", "algorithm", algo); + assertArgument(false, "invalid hashing algorithm name", "algorithm", algo); } export function createHmac(_algo: string, key: Uint8Array): CryptoHasher { const algo = ({ sha256, sha512 }[_algo]); - if (algo == null) { - return throwArgumentError("invalid hmac algorithm", "algorithm", _algo); - } + assertArgument(algo != null, "invalid hmac algorithm", "algorithm", _algo); return hmac.create(algo, key); } export function pbkdf2Sync(password: Uint8Array, salt: Uint8Array, iterations: number, keylen: number, _algo: "sha256" | "sha512"): Uint8Array { const algo = ({ sha256, sha512 }[_algo]); - if (algo == null) { - return throwArgumentError("invalid pbkdf2 algorithm", "algorithm", _algo); - } + assertArgument(algo != null, "invalid pbkdf2 algorithm", "algorithm", _algo); return pbkdf2(algo, password, salt, { c: iterations, dkLen: keylen }); } @@ -63,9 +59,7 @@ export function randomBytes(length: number): Uint8Array { }); } - if (!Number.isInteger(length) || length <= 0 || length > 1024) { - throwArgumentError("invalid length", "length", length); - } + assertArgument(Number.isInteger(length) && length > 0 && length <= 1024, "invalid length", "length", length); const result = new Uint8Array(length); crypto.getRandomValues(result); diff --git a/src.ts/crypto/hmac.ts b/src.ts/crypto/hmac.ts index 19528c201..b85f1ea73 100644 --- a/src.ts/crypto/hmac.ts +++ b/src.ts/crypto/hmac.ts @@ -1,4 +1,4 @@ -import { createHmac } from "./crypto-browser.js"; +import { createHmac } from "./crypto.js"; import { getBytes, hexlify } from "../utils/index.js"; import type { BytesLike } from "../utils/index.js"; diff --git a/src.ts/crypto/signature.ts b/src.ts/crypto/signature.ts index 224f2bba9..7c123b2e4 100644 --- a/src.ts/crypto/signature.ts +++ b/src.ts/crypto/signature.ts @@ -2,7 +2,7 @@ import { ZeroHash } from "../constants/index.js"; import { concat, dataLength, getBigInt, getBytes, getNumber, getStore, hexlify, isHexString, setStore, - assertPrivate, throwArgumentError + assertArgument, assertPrivate } from "../utils/index.js"; import type { @@ -48,26 +48,21 @@ export class Signature implements Freezable { get r(): string { return getStore(this.#props, "r"); } set r(value: BytesLike) { - if (dataLength(value) !== 32) { - throwArgumentError("invalid r", "value", value); - } + assertArgument(dataLength(value) === 32, "invalid r", "value", value); setStore(this.#props, "r", hexlify(value)); } get s(): string { return getStore(this.#props, "s"); } set s(value: BytesLike) { - if (dataLength(value) !== 32) { - throwArgumentError("invalid r", "value", value); - } else if (getBytes(value)[0] & 0x80) { - throwArgumentError("non-canonical s", "value", value); - } + assertArgument(dataLength(value) === 32, "invalid r", "value", value); + assertArgument((getBytes(value)[0] & 0x80) === 0, "non-canonical s", "value", value); setStore(this.#props, "s", hexlify(value)); } get v(): 27 | 28 { return getStore(this.#props, "v"); } set v(value: BigNumberish) { const v = getNumber(value, "value"); - if (v !== 27 && v !== 28) { throw new Error("@TODO"); } + assertArgument(v === 27 || v === 28, "invalid v", "v", value); setStore(this.#props, "v", v); } @@ -130,10 +125,6 @@ export class Signature implements Freezable { }; } - //static create(): Signature { - // return new Signature(_guard, ZeroHash, ZeroHash, 27); - //} - // Get the chain ID from an EIP-155 v static getChainId(v: BigNumberish): bigint { const bv = getBigInt(v, "v"); @@ -142,7 +133,7 @@ export class Signature implements Freezable { if ((bv == BN_27) || (bv == BN_28)) { return BN_0; } // Bad value for an EIP-155 v - if (bv < BN_35) { throwArgumentError("invalid EIP-155 v", "v", v); } + assertArgument(bv >= BN_35, "invalid EIP-155 v", "v", v); return (bv - BN_35) / BN_2; } @@ -164,14 +155,14 @@ export class Signature implements Freezable { } static from(sig?: SignatureLike): Signature { + function assertError(check: unknown, message: string): asserts check { + assertArgument(check, message, "signature", sig); + }; + if (sig == null) { return new Signature(_guard, ZeroHash, ZeroHash, 27); } - const throwError = (message: string) => { - return throwArgumentError(message, "signature", sig); - }; - if (typeof(sig) === "string") { const bytes = getBytes(sig, "signature"); if (bytes.length === 64) { @@ -185,38 +176,38 @@ export class Signature implements Freezable { if (bytes.length === 65) { const r = hexlify(bytes.slice(0, 32)); const s = bytes.slice(32, 64); - if (s[0] & 0x80) { throwError("non-canonical s"); } + assertError((s[0] & 0x80) === 0, "non-canonical s"); const v = Signature.getNormalizedV(bytes[64]); return new Signature(_guard, r, hexlify(s), v); } - return throwError("invlaid raw signature length"); + assertError(false, "invlaid raw signature length"); } if (sig instanceof Signature) { return sig.clone(); } // Get r const r = sig.r; - if (r == null) { throwError("missing r"); } - if (!isHexString(r, 32)) { throwError("invalid r"); } + assertError(r != null, "missing r"); + assertError(isHexString(r, 32), "invalid r"); // Get s; by any means necessary (we check consistency below) const s = (function(s?: string, yParityAndS?: string) { if (s != null) { - if (!isHexString(s, 32)) { throwError("invalid s"); } + assertError(isHexString(s, 32), "invalid s"); return s; } if (yParityAndS != null) { - if (!isHexString(yParityAndS, 32)) { throwError("invalid yParityAndS"); } + assertError(isHexString(yParityAndS, 32), "invalid yParityAndS"); const bytes = getBytes(yParityAndS); bytes[0] &= 0x7f; return hexlify(bytes); } - return throwError("missing s"); + assertError(false, "missing s"); })(sig.s, sig.yParityAndS); - if (getBytes(s)[0] & 0x80) { throwError("non-canonical s"); } + assertError((getBytes(s)[0] & 0x80) == 0, "non-canonical s"); // Get v; by any means necessary (we check consistency below) const { networkV, v } = (function(_v?: BigNumberish, yParityAndS?: string, yParity?: number): { networkV?: bigint, v: 27 | 28 } { @@ -229,7 +220,7 @@ export class Signature implements Freezable { } if (yParityAndS != null) { - if (!isHexString(yParityAndS, 32)) { throwError("invalid yParityAndS"); } + assertError(isHexString(yParityAndS, 32), "invalid yParityAndS"); return { v: ((getBytes(yParityAndS)[0] & 0x80) ? 28: 27) }; } @@ -238,21 +229,18 @@ export class Signature implements Freezable { case 0: return { v: 27 }; case 1: return { v: 28 }; } - return throwError("invalid yParity"); + assertError(false, "invalid yParity"); } - return throwError("missing v"); + assertError(false, "missing v"); })(sig.v, sig.yParityAndS, sig.yParity); const result = new Signature(_guard, r, s, v); if (networkV) { setStore(result.#props, "networkV", networkV); } // If multiple of v, yParity, yParityAndS we given, check they match - if ("yParity" in sig && sig.yParity !== result.yParity) { - throwError("yParity mismatch"); - } else if ("yParityAndS" in sig && sig.yParityAndS !== result.yParityAndS) { - throwError("yParityAndS mismatch"); - } + assertError(!("yParity" in sig && sig.yParity !== result.yParity), "yParity mismatch"); + assertError(!("yParityAndS" in sig && sig.yParityAndS !== result.yParityAndS), "yParityAndS mismatch"); return result; } diff --git a/src.ts/crypto/signing-key.ts b/src.ts/crypto/signing-key.ts index 0a1f130b7..d5739555c 100644 --- a/src.ts/crypto/signing-key.ts +++ b/src.ts/crypto/signing-key.ts @@ -3,7 +3,7 @@ import * as secp256k1 from "@noble/secp256k1"; import { concat, dataLength, getBytes, getBytesCopy, hexlify, toHex, - assertArgument, throwArgumentError + assertArgument } from "../utils/index.js"; import { computeHmac } from "./hmac.js"; @@ -87,7 +87,7 @@ export class SigningKey { const pubKey = secp256k1.recoverPublicKey(getBytesCopy(digest), der, sig.yParity); if (pubKey != null) { return hexlify(pubKey); } - return throwArgumentError("invalid signautre for digest", "signature", signature); + assertArgument(false, "invalid signautre for digest", "signature", signature); } static _addPoints(p0: BytesLike, p1: BytesLike, compressed?: boolean): string { diff --git a/src.ts/ethers.ts b/src.ts/ethers.ts index 2f71d194d..abadbcedf 100644 --- a/src.ts/ethers.ts +++ b/src.ts/ethers.ts @@ -81,14 +81,14 @@ export { concat, dataLength, dataSlice, getBytes, getBytesCopy, hexlify, isHexString, isBytesLike, stripZerosLeft, zeroPadBytes, zeroPadValue, assertArgument, assertArgumentCount, assertNormalize, assertPrivate, - makeError, throwArgumentError, throwError, + makeError, throwError, isCallException, isError, getIpfsGatewayFunc, FetchRequest, FetchResponse, FetchCancelSignal, FixedFormat, FixedNumber, formatFixed, parseFixed, getBigInt, getNumber, toArray, toBigInt, toHex, toNumber, toQuantity, fromTwos, toTwos, mask, formatEther, parseEther, formatUnits, parseUnits, - _toEscapedUtf8String, toUtf8Bytes, toUtf8CodePoints, toUtf8String, + toUtf8Bytes, toUtf8CodePoints, toUtf8String, Utf8ErrorFuncs, decodeRlp, encodeRlp } from "./utils/index.js"; diff --git a/src.ts/hash/namehash.ts b/src.ts/hash/namehash.ts index 687779291..d05568077 100644 --- a/src.ts/hash/namehash.ts +++ b/src.ts/hash/namehash.ts @@ -1,7 +1,7 @@ import { keccak256 } from "../crypto/index.js"; import { - concat, hexlify, throwArgumentError, toUtf8Bytes, toUtf8String + concat, hexlify, assertArgument, toUtf8Bytes, toUtf8String } from "../utils/index.js"; @@ -56,9 +56,7 @@ export function isValidName(name: string): boolean { export function namehash(name: string): string { /* istanbul ignore if */ - if (typeof(name) !== "string") { - throwArgumentError("invalid ENS name; not a string", "name", name); - } + assertArgument(typeof(name) === "string", "invalid ENS name; not a string", "name", name); let result: string | Uint8Array = Zeros; diff --git a/src.ts/hash/solidity.ts b/src.ts/hash/solidity.ts index 9841dfba0..2967300e0 100644 --- a/src.ts/hash/solidity.ts +++ b/src.ts/hash/solidity.ts @@ -3,7 +3,7 @@ import { } from "../crypto/index.js"; import { concat, dataLength, getBytes, hexlify, toArray, toTwos, toUtf8Bytes, zeroPadBytes, zeroPadValue, - throwArgumentError + assertArgument } from "../utils/index.js"; @@ -31,9 +31,7 @@ function _pack(type: string, value: any, isArray?: boolean): Uint8Array { if (match) { let size = parseInt(match[2] || "256") - if ((match[2] && String(size) !== match[2]) || (size % 8 !== 0) || size === 0 || size > 256) { - return throwArgumentError("invalid number type", "type", type) - } + assertArgument((!match[2] || match[2] === String(size)) && (size % 8 === 0) && size !== 0 && size <= 256, "invalid number type", "type", type); if (isArray) { size = 256; } @@ -46,12 +44,9 @@ function _pack(type: string, value: any, isArray?: boolean): Uint8Array { if (match) { const size = parseInt(match[1]); - if (String(size) !== match[1] || size === 0 || size > 32) { - return throwArgumentError("invalid bytes type", "type", type) - } - if (dataLength(value) !== size) { - return throwArgumentError(`invalid value for ${ type }`, "value", value) - } + assertArgument(String(size) === match[1] && size !== 0 && size <= 32, "invalid bytes type", "type", type); + assertArgument(dataLength(value) === size, `invalid value for ${ type }`, "value", value); + if (isArray) { return getBytes(zeroPadBytes(value, 32)); } return value; } @@ -60,9 +55,8 @@ function _pack(type: string, value: any, isArray?: boolean): Uint8Array { if (match && Array.isArray(value)) { const baseType = match[1]; const count = parseInt(match[2] || String(value.length)); - if (count != value.length) { - throwArgumentError(`invalid array length for ${ type }`, "value", value) - } + assertArgument(count === value.length, `invalid array length for ${ type }`, "value", value); + const result: Array = []; value.forEach(function(value) { result.push(_pack(baseType, value, true)); @@ -70,15 +64,14 @@ function _pack(type: string, value: any, isArray?: boolean): Uint8Array { return getBytes(concat(result)); } - return throwArgumentError("invalid type", "type", type) + assertArgument(false, "invalid type", "type", type) } // @TODO: Array Enum export function solidityPacked(types: ReadonlyArray, values: ReadonlyArray): string { - if (types.length != values.length) { - throwArgumentError("wrong number of values; expected ${ types.length }", "values", values) - } + assertArgument(types.length === values.length, "wrong number of values; expected ${ types.length }", "values", values); + const tight: Array = []; types.forEach(function(type, index) { tight.push(_pack(type, values[index])); diff --git a/src.ts/hash/typed-data.ts b/src.ts/hash/typed-data.ts index fa99fd58c..f37732e9e 100644 --- a/src.ts/hash/typed-data.ts +++ b/src.ts/hash/typed-data.ts @@ -3,7 +3,7 @@ import { getAddress } from "../address/index.js"; import { keccak256 } from "../crypto/index.js"; import { concat, defineProperties, getBigInt, getBytes, hexlify, isHexString, mask, toHex, toTwos, zeroPadValue, - throwArgumentError + assertArgument } from "../utils/index.js"; import { id } from "./id.js"; @@ -58,9 +58,7 @@ const domainFieldNames: Array = [ function checkString(key: string): (value: any) => string { return function (value: any){ - if (typeof(value) !== "string") { - throwArgumentError(`invalid domain value for ${ JSON.stringify(key) }`, `domain.${ key }`, value); - } + assertArgument(typeof(value) === "string", `invalid domain value for ${ JSON.stringify(key) }`, `domain.${ key }`, value); return value; } } @@ -75,13 +73,11 @@ const domainChecks: Record any> = { try { return getAddress(value).toLowerCase(); } catch (error) { } - return throwArgumentError(`invalid domain value "verifyingContract"`, "domain.verifyingContract", value); + assertArgument(false, `invalid domain value "verifyingContract"`, "domain.verifyingContract", value); }, salt: function(value: any) { const bytes = getBytes(value, "domain.salt"); - if (bytes.length !== 32) { - throwArgumentError(`invalid domain value "salt"`, "domain.salt", value); - } + assertArgument(bytes.length === 32, `invalid domain value "salt"`, "domain.salt", value); return hexlify(bytes); } } @@ -94,9 +90,7 @@ function getBaseEncoder(type: string): null | ((value: any) => string) { const signed = (match[1] === ""); const width = parseInt(match[2] || "256"); - if (width % 8 !== 0 || width > 256 || (match[2] && match[2] !== String(width))) { - throwArgumentError("invalid numeric width", "type", type); - } + assertArgument(width % 8 === 0 && width !== 0 && width <= 256 && (match[2] == null || match[2] === String(width)), "invalid numeric width", "type", type); const boundsUpper = mask(BN_MAX_UINT256, signed ? (width - 1): width); const boundsLower = signed ? ((boundsUpper + BN_1) * BN__1): BN_0; @@ -104,9 +98,7 @@ function getBaseEncoder(type: string): null | ((value: any) => string) { return function(_value: BigNumberish) { const value = getBigInt(_value, "value"); - if (value < boundsLower || value > boundsUpper) { - throwArgumentError(`value out-of-bounds for ${ type }`, "value", value); - } + assertArgument(value >= boundsLower && value <= boundsUpper, `value out-of-bounds for ${ type }`, "value", value); return toHex(toTwos(value, 256), 32); }; @@ -118,15 +110,11 @@ function getBaseEncoder(type: string): null | ((value: any) => string) { const match = type.match(/^bytes(\d+)$/); if (match) { const width = parseInt(match[1]); - if (width === 0 || width > 32 || match[1] !== String(width)) { - throwArgumentError("invalid bytes width", "type", type); - } + assertArgument(width !== 0 && width <= 32 && match[1] === String(width), "invalid bytes width", "type", type); return function(value: BytesLike) { const bytes = getBytes(value); - if (bytes.length !== width) { - throwArgumentError(`invalid length for ${ type }`, "value", value); - } + assertArgument(bytes.length === width, `invalid length for ${ type }`, "value", value); return hexPadRight(value); }; } @@ -192,24 +180,18 @@ export class TypedDataEncoder { for (const field of types[name]) { // Check each field has a unique name - if (uniqueNames.has(field.name)) { - throwArgumentError(`duplicate variable name ${ JSON.stringify(field.name) } in ${ JSON.stringify(name) }`, "types", types); - } + assertArgument(!uniqueNames.has(field.name), `duplicate variable name ${ JSON.stringify(field.name) } in ${ JSON.stringify(name) }`, "types", types); uniqueNames.add(field.name); // Get the base type (drop any array specifiers) const baseType = ((field.type.match(/^([^\x5b]*)(\x5b|$)/)))[1] || null; - if (baseType === name) { - throwArgumentError(`circular type reference to ${ JSON.stringify(baseType) }`, "types", types); - } + assertArgument(baseType !== name, `circular type reference to ${ JSON.stringify(baseType) }`, "types", types); // Is this a base encoding type? const encoder = getBaseEncoder(baseType); if (encoder) { continue; } - if (!parents.has(baseType)) { - throwArgumentError(`unknown type ${ JSON.stringify(baseType) }`, "types", types); - } + assertArgument(parents.has(baseType), `unknown type ${ JSON.stringify(baseType) }`, "types", types); // Add linkage (parents.get(baseType) as Array).push(name); @@ -219,20 +201,14 @@ export class TypedDataEncoder { // Deduce the primary type const primaryTypes = Array.from(parents.keys()).filter((n) => ((parents.get(n) as Array).length === 0)); - - if (primaryTypes.length === 0) { - throwArgumentError("missing primary type", "types", types); - } else if (primaryTypes.length > 1) { - throwArgumentError(`ambiguous primary types or unused types: ${ primaryTypes.map((t) => (JSON.stringify(t))).join(", ") }`, "types", types); - } + assertArgument(primaryTypes.length !== 0, "missing primary type", "types", types); + assertArgument(primaryTypes.length === 1, `ambiguous primary types or unused types: ${ primaryTypes.map((t) => (JSON.stringify(t))).join(", ") }`, "types", types); defineProperties(this, { primaryType: primaryTypes[0] }); // Check for circular type references function checkCircular(type: string, found: Set) { - if (found.has(type)) { - throwArgumentError(`circular type reference to ${ JSON.stringify(type) }`, "types", types); - } + assertArgument(!found.has(type), `circular type reference to ${ JSON.stringify(type) }`, "types", types); found.add(type); @@ -282,11 +258,8 @@ export class TypedDataEncoder { if (match) { const subtype = match[1]; const subEncoder = this.getEncoder(subtype); - const length = parseInt(match[3]); return (value: Array) => { - if (length >= 0 && value.length !== length) { - throwArgumentError("array length mismatch; expected length ${ arrayLength }", "value", value); - } + assertArgument(!match[3] || parseInt(match[3]) === value.length, `array length mismatch; expected length ${ parseInt(match[3]) }`, "value", value); let result = value.map(subEncoder); if (this.#fullTypes.has(subtype)) { @@ -312,14 +285,12 @@ export class TypedDataEncoder { } } - return throwArgumentError(`unknown type: ${ type }`, "type", type); + assertArgument(false, `unknown type: ${ type }`, "type", type); } encodeType(name: string): string { const result = this.#fullTypes.get(name); - if (!result) { - return throwArgumentError(`unknown type: ${ JSON.stringify(name) }`, "name", name); - } + assertArgument(result, `unknown type: ${ JSON.stringify(name) }`, "name", name); return result; } @@ -349,12 +320,8 @@ export class TypedDataEncoder { // Array const match = type.match(/^(.*)(\x5b(\d*)\x5d)$/); if (match) { - const subtype = match[1]; - const length = parseInt(match[3]); - if (length >= 0 && value.length !== length) { - throwArgumentError("array length mismatch; expected length ${ arrayLength }", "value", value); - } - return value.map((v: any) => this._visit(subtype, v, callback)); + assertArgument(!match[3] || parseInt(match[3]) === value.length, `array length mismatch; expected length ${ parseInt(match[3]) }`, "value", value); + return value.map((v: any) => this._visit(match[1], v, callback)); } // Struct @@ -366,7 +333,7 @@ export class TypedDataEncoder { }, >{}); } - return throwArgumentError(`unknown type: ${ type }`, "type", type); + assertArgument(false, `unknown type: ${ type }`, "type", type); } visit(value: Record, callback: (type: string, data: any) => any): any { @@ -389,9 +356,7 @@ export class TypedDataEncoder { const domainFields: Array = [ ]; for (const name in domain) { const type = domainFieldTypes[name]; - if (!type) { - throwArgumentError(`invalid typed-data domain key: ${ JSON.stringify(name) }`, "domain", domain); - } + assertArgument(type, `invalid typed-data domain key: ${ JSON.stringify(name) }`, "domain", domain); domainFields.push({ name, type }); } @@ -475,11 +440,9 @@ export class TypedDataEncoder { const encoder = TypedDataEncoder.from(types); const typesWithDomain = Object.assign({ }, types); - if (typesWithDomain.EIP712Domain) { - throwArgumentError("types must not contain EIP712Domain type", "types.EIP712Domain", types); - } else { - typesWithDomain.EIP712Domain = domainTypes; - } + assertArgument(typesWithDomain.EIP712Domain == null, "types must not contain EIP712Domain type", "types.EIP712Domain", types); + + typesWithDomain.EIP712Domain = domainTypes; // Validate the data structures and types encoder.encode(value); @@ -506,13 +469,11 @@ export class TypedDataEncoder { case "bool": return !!value; case "string": - if (typeof(value) !== "string") { - throwArgumentError(`invalid string`, "value", value); - } + assertArgument(typeof(value) === "string", "invalid string", "value", value); return value; } - return throwArgumentError("unsupported type", "type", type); + assertArgument(false, "unsupported type", "type", type); }) }; } diff --git a/src.ts/providers/abstract-provider.ts b/src.ts/providers/abstract-provider.ts index c461a8a67..4ac3fc2c9 100644 --- a/src.ts/providers/abstract-provider.ts +++ b/src.ts/providers/abstract-provider.ts @@ -11,7 +11,7 @@ import { Transaction } from "../transaction/index.js"; import { concat, dataLength, dataSlice, hexlify, isHexString, getBigInt, getBytes, getNumber, - isCallException, makeError, throwError, throwArgumentError, + isCallException, makeError, throwError, assertArgument, FetchRequest, toArray, toQuantity, defineProperties, EventPayload, resolveProperties, @@ -198,7 +198,7 @@ async function getSubscription(_event: ProviderEvent, provider: AbstractProvider return { filter, tag: getTag("event", filter), type: "event" }; } - return throwArgumentError("unknown ProviderEvent", "event", _event); + assertArgument(false, "unknown ProviderEvent", "event", _event); } function getTime(): number { return (new Date()).getTime(); } @@ -500,7 +500,7 @@ export class AbstractProvider implements Provider { return this.getBlockNumber().then((b) => toQuantity(b + blockTag)); } - return throwArgumentError("invalid blockTag", "blockTag", blockTag); + assertArgument(false, "invalid blockTag", "blockTag", blockTag); } _getFilter(filter: Filter | FilterByBlockHash): PerformActionFilter | Promise { diff --git a/src.ts/providers/abstract-signer.ts b/src.ts/providers/abstract-signer.ts index 5e231809b..027a812a8 100644 --- a/src.ts/providers/abstract-signer.ts +++ b/src.ts/providers/abstract-signer.ts @@ -1,7 +1,7 @@ import { Transaction } from "../transaction/index.js"; import { defineProperties, getBigInt, resolveProperties, - throwArgumentError, throwError + assertArgument, throwError } from "../utils/index.js"; import type { TypedDataDomain, TypedDataField } from "../hash/index.js"; @@ -40,9 +40,7 @@ export abstract class AbstractSigner

{ - if (to == null) { - return throwArgumentError("transaction to ENS name not configured", "tx.to", pop.to); - } + assertArgument(to != null, "transaction to ENS name not configured", "tx.to", pop.to); return to; }); } @@ -53,9 +51,8 @@ export abstract class AbstractSigner

{ - if (!from || address.toLowerCase() !== from.toLowerCase()) { - return throwArgumentError("transaction from mismatch", "tx.from", from); - } + assertArgument(from && address.toLowerCase() === from.toLowerCase(), + "transaction from mismatch", "tx.from", from); return address; }); } @@ -84,9 +81,7 @@ export abstract class AbstractSigner

(this.provider)).getNetwork(); if (pop.chainId != null) { const chainId = getBigInt(pop.chainId); - if (chainId !== network.chainId) { - throwArgumentError("transaction chainId mismatch", "tx.chainId", tx.chainId); - } + assertArgument(chainId === network.chainId, "transaction chainId mismatch", "tx.chainId", tx.chainId); } else { pop.chainId = network.chainId; } @@ -94,9 +89,9 @@ export abstract class AbstractSigner

) { } function callAddress(value: string): string { - if (value.length !== 66 || dataSlice(value, 0, 12) !== "0x000000000000000000000000") { - throwArgumentError("invalid call address", "value", value); - } + assertArgument(value.length === 66 && dataSlice(value, 0, 12) === "0x000000000000000000000000", + "invalid call address", "value", value); return getAddress("0x" + value.substring(26)); } @@ -95,7 +94,7 @@ function getIpfsLink(link: string): string { } else if (link.match(/^ipfs:\/\//i)) { link = link.substring(7); } else { - throwArgumentError("unsupported IPFS format", "link", link); + assertArgument(false, "unsupported IPFS format", "link", link); } return `https:/\/gateway.ipfs.io/ipfs/${ link }`; diff --git a/src.ts/providers/format.ts b/src.ts/providers/format.ts index 8a14c9cc5..4aa5730da 100644 --- a/src.ts/providers/format.ts +++ b/src.ts/providers/format.ts @@ -4,7 +4,7 @@ import { Signature } from "../crypto/index.js" import { accessListify } from "../transaction/index.js"; import { getBigInt, getNumber, hexlify, isHexString, zeroPadValue, - throwArgumentError, throwError + assertArgument, throwError } from "../utils/index.js"; @@ -63,20 +63,16 @@ export function formatBoolean(value: any): boolean { case false: case "false": return false; } - return throwArgumentError(`invalid boolean; ${ JSON.stringify(value) }`, "value", value); + assertArgument(false, `invalid boolean; ${ JSON.stringify(value) }`, "value", value); } export function formatData(value: string): string { - if (!isHexString(value, true)) { - throwArgumentError("", "value", value); - } + assertArgument(isHexString(value, true), "invalid data", "value", value); return value; } export function formatHash(value: any): string { - if (!isHexString(value, 32)) { - throwArgumentError("", "value", value); - } + assertArgument(isHexString(value, 32), "invalid hash", "value", value); return value; } diff --git a/src.ts/providers/network.ts b/src.ts/providers/network.ts index 9cbefaeac..1ec9486e4 100644 --- a/src.ts/providers/network.ts +++ b/src.ts/providers/network.ts @@ -1,6 +1,6 @@ import { accessListify } from "../transaction/index.js"; import { - getStore, getBigInt, setStore, throwArgumentError + getStore, getBigInt, setStore, assertArgument } from "../utils/index.js"; import { EnsPlugin, GasCostPlugin } from "./plugins-network.js"; @@ -192,7 +192,7 @@ export class Network { return new Network("unknown", network); } - throwArgumentError("unknown network", "network", network); + assertArgument(false, "unknown network", "network", network); } // Clonable with network-like abilities @@ -205,9 +205,8 @@ export class Network { // Networkish if (typeof(network) === "object") { - if (typeof(network.name) !== "string" || typeof(network.chainId) !== "number") { - throwArgumentError("invalid network object name or chainId", "network", network); - } + assertArgument(typeof(network.name) === "string" && typeof(network.chainId) === "number", + "invalid network object name or chainId", "network", network); const custom = new Network((network.name), (network.chainId)); @@ -222,7 +221,7 @@ export class Network { return custom; } - return throwArgumentError("invalid network", "network", network); + assertArgument(false, "invalid network", "network", network); } /** @@ -233,7 +232,7 @@ export class Network { if (typeof(nameOrChainId) === "number") { nameOrChainId = BigInt(nameOrChainId); } const existing = Networks.get(nameOrChainId); if (existing) { - throwArgumentError(`conflicting network for ${ JSON.stringify(existing.name) }`, "nameOrChainId", nameOrChainId); + assertArgument(false, `conflicting network for ${ JSON.stringify(existing.name) }`, "nameOrChainId", nameOrChainId); } Networks.set(nameOrChainId, networkFunc); } diff --git a/src.ts/providers/plugins-network.ts b/src.ts/providers/plugins-network.ts index 3058014ea..5d13c8012 100644 --- a/src.ts/providers/plugins-network.ts +++ b/src.ts/providers/plugins-network.ts @@ -1,6 +1,6 @@ import { defineProperties } from "../utils/properties.js"; -import { throwArgumentError } from "../utils/index.js"; +import { assertArgument } from "../utils/index.js"; import type { FeeData, Provider } from "./provider.js"; @@ -52,9 +52,7 @@ export class GasCostPlugin extends NetworkPlugin implements GasCostParameters { function set(name: keyof GasCostParameters, nullish: number): void { let value = (costs || { })[name]; if (value == null) { value = nullish; } - if (typeof(value) !== "number") { - throwArgumentError(`invalud value for ${ name }`, "costs", costs); - } + assertArgument(typeof(value) === "number", `invalud value for ${ name }`, "costs", costs); props[name] = value; } diff --git a/src.ts/providers/provider-alchemy.ts b/src.ts/providers/provider-alchemy.ts index d0ed066da..2b1194aac 100644 --- a/src.ts/providers/provider-alchemy.ts +++ b/src.ts/providers/provider-alchemy.ts @@ -1,6 +1,6 @@ import { - defineProperties, resolveProperties, throwArgumentError, throwError, + defineProperties, resolveProperties, assertArgument, throwError, FetchRequest } from "../utils/index.js"; @@ -36,7 +36,7 @@ function getHost(name: string): string { return "opt-goerli.g.alchemy.com"; } - return throwArgumentError("unsupported network", "network", name); + assertArgument(false, "unsupported network", "network", name); } export class AlchemyProvider extends JsonRpcProvider implements CommunityResourcable { diff --git a/src.ts/providers/provider-ankr.ts b/src.ts/providers/provider-ankr.ts index 40878f2a3..0456096e5 100644 --- a/src.ts/providers/provider-ankr.ts +++ b/src.ts/providers/provider-ankr.ts @@ -1,5 +1,5 @@ import { - defineProperties, FetchRequest, throwArgumentError + defineProperties, FetchRequest, assertArgument } from "../utils/index.js"; import { AbstractProvider } from "./abstract-provider.js"; @@ -28,7 +28,8 @@ function getHost(name: string): string { case "arbitrum": return "rpc.ankr.com/arbitrum"; } - return throwArgumentError("unsupported network", "network", name); + + assertArgument(false, "unsupported network", "network", name); } diff --git a/src.ts/providers/provider-cloudflare.ts b/src.ts/providers/provider-cloudflare.ts index 9e8232958..2f814a04b 100644 --- a/src.ts/providers/provider-cloudflare.ts +++ b/src.ts/providers/provider-cloudflare.ts @@ -1,4 +1,4 @@ -import { throwArgumentError } from "../utils/index.js"; +import { assertArgument } from "../utils/index.js"; import { Network } from "./network.js"; import { JsonRpcProvider } from "./provider-jsonrpc.js"; @@ -9,9 +9,7 @@ import type { Networkish } from "./network.js"; export class CloudflareProvider extends JsonRpcProvider { constructor(_network: Networkish = "mainnet") { const network = Network.from(_network); - if (network.name !== "mainnet") { - return throwArgumentError("unsupported network", "network", _network); - } + assertArgument(network.name === "mainnet", "unsupported network", "network", _network); super("https:/\/cloudflare-eth.com/", network, { staticNetwork: network }); } } diff --git a/src.ts/providers/provider-etherscan-base.ts b/src.ts/providers/provider-etherscan-base.ts index 3a82b4c47..3f5f5283a 100644 --- a/src.ts/providers/provider-etherscan-base.ts +++ b/src.ts/providers/provider-etherscan-base.ts @@ -4,7 +4,7 @@ import { defineProperties, hexlify, toQuantity, FetchRequest, - throwArgumentError, throwError, + assertArgument, throwError, toUtf8String } from "../utils/index.js"; @@ -103,7 +103,7 @@ export class BaseEtherscanProvider extends AbstractProvider { default: } - return throwArgumentError("unsupported network", "network", this.network); + assertArgument(false, "unsupported network", "network", this.network); } getUrl(module: string, params: Record): string { diff --git a/src.ts/providers/provider-fallback.ts b/src.ts/providers/provider-fallback.ts index a691ab6b5..4e56ca54f 100644 --- a/src.ts/providers/provider-fallback.ts +++ b/src.ts/providers/provider-fallback.ts @@ -1,6 +1,6 @@ import { - getBigInt, getNumber, hexlify, throwError, throwArgumentError + getBigInt, getNumber, hexlify, throwError, assertArgument } from "../utils/index.js"; import { AbstractProvider } from "./abstract-provider.js"; @@ -284,9 +284,8 @@ export class FallbackProvider extends AbstractProvider { this.eventQuorum = 1; this.eventWorkers = 1; - if (this.quorum > this.#configs.reduce((a, c) => (a + c.weight), 0)) { - throwArgumentError("quorum exceed provider wieght", "quorum", this.quorum); - } + assertArgument(this.quorum <= this.#configs.reduce((a, c) => (a + c.weight), 0), + "quorum exceed provider wieght", "quorum", this.quorum); } // @TOOD: Copy these and only return public values diff --git a/src.ts/providers/provider-infura.ts b/src.ts/providers/provider-infura.ts index 9ce985e6c..043d644b5 100644 --- a/src.ts/providers/provider-infura.ts +++ b/src.ts/providers/provider-infura.ts @@ -1,5 +1,5 @@ import { - defineProperties, FetchRequest, throwArgumentError, throwError + defineProperties, FetchRequest, assertArgument, throwError } from "../utils/index.js"; import { showThrottleMessage } from "./community.js"; @@ -37,7 +37,7 @@ function getHost(name: string): string { return "optimism-goerli.infura.io"; } - return throwArgumentError("unsupported network", "network", name); + assertArgument(false, "unsupported network", "network", name); } export class InfuraWebSocketProvider extends WebSocketProvider implements CommunityResourcable { diff --git a/src.ts/providers/provider-jsonrpc.ts b/src.ts/providers/provider-jsonrpc.ts index bffba381d..793e8ad69 100644 --- a/src.ts/providers/provider-jsonrpc.ts +++ b/src.ts/providers/provider-jsonrpc.ts @@ -9,7 +9,7 @@ import { TypedDataEncoder } from "../hash/index.js"; import { accessListify } from "../transaction/index.js"; import { defineProperties, getBigInt, hexlify, isHexString, toQuantity, toUtf8Bytes, - makeError, throwArgumentError, throwError, + makeError, assertArgument, throwError, FetchRequest, resolveProperties } from "../utils/index.js"; @@ -220,9 +220,8 @@ export class JsonRpcSigner extends AbstractSigner { const _from = tx.from; promises.push((async () => { const from = await resolveAddress(_from, this.provider); - if (from == null || from.toLowerCase() !== this.address.toLowerCase()) { - throwArgumentError("from address mismatch", "transaction", _tx); - } + assertArgument(from != null && from.toLowerCase() === this.address.toLowerCase(), + "from address mismatch", "transaction", _tx); tx.from = from; })()); } else { @@ -287,9 +286,8 @@ export class JsonRpcSigner extends AbstractSigner { // Make sure the from matches the sender if (tx.from) { const from = await resolveAddress(tx.from, this.provider); - if (from == null || from.toLowerCase() !== this.address.toLowerCase()) { - return throwArgumentError("from address mismatch", "transaction", _tx); - } + assertArgument(from != null && from.toLowerCase() === this.address.toLowerCase(), + "from address mismatch", "transaction", _tx); tx.from = from; } else { tx.from = this.address; @@ -312,9 +310,7 @@ export class JsonRpcSigner extends AbstractSigner { // Populate any ENS names (in-place) const populated = await TypedDataEncoder.resolveNames(domain, types, value, async (value: string) => { const address = await resolveAddress(value); - if (address == null) { - return throwArgumentError("TypedData does not support null address", "value", value); - } + assertArgument(address != null, "TypedData does not support null address", "value", value); return address; }); @@ -462,9 +458,8 @@ export class JsonRpcApiProvider extends AbstractProvider { // This could be relaxed in the future to just check equivalent networks const staticNetwork = this._getOption("staticNetwork"); if (staticNetwork) { - if (staticNetwork !== network) { - throwArgumentError("staticNetwork MUST match network object", "options", options); - } + assertArgument(staticNetwork === network, + "staticNetwork MUST match network object", "options", options); this.#network = staticNetwork; } } diff --git a/src.ts/transaction/transaction.ts b/src.ts/transaction/transaction.ts index 38a8fb08d..0bc0702ce 100644 --- a/src.ts/transaction/transaction.ts +++ b/src.ts/transaction/transaction.ts @@ -3,7 +3,7 @@ import { getAddress } from "../address/index.js"; import { keccak256, Signature } from "../crypto/index.js"; import { concat, decodeRlp, encodeRlp, getBytes, getStore, getBigInt, getNumber, hexlify, - setStore, throwArgumentError, toArray, zeroPadValue + setStore, assertArgument, toArray, zeroPadValue } from "../utils/index.js"; import { accessListify } from "./accesslist.js"; @@ -56,7 +56,7 @@ function handleData(value: string, param: string): string { try { return hexlify(value); } catch (error) { - return throwArgumentError("invalid data", param, value); + assertArgument(false, "invalid data", param, value); } } @@ -64,7 +64,7 @@ function handleAccessList(value: any, param: string): AccessList { try { return accessListify(value); } catch (error) { - return throwArgumentError("invalid accessList", param, value); + assertArgument(false, "invalid accessList", param, value); } } @@ -76,16 +76,14 @@ function handleNumber(_value: string, param: string): number { function handleUint(_value: string, param: string): bigint { if (_value === "0x") { return BN_0; } const value = getBigInt(_value, param); - if (value > BN_MAX_UINT) { throwArgumentError("value exceeds uint size", param, value); } + assertArgument(value <= BN_MAX_UINT, "value exceeds uint size", param, value); return value; } function formatNumber(_value: BigNumberish, name: string): Uint8Array { const value = getBigInt(_value, "value"); const result = toArray(value); - if (result.length > 32) { - throwArgumentError(`value too large`, `tx.${ name }`, value); - } + assertArgument(result.length <= 32, `value too large`, `tx.${ name }`, value); return result; } @@ -96,9 +94,8 @@ function formatAccessList(value: AccessListish): Array<[ string, Array ] function _parseLegacy(data: Uint8Array): TransactionLike { const fields: any = decodeRlp(data); - if (!Array.isArray(fields) || (fields.length !== 9 && fields.length !== 6)) { - return throwArgumentError("invalid field count for legacy transaction", "data", data); - } + assertArgument(Array.isArray(fields) && (fields.length === 9 || fields.length === 6), + "invalid field count for legacy transaction", "data", data); const tx: TransactionLike = { type: 0, @@ -130,9 +127,7 @@ function _parseLegacy(data: Uint8Array): TransactionLike { tx.chainId = chainId // Signed Legacy Transaction - if (chainId === BN_0 && (v < BN_27 || v > BN_28)) { - throwArgumentError("non-canonical legacy v", "v", fields[6]); - } + assertArgument(chainId !== BN_0 || (v === BN_27 || v === BN_28), "non-canonical legacy v", "v", fields[6]); tx.signature = Signature.from({ r: zeroPadValue(fields[7], 32), @@ -163,9 +158,8 @@ function _serializeLegacy(tx: Transaction, sig?: Signature): string { // We have a chainId in the tx and an EIP-155 v in the signature, // make sure they agree with each other - if (sig && sig.networkV != null && sig.legacyChainId !== chainId) { - throwArgumentError("tx.chainId/sig.v mismatch", "sig", sig); - } + assertArgument(!sig || sig.networkV == null || sig.legacyChainId === chainId, + "tx.chainId/sig.v mismatch", "sig", sig); } else if (sig) { // No chainId provided, but the signature is signing with EIP-155; derive chainId @@ -190,7 +184,7 @@ function _serializeLegacy(tx: Transaction, sig?: Signature): string { if (chainId !== BN_0) { v = Signature.getChainIdV(chainId, sig.v); } else if (BigInt(sig.v) !== v) { - throwArgumentError("tx.chainId/sig.v mismatch", "sig", sig); + assertArgument(false, "tx.chainId/sig.v mismatch", "sig", sig); } fields.push(toArray(v)); @@ -206,7 +200,7 @@ function _parseEipSignature(tx: TransactionLike, fields: Array, serializ yParity = handleNumber(fields[0], "yParity"); if (yParity !== 0 && yParity !== 1) { throw new Error("bad yParity"); } } catch (error) { - return throwArgumentError("invalid yParity", "yParity", fields[0]); + assertArgument(false, "invalid yParity", "yParity", fields[0]); } const r = zeroPadValue(fields[1], 32); @@ -219,9 +213,8 @@ function _parseEipSignature(tx: TransactionLike, fields: Array, serializ function _parseEip1559(data: Uint8Array): TransactionLike { const fields: any = decodeRlp(getBytes(data).slice(1)); - if (!Array.isArray(fields) || (fields.length !== 9 && fields.length !== 12)) { - throwArgumentError("invalid field count for transaction type: 2", "data", hexlify(data)); - } + assertArgument(Array.isArray(fields) && (fields.length === 9 || fields.length === 12), + "invalid field count for transaction type: 2", "data", hexlify(data)); const maxPriorityFeePerGas = handleUint(fields[2], "maxPriorityFeePerGas"); const maxFeePerGas = handleUint(fields[3], "maxFeePerGas"); @@ -274,9 +267,8 @@ function _serializeEip1559(tx: TransactionLike, sig?: Signature): string { function _parseEip2930(data: Uint8Array): TransactionLike { const fields: any = decodeRlp(getBytes(data).slice(1)); - if (!Array.isArray(fields) || (fields.length !== 8 && fields.length !== 11)) { - throwArgumentError("invalid field count for transaction type: 1", "data", hexlify(data)); - } + assertArgument(Array.isArray(fields) && (fields.length === 8 || fields.length === 11), + "invalid field count for transaction type: 1", "data", hexlify(data)); const tx: TransactionLike = { type: 1, diff --git a/src.ts/utils/base58.ts b/src.ts/utils/base58.ts index 5ddc08f78..987fc7e73 100644 --- a/src.ts/utils/base58.ts +++ b/src.ts/utils/base58.ts @@ -1,6 +1,6 @@ import { getBytes } from "./data.js"; -import { throwArgumentError } from "./errors.js"; +import { assertArgument } from "./errors.js"; import { toBigInt, toHex } from "./maths.js"; import type { BytesLike } from "./index.js"; @@ -17,9 +17,7 @@ function getAlpha(letter: string): bigint { } } const result = Lookup[letter]; - if (result == null) { - throwArgumentError(`invalid base58 value`, "letter", letter); - } + assertArgument(result != null, `invalid base58 value`, "letter", letter); return result; } diff --git a/src.ts/utils/data.ts b/src.ts/utils/data.ts index a83ccb430..3697aeb65 100644 --- a/src.ts/utils/data.ts +++ b/src.ts/utils/data.ts @@ -1,4 +1,4 @@ -import { throwArgumentError, throwError } from "./errors.js"; +import { assertArgument, throwError } from "./errors.js"; export type BytesLike = string | Uint8Array; @@ -19,7 +19,7 @@ function _getBytes(value: BytesLike, name?: string, copy?: boolean): Uint8Array return result; } - return throwArgumentError("invalid BytesLike value", name || "value", value); + assertArgument(false, "invalid BytesLike value", name || "value", value); } /** diff --git a/src.ts/utils/errors.ts b/src.ts/utils/errors.ts index 1baf937f0..ea3f4eaa6 100644 --- a/src.ts/utils/errors.ts +++ b/src.ts/utils/errors.ts @@ -327,16 +327,6 @@ export function throwError>(m throw makeError(message, code, info); } -/** - * Throws an [[api:ArgumentError]] with %%message%% for the parameter with - * %%name%% and the %%value%%. - */ -export function throwArgumentError(message: string, name: string, value: any): never { - return throwError(message, "INVALID_ARGUMENT", { - argument: name, - value: value - }); -} /** * A simple helper to simply ensuring provided arguments match expected @@ -346,7 +336,9 @@ export function throwArgumentError(message: string, name: string, value: any): n * any further code does not need additional compile-time checks. */ export function assertArgument(check: unknown, message: string, name: string, value: unknown): asserts check { - if (!check) { throwArgumentError(message, name, value); } + if (!check) { + throwError(message, "INVALID_ARGUMENT", { argument: name, value: value }); + } } export function assertArgumentCount(count: number, expectedCount: number, message: string = ""): void { diff --git a/src.ts/utils/fetch.ts b/src.ts/utils/fetch.ts index dc2d50879..4e85ed6cc 100644 --- a/src.ts/utils/fetch.ts +++ b/src.ts/utils/fetch.ts @@ -1,6 +1,6 @@ import { decodeBase64, encodeBase64 } from "./base64.js"; import { hexlify } from "./data.js"; -import { assertArgument, throwArgumentError, throwError } from "./errors.js"; +import { assertArgument, throwError } from "./errors.js"; import { defineProperties } from "./properties.js"; import { toUtf8Bytes, toUtf8String } from "./utf8.js" @@ -310,9 +310,7 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> { * Sets an ``Authorization`` for %%username%% with %%password%%. */ setCredentials(username: string, password: string): void { - if (username.match(/:/)) { - throwArgumentError("invalid basic authentication username", "username", "[REDACTED]"); - } + assertArgument(!username.match(/:/), "invalid basic authentication username", "username", "[REDACTED]"); this.#creds = `${ username }:${ password }`; } @@ -762,8 +760,8 @@ export class FetchResponse implements Iterable<[ key: string, value: string ]> { throwThrottleError(message?: string, stall?: number): never { if (stall == null) { stall = -1; - } else if (typeof(stall) !== "number" || !Number.isInteger(stall) || stall < 0) { - return throwArgumentError("invalid stall timeout", "stall", stall); + } else { + assertArgument(Number.isInteger(stall) && stall >= 0, "invalid stall timeout", "stall", stall); } const error = new Error(message || "throttling requests"); diff --git a/src.ts/utils/fixednumber.ts b/src.ts/utils/fixednumber.ts index 57da0a53a..ebd4bf324 100644 --- a/src.ts/utils/fixednumber.ts +++ b/src.ts/utils/fixednumber.ts @@ -1,5 +1,5 @@ import { getBytes } from "./data.js"; -import { throwArgumentError, throwError } from "./errors.js"; +import { assertArgument, throwError } from "./errors.js"; import { getBigInt, getNumber, fromTwos, toBigInt, toHex, toTwos } from "./maths.js"; import type { BigNumberish, BytesLike, Numeric } from "./index.js"; @@ -22,9 +22,8 @@ while (zeros.length < 256) { zeros += zeros; } // Returns a string "1" followed by decimal "0"s function getMultiplier(decimals: number): bigint { - if (typeof(decimals) !== "number" || decimals < 0 || decimals > 256 || decimals % 1 ) { - throwArgumentError("invalid decimal length", "decimals", decimals); - } + assertArgument(Number.isInteger(decimals) && decimals >= 0 && decimals <= 256, + "invalid decimal length", "decimals", decimals); return BigInt("1" + zeros.substring(0, decimals)); } @@ -65,23 +64,18 @@ export function parseFixed(value: string, _decimals: Numeric): bigint { const multiplier = getMultiplier(decimals); - if (typeof(value) !== "string" || !value.match(/^-?[0-9.]+$/)) { - throwArgumentError("invalid decimal value", "value", value); - } + assertArgument(typeof(value) === "string" && value.match(/^-?[0-9.]+$/), + "invalid decimal value", "value", value); // Is it negative? const negative = (value.substring(0, 1) === "-"); if (negative) { value = value.substring(1); } - if (value === ".") { - throwArgumentError("missing value", "value", value); - } + assertArgument(value !== ".", "missing value", "value", value); // Split it into a whole and fractional part const comps = value.split("."); - if (comps.length > 2) { - throwArgumentError("too many decimal points", "value", value); - } + assertArgument(comps.length <= 2, "too many decimal points", "value", value); let whole = (comps[0] || "0"), fraction = (comps[1] || "0"); @@ -155,9 +149,7 @@ export class FixedFormat { signed = false; } else { const match = value.match(/^(u?)fixed([0-9]+)x([0-9]+)$/); - if (!match) { - return throwArgumentError("invalid fixed format", "format", value); - } + assertArgument(match, "invalid fixed format", "format", value); signed = (match[1] !== "u"); width = parseInt(match[2]); decimals = parseInt(match[3]); @@ -165,9 +157,8 @@ export class FixedFormat { } else if (value) { const check = (key: string, type: string, defaultValue: any): any => { if (value[key] == null) { return defaultValue; } - if (typeof(value[key]) !== type) { - throwArgumentError("invalid fixed format (" + key + " not " + type +")", "format." + key, value[key]); - } + assertArgument(typeof(value[key]) === type, + "invalid fixed format (" + key + " not " + type +")", "format." + key, value[key]); return value[key]; } signed = check("signed", "boolean", signed); @@ -175,13 +166,8 @@ export class FixedFormat { decimals = check("decimals", "number", decimals); } - if (width % 8) { - throwArgumentError("invalid fixed format width (not byte aligned)", "format.width", width); - } - - if (decimals > 80) { - throwArgumentError("invalid fixed format (decimals too large)", "format.decimals", decimals); - } + assertArgument((width % 8) === 0, "invalid fixed format width (not byte aligned)", "format.width", width); + assertArgument(decimals <= 80, "invalid fixed format (decimals too large)", "format.decimals", decimals); return new FixedFormat(_constructorGuard, signed, width, decimals); } @@ -215,9 +201,8 @@ export class FixedNumber { } #checkFormat(other: FixedNumber): void { - if (this.format.name !== other.format.name) { - throwArgumentError("incompatible format; use fixedNumber.toFormat", "other", other); - } + assertArgument(this.format.name === other.format.name, + "incompatible format; use fixedNumber.toFormat", "other", other); } /** @@ -288,9 +273,8 @@ export class FixedNumber { const comps = this.toString().split("."); if (comps.length === 1) { comps.push("0"); } - if (decimals < 0 || decimals > 80 || (decimals % 1)) { - throwArgumentError("invalid decimal count", "decimals", decimals); - } + assertArgument(Number.isInteger(decimals) && decimals >= 0 && decimals <= 80, + "invalid decimal count", "decimals", decimals); if (comps[1].length <= decimals) { return this; } @@ -391,7 +375,7 @@ export class FixedNumber { } } - return throwArgumentError("invalid FixedNumber value", "value", value); + assertArgument(false, "invalid FixedNumber value", "value", value); } static isFixedNumber(value: any): value is FixedNumber { diff --git a/src.ts/utils/index.ts b/src.ts/utils/index.ts index e375eb099..4d0cea914 100644 --- a/src.ts/utils/index.ts +++ b/src.ts/utils/index.ts @@ -25,7 +25,7 @@ export { export { isCallException, isError, - makeError, throwError, throwArgumentError, + makeError, throwError, assertArgument, assertArgumentCount, assertPrivate, assertNormalize } from "./errors.js" @@ -53,7 +53,6 @@ export { getStore, setStore} from "./storage.js"; export { formatEther, parseEther, formatUnits, parseUnits } from "./units.js"; export { - _toEscapedUtf8String, toUtf8Bytes, toUtf8CodePoints, toUtf8String, diff --git a/src.ts/utils/maths.ts b/src.ts/utils/maths.ts index 8fca888f7..ddf37097e 100644 --- a/src.ts/utils/maths.ts +++ b/src.ts/utils/maths.ts @@ -1,5 +1,5 @@ import { hexlify, isBytesLike } from "./data.js"; -import { throwArgumentError } from "./errors.js"; +import { assertArgument } from "./errors.js"; import type { BytesLike } from "./data.js"; @@ -68,11 +68,8 @@ export function getBigInt(value: BigNumberish, name?: string): bigint { switch (typeof(value)) { case "bigint": return value; case "number": - if (!Number.isInteger(value)) { - throwArgumentError("underflow", name || "value", value); - } else if (value < -maxValue || value > maxValue) { - throwArgumentError("overflow", name || "value", value); - } + assertArgument(Number.isInteger(value), "underflow", name || "value", value); + assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value); return BigInt(value); case "string": try { @@ -81,10 +78,10 @@ export function getBigInt(value: BigNumberish, name?: string): bigint { } return BigInt(value); } catch(e: any) { - throwArgumentError(`invalid BigNumberish string: ${ e.message }`, name || "value", value); + assertArgument(false, `invalid BigNumberish string: ${ e.message }`, name || "value", value); } } - return throwArgumentError("invalid BigNumberish value", name || "value", value); + assertArgument(false, "invalid BigNumberish value", name || "value", value); } @@ -114,25 +111,20 @@ export function toBigInt(value: BigNumberish | Uint8Array): bigint { export function getNumber(value: BigNumberish, name?: string): number { switch (typeof(value)) { case "bigint": - if (value < -maxValue || value > maxValue) { - throwArgumentError("overflow", name || "value", value); - } + assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value); return Number(value); case "number": - if (!Number.isInteger(value)) { - throwArgumentError("underflow", name || "value", value); - } else if (value < -maxValue || value > maxValue) { - throwArgumentError("overflow", name || "value", value); - } + assertArgument(Number.isInteger(value), "underflow", name || "value", value); + assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value); return value; case "string": try { return getNumber(BigInt(value), name); } catch(e: any) { - throwArgumentError(`invalid numeric string: ${ e.message }`, name || "value", value); + assertArgument(false, `invalid numeric string: ${ e.message }`, name || "value", value); } } - return throwArgumentError("invalid numeric value", name || "value", value); + assertArgument(false, "invalid numeric value", name || "value", value); } diff --git a/src.ts/utils/rlp-decode.ts b/src.ts/utils/rlp-decode.ts index 01d5d095d..a75776200 100644 --- a/src.ts/utils/rlp-decode.ts +++ b/src.ts/utils/rlp-decode.ts @@ -1,7 +1,7 @@ //See: https://github.com/ethereum/wiki/wiki/RLP import { hexlify } from "./data.js"; -import { throwArgumentError, throwError } from "./errors.js"; +import { assertArgument, throwError } from "./errors.js"; import { getBytes } from "./data.js"; import type { BytesLike, RlpStructuredData } from "./index.js"; @@ -104,9 +104,7 @@ function _decode(data: Uint8Array, offset: number): { consumed: number, result: export function decodeRlp(_data: BytesLike): RlpStructuredData { const data = getBytes(_data, "data"); const decoded = _decode(data, 0); - if (decoded.consumed !== data.length) { - throwArgumentError("unexpected junk after rlp payload", "data", _data); - } + assertArgument(decoded.consumed === data.length, "unexpected junk after rlp payload", "data", _data); return decoded.result; } diff --git a/src.ts/utils/units.ts b/src.ts/utils/units.ts index 801a4620d..217dbc5a9 100644 --- a/src.ts/utils/units.ts +++ b/src.ts/utils/units.ts @@ -1,5 +1,5 @@ import { formatFixed, parseFixed } from "./fixednumber.js"; -import { throwArgumentError } from "./errors.js"; +import { assertArgument } from "./errors.js"; import type { BigNumberish, Numeric } from "../utils/index.js"; @@ -23,7 +23,7 @@ const names = [ export function formatUnits(value: BigNumberish, unit?: string | Numeric): string { if (typeof(unit) === "string") { const index = names.indexOf(unit); - if (index === -1) { throwArgumentError("invalid unit", "unit", unit); } + assertArgument(index >= 0, "invalid unit", "unit", unit); unit = 3 * index; } return formatFixed(value, (unit != null) ? unit: 18); @@ -35,15 +35,14 @@ export function formatUnits(value: BigNumberish, unit?: string | Numeric): strin * or the name of a unit (e.g. ``"gwei"`` for 9 decimal places). */ export function parseUnits(value: string, unit?: string | Numeric): bigint { - if (typeof(value) !== "string") { - throwArgumentError("value must be a string", "value", value); - } + assertArgument(typeof(value) === "string", "value must be a string", "value", value); if (typeof(unit) === "string") { const index = names.indexOf(unit); - if (index === -1) { throwArgumentError("invalid unit", "unit", unit); } + assertArgument(index >= 0, "invalid unit", "unit", unit); unit = 3 * index; } + return parseFixed(value, (unit != null) ? unit: 18); } diff --git a/src.ts/utils/utf8.ts b/src.ts/utils/utf8.ts index 4b2e45d1e..938c7c067 100644 --- a/src.ts/utils/utf8.ts +++ b/src.ts/utils/utf8.ts @@ -1,5 +1,5 @@ import { getBytes } from "./data.js"; -import { assertNormalize, throwArgumentError } from "./errors.js"; +import { assertArgument, assertNormalize } from "./errors.js"; import type { BytesLike } from "./index.js"; @@ -44,10 +44,8 @@ export type Utf8ErrorReason = export type Utf8ErrorFunc = (reason: Utf8ErrorReason, offset: number, bytes: ArrayLike, output: Array, badCodepoint?: number) => number; - - function errorFunc(reason: Utf8ErrorReason, offset: number, bytes: ArrayLike, output: Array, badCodepoint?: number): number { - return throwArgumentError(`invalid codepoint at offset ${ offset }; ${ reason }`, "bytes", bytes); + assertArgument(false, `invalid codepoint at offset ${ offset }; ${ reason }`, "bytes", bytes); } function ignoreFunc(reason: Utf8ErrorReason, offset: number, bytes: ArrayLike, output: Array, badCodepoint?: number): number { @@ -215,9 +213,8 @@ export function toUtf8Bytes(str: string, form?: UnicodeNormalizationForm): Uint8 i++; const c2 = str.charCodeAt(i); - if (i >= str.length || (c2 & 0xfc00) !== 0xdc00) { - throw new Error("invalid utf-8 string"); - } + assertArgument(i < str.length && ((c2 & 0xfc00) === 0xdc00), + "invalid surrogate pair", "str", str); // Surrogate Pair const pair = 0x10000 + ((c & 0x03ff) << 10) + (c2 & 0x03ff); @@ -236,37 +233,6 @@ export function toUtf8Bytes(str: string, form?: UnicodeNormalizationForm): Uint8 return new Uint8Array(result); }; -function escapeChar(value: number): string { - const hex = ("0000" + value.toString(16)); - return "\\u" + hex.substring(hex.length - 4); -} - -export function _toEscapedUtf8String(bytes: BytesLike, onError?: Utf8ErrorFunc): string { - return '"' + getUtf8CodePoints(bytes, onError).map((codePoint) => { - if (codePoint < 256) { - switch (codePoint) { - case 8: return "\\b"; - case 9: return "\\t"; - case 10: return "\\n" - case 13: return "\\r"; - case 34: return "\\\""; - case 92: return "\\\\"; - } - - if (codePoint >= 32 && codePoint < 127) { - return String.fromCharCode(codePoint); - } - } - - if (codePoint <= 0xffff) { - return escapeChar(codePoint); - } - - codePoint -= 0x10000; - return escapeChar(((codePoint >> 10) & 0x3ff) + 0xd800) + escapeChar((codePoint & 0x3ff) + 0xdc00); - }).join("") + '"'; -} - export function _toUtf8String(codePoints: Array): string { return codePoints.map((codePoint) => { if (codePoint <= 0xffff) { diff --git a/src.ts/wallet/base-wallet.ts b/src.ts/wallet/base-wallet.ts index d52742685..08184cc59 100644 --- a/src.ts/wallet/base-wallet.ts +++ b/src.ts/wallet/base-wallet.ts @@ -3,7 +3,7 @@ import { hashMessage, TypedDataEncoder } from "../hash/index.js"; import { AbstractSigner } from "../providers/index.js"; import { computeAddress, Transaction } from "../transaction/index.js"; import { - defineProperties, resolveProperties, throwArgumentError, throwError + defineProperties, resolveProperties, assertArgument, throwError } from "../utils/index.js"; import type { SigningKey } from "../crypto/index.js"; @@ -46,9 +46,8 @@ export class BaseWallet extends AbstractSigner { if (from != null) { tx.from = from; } if (tx.from != null) { - if (getAddress((tx.from)) !== this.address) { - throwArgumentError("transaction from address mismatch", "tx.from", tx.from); - } + assertArgument(getAddress((tx.from)) === this.address, + "transaction from address mismatch", "tx.from", tx.from); delete tx.from; } diff --git a/src.ts/wallet/hdwallet.ts b/src.ts/wallet/hdwallet.ts index 35fa494e6..28292aa36 100644 --- a/src.ts/wallet/hdwallet.ts +++ b/src.ts/wallet/hdwallet.ts @@ -5,7 +5,7 @@ import { concat, dataSlice, decodeBase58, defineProperties, encodeBase58, getBytes, hexlify, getNumber, toBigInt, toHex, - assertPrivate, throwArgumentError, throwError + assertPrivate, assertArgument, throwError } from "../utils/index.js"; import { langEn } from "../wordlists/lang-en.js"; @@ -216,9 +216,8 @@ export class HDNodeWallet extends BaseWallet { static fromExtendedKey(extendedKey: string): HDNodeWallet | HDNodeVoidWallet { const bytes = getBytes(decodeBase58(extendedKey)); // @TODO: redact - if (bytes.length !== 82 || encodeBase58Check(bytes.slice(0, 78)) !== extendedKey) { - throwArgumentError("invalid extended key", "extendedKey", "[ REDACTED ]"); - } + assertArgument(bytes.length === 82 || encodeBase58Check(bytes.slice(0, 78)) === extendedKey, + "invalid extended key", "extendedKey", "[ REDACTED ]"); const depth = bytes[4]; const parentFingerprint = hexlify(bytes.slice(5, 9)); @@ -242,7 +241,7 @@ export class HDNodeWallet extends BaseWallet { } - return throwArgumentError("invalid extended key prefix", "extendedKey", "[ REDACTED ]"); + assertArgument(false, "invalid extended key prefix", "extendedKey", "[ REDACTED ]"); } static createRandom(password: string = "", path: null | string = defaultPath, wordlist: Wordlist = langEn): HDNodeWallet { @@ -342,9 +341,7 @@ export class HDNodeWalletManager { export function getAccountPath(_index: Numeric): string { const index = getNumber(_index, "index"); - if (index < 0 || index >= HardenedBit) { - throwArgumentError("invalid account index", "index", index); - } + assertArgument(index >= 0 && index < HardenedBit, "invalid account index", "index", index); return `m/44'/60'/${ index }'/0/0`; } diff --git a/src.ts/wallet/json-crowdsale.ts b/src.ts/wallet/json-crowdsale.ts index ef14647f1..47b6f33cf 100644 --- a/src.ts/wallet/json-crowdsale.ts +++ b/src.ts/wallet/json-crowdsale.ts @@ -3,7 +3,7 @@ import { CBC, pkcs7Strip } from "aes-js"; import { getAddress } from "../address/index.js"; import { pbkdf2 } from "../crypto/index.js"; import { id } from "../hash/index.js"; -import { getBytes, throwArgumentError } from "../utils/index.js"; +import { getBytes, assertArgument } from "../utils/index.js"; import { getPassword, looseArrayify, spelunk } from "./utils.js"; @@ -31,9 +31,7 @@ export function decryptCrowdsaleJson(json: string, _password: string | Uint8Arra // Encrypted Seed const encseed = looseArrayify(spelunk(data, "encseed:string!")); - if (!encseed || (encseed.length % 16) !== 0) { - throwArgumentError("invalid encseed", "json", json); - } + assertArgument(encseed && (encseed.length % 16) === 0, "invalid encseed", "json", json); const key = getBytes(pbkdf2(password, password, 2000, 32, "sha256")).slice(0, 16); diff --git a/src.ts/wallet/json-keystore.ts b/src.ts/wallet/json-keystore.ts index ea8c410aa..d61246ee2 100644 --- a/src.ts/wallet/json-keystore.ts +++ b/src.ts/wallet/json-keystore.ts @@ -4,7 +4,7 @@ import { getAddress } from "../address/index.js"; import { keccak256, pbkdf2, randomBytes, scrypt, scryptSync } from "../crypto/index.js"; import { computeAddress } from "../transaction/index.js"; import { - concat, getBytes, hexlify, throwArgumentError, throwError + concat, getBytes, hexlify, assertArgument, throwError } from "../utils/index.js"; import { getPassword, spelunk, uuidV4, zpad } from "./utils.js"; @@ -77,9 +77,8 @@ function getAccount(data: any, _key: string): KeystoreAccount { const ciphertext = spelunk(data, "crypto.ciphertext:data!"); const computedMAC = hexlify(keccak256(concat([ key.slice(16, 32), ciphertext ]))).substring(2); - if (computedMAC !== spelunk(data, "crypto.mac:string!").toLowerCase()) { - return throwArgumentError("incorrect password", "password", "[ REDACTED ]"); - } + assertArgument(computedMAC === spelunk(data, "crypto.mac:string!").toLowerCase(), + "incorrect password", "password", "[ REDACTED ]"); const privateKey = decrypt(data, key.slice(0, 16), ciphertext); @@ -88,9 +87,7 @@ function getAccount(data: any, _key: string): KeystoreAccount { let check = data.address.toLowerCase(); if (check.substring(0, 2) !== "0x") { check = "0x" + check; } - if (getAddress(check) !== address) { - throwArgumentError("keystore address/privateKey mismatch", "address", data.address); - } + assertArgument(getAddress(check) === address, "keystore address/privateKey mismatch", "address", data.address); } const account: KeystoreAccount = { address, privateKey }; @@ -134,7 +131,7 @@ function getKdfParams(data: any): KdfParams { const kdf = spelunk(data, "crypto.kdf:string"); if (kdf && typeof(kdf) === "string") { const throwError = function(name: string, value: any): never { - return throwArgumentError("invalid key-derivation function parameters", name, value); + assertArgument(false, "invalid key-derivation function parameters", name, value); } if (kdf.toLowerCase() === "scrypt") { @@ -173,7 +170,7 @@ function getKdfParams(data: any): KdfParams { } } - return throwArgumentError("unsupported key-derivation function", "kdf", kdf); + assertArgument(false, "unsupported key-derivation function", "kdf", kdf); } @@ -275,15 +272,11 @@ export async function encryptKeystoreJson(account: KeystoreAccount, password: st // Override initialization vector const iv = (options.iv != null) ? getBytes(options.iv, "options.iv"): randomBytes(16); - if (iv.length !== 16) { - throwArgumentError("invalid options.iv", "options.iv", options.iv); - } + assertArgument(iv.length === 16, "invalid options.iv", "options.iv", options.iv); // Override the uuid const uuidRandom = (options.uuid != null) ? getBytes(options.uuid, "options.uuid"): randomBytes(16); - if (uuidRandom.length !== 16) { - throwArgumentError("invalid options.uuid", "options.uuid", options.iv); - } + assertArgument(uuidRandom.length === 16, "invalid options.uuid", "options.uuid", options.iv); if (uuidRandom.length !== 16) { throw new Error("invalid uuid"); } // Override the scrypt password-based key derivation function parameters diff --git a/src.ts/wallet/mnemonic.ts b/src.ts/wallet/mnemonic.ts index 3a9e0cf71..7f37768e4 100644 --- a/src.ts/wallet/mnemonic.ts +++ b/src.ts/wallet/mnemonic.ts @@ -1,6 +1,6 @@ import { pbkdf2, sha256 } from "../crypto/index.js"; import { - defineProperties, getBytes, hexlify, assertNormalize, assertPrivate, throwArgumentError, toUtf8Bytes + defineProperties, getBytes, hexlify, assertNormalize, assertPrivate, assertArgument, toUtf8Bytes } from "../utils/index.js"; import { langEn } from "../wordlists/lang-en.js"; @@ -25,18 +25,15 @@ function mnemonicToEntropy(mnemonic: string, wordlist: null | Wordlist = langEn) if (wordlist == null) { wordlist = langEn; } const words = wordlist.split(mnemonic); - if ((words.length % 3) !== 0 || words.length < 12 || words.length > 24) { - throwArgumentError("invalid mnemonic length", "mnemonic", "[ REDACTED ]"); - } + assertArgument((words.length % 3) === 0 && words.length >= 12 && words.length <= 24, + "invalid mnemonic length", "mnemonic", "[ REDACTED ]"); const entropy = new Uint8Array(Math.ceil(11 * words.length / 8)); let offset = 0; for (let i = 0; i < words.length; i++) { let index = wordlist.getWordIndex(words[i].normalize("NFKD")); - if (index === -1) { - throwArgumentError(`invalid mnemonic word at index ${ i }`, "mnemonic", "[ REDACTED ]"); - } + assertArgument(index >= 0, `invalid mnemonic word at index ${ i }`, "mnemonic", "[ REDACTED ]"); for (let bit = 0; bit < 11; bit++) { if (index & (1 << (10 - bit))) { @@ -54,17 +51,15 @@ function mnemonicToEntropy(mnemonic: string, wordlist: null | Wordlist = langEn) const checksum = getBytes(sha256(entropy.slice(0, entropyBits / 8)))[0] & checksumMask; - if (checksum !== (entropy[entropy.length - 1] & checksumMask)) { - throwArgumentError("invalid mnemonic checksum", "mnemonic", "[ REDACTED ]"); - } + assertArgument(checksum === (entropy[entropy.length - 1] & checksumMask), + "invalid mnemonic checksum", "mnemonic", "[ REDACTED ]"); return hexlify(entropy.slice(0, entropyBits / 8)); } function entropyToMnemonic(entropy: Uint8Array, wordlist: null | Wordlist = langEn): string { - if ((entropy.length % 4) || entropy.length < 16 || entropy.length > 32) { - throwArgumentError("invalid entropy size", "entropy", "[ REDACTED ]"); - } + assertArgument((entropy.length % 4) === 0 && entropy.length >= 16 && entropy.length <= 32, + "invalid entropy size", "entropy", "[ REDACTED ]"); if (wordlist == null) { wordlist = langEn; } diff --git a/src.ts/wallet/utils.ts b/src.ts/wallet/utils.ts index 35adebe75..dfa074886 100644 --- a/src.ts/wallet/utils.ts +++ b/src.ts/wallet/utils.ts @@ -1,5 +1,5 @@ import { - getBytes, getBytesCopy, hexlify, throwArgumentError, toUtf8Bytes + getBytes, getBytesCopy, hexlify, assertArgument, toUtf8Bytes } from "../utils/index.js"; import type { BytesLike } from "../utils/index.js"; @@ -28,9 +28,8 @@ export function getPassword(password: string | Uint8Array): Uint8Array { export function spelunk(object: any, _path: string): T { const match = _path.match(/^([a-z0-9$_.-]*)(:([a-z]+))?(!)?$/i); - if (match == null) { - return throwArgumentError("invalid path", "path", _path); - } + assertArgument(match != null, "invalid path", "path", _path); + const path = match[1]; const type = match[3]; const reqd = (match[4] === "!"); @@ -60,9 +59,7 @@ export function spelunk(object: any, _path: string): T { if (cur == null) { break; } } - if (reqd && cur == null) { - throwArgumentError("missing required value", "path", path); - } + assertArgument(!reqd || cur != null, "missing required value", "path", path); if (type && cur != null) { if (type === "int") { @@ -86,7 +83,7 @@ export function spelunk(object: any, _path: string): T { if (type === "array" && Array.isArray(cur)) { return cur; } if (type === typeof(cur)) { return cur; } - throwArgumentError(`wrong type found for ${ type } `, "path", path); + assertArgument(false, `wrong type found for ${ type } `, "path", path); } return cur; diff --git a/src.ts/wallet/wallet.ts b/src.ts/wallet/wallet.ts index dfd393ca0..a58b63976 100644 --- a/src.ts/wallet/wallet.ts +++ b/src.ts/wallet/wallet.ts @@ -1,6 +1,6 @@ import { randomBytes, SigningKey } from "../crypto/index.js"; import { computeAddress } from "../transaction/index.js"; -import { isHexString, throwArgumentError } from "../utils/index.js"; +import { isHexString, assertArgument } from "../utils/index.js"; import { BaseWallet } from "./base-wallet.js"; import { HDNodeWallet } from "./hdwallet.js"; @@ -89,9 +89,7 @@ export class Wallet extends BaseWallet { // A signing key if (signingKey == null) { signingKey = trySigningKey(key); } - if (signingKey == null) { - throwArgumentError("invalid key", "key", "[ REDACTED ]"); - } + assertArgument(signingKey != null, "invalid key", "key", "[ REDACTED ]"); super(signingKey as SigningKey, provider); this.#mnemonic = mnemonic; @@ -123,13 +121,12 @@ export class Wallet extends BaseWallet { if (progress) { progress(1); await stall(0); } } else { - return throwArgumentError("invalid JSON wallet", "json", "[ REDACTED ]"); + assertArgument(false, "invalid JSON wallet", "json", "[ REDACTED ]"); } const wallet = new Wallet(account.privateKey); - if (wallet.address !== account.address) { - throwArgumentError("address/privateKey mismatch", "json", "[ REDACTED ]"); - } + assertArgument(wallet.address === account.address, + "address/privateKey mismatch", "json", "[ REDACTED ]"); // @TODO: mnemonic return wallet; } @@ -141,13 +138,12 @@ export class Wallet extends BaseWallet { } else if (isCrowdsaleJson(json)) { account = decryptCrowdsaleJson(json, password); } else { - return throwArgumentError("invalid JSON wallet", "json", "[ REDACTED ]"); + assertArgument(false, "invalid JSON wallet", "json", "[ REDACTED ]"); } const wallet = new Wallet(account.privateKey); - if (wallet.address !== account.address) { - throwArgumentError("address/privateKey mismatch", "json", "[ REDACTED ]"); - } + assertArgument(wallet.address === account.address, + "address/privateKey mismatch", "json", "[ REDACTED ]"); // @TODO: mnemonic return wallet; } diff --git a/src.ts/wordlists/lang-ja.ts b/src.ts/wordlists/lang-ja.ts index 4f328bf04..b290e8776 100644 --- a/src.ts/wordlists/lang-ja.ts +++ b/src.ts/wordlists/lang-ja.ts @@ -1,6 +1,6 @@ import { id } from "../hash/index.js"; import { - hexlify, throwArgumentError, toUtf8Bytes, toUtf8String + hexlify, assertArgument, toUtf8Bytes, toUtf8String } from "../utils/index.js"; import { Wordlist } from "./wordlist.js"; @@ -136,9 +136,8 @@ class LangJa extends Wordlist { getWord(index: number): string { const words = loadWords(); - if (index < 0 || index >= words.length) { - throwArgumentError(`invalid word index: ${ index }`, "index", index); - } + assertArgument(index >= 0 && index < words.length, + `invalid word index: ${ index }`, "index", index); return words[index]; } diff --git a/src.ts/wordlists/lang-ko.ts b/src.ts/wordlists/lang-ko.ts index 77c20f926..12b6562d7 100644 --- a/src.ts/wordlists/lang-ko.ts +++ b/src.ts/wordlists/lang-ko.ts @@ -1,5 +1,5 @@ import { id } from "../hash/index.js"; -import { throwArgumentError, toUtf8String } from "../utils/index.js"; +import { assertArgument, toUtf8String } from "../utils/index.js"; import { Wordlist } from "./wordlist.js"; @@ -69,9 +69,8 @@ class LangKo extends Wordlist { getWord(index: number): string { const words = loadWords(); - if (index < 0 || index >= words.length) { - throwArgumentError(`invalid word index: ${ index }`, "index", index); - } + assertArgument(index >= 0 && index < words.length, + `invalid word index: ${ index }`, "index", index); return words[index]; } diff --git a/src.ts/wordlists/lang-zh.ts b/src.ts/wordlists/lang-zh.ts index 2b8a4c1d3..e88d5b8ee 100644 --- a/src.ts/wordlists/lang-zh.ts +++ b/src.ts/wordlists/lang-zh.ts @@ -1,5 +1,5 @@ import { id } from "../hash/index.js"; -import { throwArgumentError, toUtf8String } from "../utils/index.js"; +import { assertArgument, toUtf8String } from "../utils/index.js"; import { Wordlist } from "./wordlist.js"; @@ -63,9 +63,8 @@ class LangZh extends Wordlist { getWord(index: number): string { const words = loadWords(this.locale); - if (index < 0 || index >= words.length) { - throwArgumentError(`invalid word index: ${ index }`, "index", index); - } + assertArgument(index >= 0 && index < words.length, + `invalid word index: ${ index }`, "index", index); return words[index]; } diff --git a/src.ts/wordlists/wordlist-owl.ts b/src.ts/wordlists/wordlist-owl.ts index ac2dac611..541f7ebc6 100644 --- a/src.ts/wordlists/wordlist-owl.ts +++ b/src.ts/wordlists/wordlist-owl.ts @@ -3,7 +3,7 @@ // data files to be consumed by this class import { id } from "../hash/index.js"; -import { throwArgumentError } from "../utils/index.js"; +import { assertArgument } from "../utils/index.js"; import { decodeOwl } from "./decode-owl.js"; import { Wordlist } from "./wordlist.js"; @@ -45,9 +45,7 @@ export class WordlistOwl extends Wordlist { getWord(index: number): string { const words = this.#loadWords(); - if (index < 0 || index >= words.length) { - throwArgumentError(`invalid word index: ${ index }`, "index", index); - } + assertArgument(index >= 0 && index < words.length, `invalid word index: ${ index }`, "index", index); return words[index]; }