diff --git a/packages/abi/src.ts/fragments.ts b/packages/abi/src.ts/fragments.ts index 92b0dcf0..cfc741a0 100644 --- a/packages/abi/src.ts/fragments.ts +++ b/packages/abi/src.ts/fragments.ts @@ -2,7 +2,7 @@ import { BigNumber } from "@ethersproject/bignumber"; import * as errors from "@ethersproject/errors"; -import { defineReadOnly, isNamedInstance } from "@ethersproject/properties"; +import { defineReadOnly } from "@ethersproject/properties"; export interface JsonFragmentType { @@ -238,6 +238,8 @@ export class ParamType { readonly arrayLength: number; readonly arrayChildren: ParamType; + readonly _isParamType: boolean; + constructor(constructorGuard: any, params: any) { if (constructorGuard !== _constructorGuard) { throw new Error("use fromString"); } populate(this, params); @@ -259,6 +261,10 @@ export class ParamType { baseType: ((this.components != null) ? "tuple": this.type) }); } + + this._isParamType = true; + + Object.freeze(this); } // Format the parameter fragment @@ -298,7 +304,7 @@ export class ParamType { } static fromObject(value: JsonFragmentType | ParamType): ParamType { - if (isNamedInstance(ParamType, value)) { return value; } + if (ParamType.isParamType(value)) { return value; } return new ParamType(_constructorGuard, { name: (value.name || null), @@ -320,6 +326,10 @@ export class ParamType { return ParamTypify(parseParamType(value, !!allowIndexed)); } + + static isParamType(value: any): value is ParamType { + return !!(value != null && value._isParamType); + } }; function parseParams(value: string, allowIndex: boolean): Array { @@ -332,9 +342,15 @@ export abstract class Fragment { readonly name: string; readonly inputs: Array; + readonly _isFragment: boolean; + constructor(constructorGuard: any, params: any) { if (constructorGuard !== _constructorGuard) { throw new Error("use a static from method"); } populate(this, params); + + this._isFragment = true; + + Object.freeze(this); } // @TOOD: move logic to sub-classes; make this abstract @@ -370,14 +386,17 @@ export abstract class Fragment { } static from(value: Fragment | JsonFragment | string): Fragment { + if (Fragment.isFragment(value)) { return value; } + if (typeof(value) === "string") { return Fragment.fromString(value); } + return Fragment.fromObject(value); } static fromObject(value: Fragment | JsonFragment): Fragment { - if (isNamedInstance(Fragment, value)) { return value; } + if (Fragment.isFragment(value)) { return value; } if (value.type === "function") { return FunctionFragment.fromObject(value); @@ -412,6 +431,10 @@ export abstract class Fragment { throw new Error("unknown fragment"); } + + static isFragment(value: any): value is Fragment { + return !!(value && value._isFragment); + } } export class EventFragment extends Fragment { @@ -425,7 +448,7 @@ export class EventFragment extends Fragment { } static fromObject(value: JsonFragment | EventFragment): EventFragment { - if (isNamedInstance(EventFragment, value)) { return value; } + if (EventFragment.isEventFragment(value)) { return value; } if (value.type !== "event") { throw new Error("invalid event object - " + value.type); } @@ -462,6 +485,10 @@ export class EventFragment extends Fragment { type: "event" }); } + + static isEventFragment(value: any): value is EventFragment { + return (value && value._isFragment && value.type === "event"); + } } function parseGas(value: string, params: any): string { @@ -528,7 +555,7 @@ export class ConstructorFragment extends Fragment { } static fromObject(value: ConstructorFragment | JsonFragment): ConstructorFragment { - if (isNamedInstance(ConstructorFragment, value)) { return value; } + if (ConstructorFragment.isConstructorFragment(value)) { return value; } if (value.type !== "constructor") { throw new Error("invalid constructor object - " + value.type); } @@ -557,6 +584,9 @@ export class ConstructorFragment extends Fragment { return ConstructorFragment.fromObject(params); } + static isConstructorFragment(value: any): value is ConstructorFragment { + return (value && value._isFragment && value.type === "constructor"); + } } export class FunctionFragment extends ConstructorFragment { @@ -571,7 +601,7 @@ export class FunctionFragment extends ConstructorFragment { } static fromObject(value: FunctionFragment | JsonFragment): FunctionFragment { - if (isNamedInstance(FunctionFragment, value)) { return value; } + if (FunctionFragment.isFunctionFragment(value)) { return value; } if (value.type !== "function") { throw new Error("invalid function object - " + value.type); } @@ -619,6 +649,10 @@ export class FunctionFragment extends ConstructorFragment { return FunctionFragment.fromObject(params); } + + static isFunctionFragment(value: any): value is FunctionFragment { + return (value && value._isFragment && value.type === "function"); + } } //export class ErrorFragment extends Fragment { diff --git a/packages/abi/src.ts/interface.ts b/packages/abi/src.ts/interface.ts index 32d1a692..d4a5c72b 100644 --- a/packages/abi/src.ts/interface.ts +++ b/packages/abi/src.ts/interface.ts @@ -6,7 +6,7 @@ import { arrayify, BytesLike, concat, hexDataSlice, hexlify, hexZeroPad, isHexSt import { id } from "@ethersproject/hash"; import { keccak256 } from "@ethersproject/keccak256" import * as errors from "@ethersproject/errors"; -import { defineReadOnly, Description, isNamedInstance } from "@ethersproject/properties"; +import { defineReadOnly, Description } from "@ethersproject/properties"; import { AbiCoder, defaultAbiCoder } from "./abi-coder"; import { ConstructorFragment, EventFragment, Fragment, FunctionFragment, JsonFragment, ParamType } from "./fragments"; @@ -31,6 +31,10 @@ export class TransactionDescription extends Description { export class Indexed extends Description { readonly hash: string; + + static isIndexed(value: any): value is Indexed { + return !!(value && value._isIndexed); + } } export class Result { @@ -51,6 +55,8 @@ export class Interface { readonly _abiCoder: AbiCoder; + static _isInterface: boolean; + constructor(fragments: string | Array) { errors.checkNew(new.target, Interface); @@ -62,9 +68,6 @@ export class Interface { } defineReadOnly(this, "fragments", abi.map((fragment) => { - if (isNamedInstance(Fragment, fragment)) { - return fragment - } return Fragment.from(fragment); }).filter((fragment) => (fragment != null))); @@ -122,6 +125,8 @@ export class Interface { if (!this.deploy) { defineReadOnly(this, "deploy", ConstructorFragment.from( { type: "constructor" } )); } + + defineReadOnly(this, "_isInterface", true); } static getAbiCoder(): AbiCoder { @@ -325,10 +330,10 @@ export class Interface { eventFragment.inputs.forEach((param, index) => { if (param.indexed) { if (resultIndexed == null) { - result[index] = new Indexed({ hash: null }); + result[index] = new Indexed({ _isIndexed: true, hash: null }); } else if (dynamic[index]) { - result[index] = new Indexed({ hash: resultIndexed[indexedIndex++] }); + result[index] = new Indexed({ _isIndexed: true, hash: resultIndexed[indexedIndex++] }); } else { result[index] = resultIndexed[indexedIndex++]; @@ -387,6 +392,10 @@ export class Interface { return new Interface(value); } */ + + static isInterface(value: any): value is Interface { + return !!(value && value._isInterface); + } } function getFragment(hash: string, calcFunc: (f: Fragment) => string, items: { [ sig: string ]: Fragment } ) { diff --git a/packages/abstract-provider/src.ts/index.ts b/packages/abstract-provider/src.ts/index.ts index 578707bb..775a11f5 100644 --- a/packages/abstract-provider/src.ts/index.ts +++ b/packages/abstract-provider/src.ts/index.ts @@ -5,7 +5,7 @@ import { BytesLike, isHexString } from "@ethersproject/bytes"; import * as errors from "@ethersproject/errors"; import { checkAbstract } from "@ethersproject/errors"; import { Network } from "@ethersproject/networks"; -import { defineReadOnly } from "@ethersproject/properties"; +import { Description, defineReadOnly } from "@ethersproject/properties"; import { Transaction } from "@ethersproject/transactions"; import { OnceBlockable } from "@ethersproject/web"; @@ -128,11 +128,13 @@ export interface FilterByBlockHash extends EventFilter { // call(transaction: TransactionRequest): Promise; //}; -export class ForkEvent { +export abstract class ForkEvent extends Description { readonly expiry: number; - constructor(expiry?: number) { - defineReadOnly(this, "expiry", expiry || 0); + readonly _isForkEvent: boolean; + + static isForkEvent(value: any): value is ForkEvent { + return !!(value && value._isForkEvent); } } @@ -143,8 +145,13 @@ export class BlockForkEvent extends ForkEvent { if (!isHexString(blockhash, 32)) { errors.throwArgumentError("invalid blockhash", "blockhash", blockhash); } - super(expiry); - defineReadOnly(this, "blockhash", blockhash); + + super({ + _isForkEvent: true, + _isBlockForkEvent: true, + expiry: (expiry || 0), + blockHash: blockhash + }); } } @@ -155,8 +162,13 @@ export class TransactionForkEvent extends ForkEvent { if (!isHexString(hash, 32)) { errors.throwArgumentError("invalid transaction hash", "hash", hash); } - super(expiry); - defineReadOnly(this, "hash", hash); + + super({ + _isForkEvent: true, + _isTransactionForkEvent: true, + expiry: (expiry || 0), + hash: hash + }); } } @@ -171,9 +183,14 @@ export class TransactionOrderForkEvent extends ForkEvent { if (!isHexString(afterHash, 32)) { errors.throwArgumentError("invalid transaction hash", "afterHash", afterHash); } - super(expiry); - defineReadOnly(this, "beforeHash", beforeHash); - defineReadOnly(this, "afterHash", afterHash); + + super({ + _isForkEvent: true, + _isTransactionOrderForkEvent: true, + expiry: (expiry || 0), + beforeHash: beforeHash, + afterHash: afterHash + }); } } @@ -239,8 +256,15 @@ export abstract class Provider implements OnceBlockable { // @TODO: This *could* be implemented here, but would pull in events... abstract waitForTransaction(transactionHash: string, timeout?: number): Promise; + readonly _isProvider: boolean; + constructor() { checkAbstract(new.target, Provider); + defineReadOnly(this, "_isProvider", true); + } + + static isProvider(value: any): value is Provider { + return !!(value && value._isProvider); } /* diff --git a/packages/abstract-signer/src.ts/index.ts b/packages/abstract-signer/src.ts/index.ts index 1af05aae..31dd4de5 100644 --- a/packages/abstract-signer/src.ts/index.ts +++ b/packages/abstract-signer/src.ts/index.ts @@ -51,11 +51,14 @@ export abstract class Signer { // This MAY throw if changing providers is not supported. abstract connect(provider: Provider): Signer; + readonly _isSigner: boolean; + /////////////////// // Sub-classes MUST call super constructor() { errors.checkAbstract(new.target, Signer); + defineReadOnly(this, "_isSigner", true); } @@ -179,6 +182,10 @@ export abstract class Signer { operation: (operation || "_checkProvider") }); } } + + static isSigner(value: any): value is Signer { + return !!(value && value._isSigner); + } } export class VoidSigner extends Signer { diff --git a/packages/bignumber/src.ts/bignumber.ts b/packages/bignumber/src.ts/bignumber.ts index 990e8e60..97e78411 100644 --- a/packages/bignumber/src.ts/bignumber.ts +++ b/packages/bignumber/src.ts/bignumber.ts @@ -11,7 +11,6 @@ import * as BN from "bn.js"; import { Bytes, Hexable, hexlify, isBytes, isHexString } from "@ethersproject/bytes"; -import { defineReadOnly, isNamedInstance } from "@ethersproject/properties"; import * as errors from "@ethersproject/errors"; @@ -22,18 +21,20 @@ const MAX_SAFE = 0x1fffffffffffff; export type BigNumberish = BigNumber | Bytes | string | number; -/* -export function isBigNumberLike(value: any): value is BigNumberish { - return (BigNumber.isBigNumber(value) || - (!!((value).toHexString)) || - isBytes(value) || - value.match(/^-?([0-9]+|0x[0-9a-f]+)$/i) || - typeof(value) === "number"); +export function isBigNumberish(value: any): value is BigNumberish { + return (value != null) && ( + BigNumber.isBigNumber(value) || + (typeof(value) === "number" && (value % 1) === 0) || + (typeof(value) === "string" && !!value.match(/^-?[0-9]+$/)) || + isHexString(value) || + (typeof(value) === "bigint") || + isBytes(value) + ); } -*/ export class BigNumber implements Hexable { readonly _hex: string; + readonly _isBigNumber: boolean; constructor(constructorGuard: any, hex: string) { errors.checkNew(new.target, BigNumber); @@ -44,7 +45,10 @@ export class BigNumber implements Hexable { }); } - defineReadOnly(this, "_hex", hex); + this._hex = hex; + this._isBigNumber = true; + + Object.freeze(this); } fromTwos(value: number): BigNumber { @@ -189,104 +193,10 @@ export class BigNumber implements Hexable { } static isBigNumber(value: any): value is BigNumber { - return isNamedInstance(this, value); + return !!(value && value._isBigNumber); } } -/* -export function bigNumberify(value: BigNumberish): BigNumber { - if (BigNumber.isBigNumber(value)) { return value; } - return new BigNumber(value); -} -*/ - -/* -function zeros(length) { - let result = ""; - while (result.length < length) { tens += "0"; } - return result; -} -export class FixedNumber { - readonly value: BigNumber; - readonly decimalPlaces: number; - - constructor(value: BigNumberish, decimalPlaces: number) { - defineReadOnly(this, "value", bigNumberify(value)); - defineReadOnly(this, "decimalPlaces", decimalPlaces); - } - - toString(): string { - return formatUnits(this.value, this.decimalPlaces); - } - - static fromString(value: string): FixedNumber { - let comps = value.split("."); - let decimalPlaces = 0; - if (comps.length === 2) { decimalPlaces = comps[1].length; } - return new FixedNumber(parseUnits(value, decimalPlaces), decimalPlaces); - } -*/ -/* - - readonly negative: boolean; - readonly whole: BigNumber; - readonly fraction: BigNumber; - constructor(whole: BigNumberish, fraction: BigNumberish, negative?: boolean) { - if (whole.lt(constants.Zero)) { - errors.throwError("whole component must be positive", errors.INVALID_ARGUMENT, { - argument: whole, - value: whole - }); - } - defineReadOnly(this, "whole", bigNumberify(whole)); - defineReadOnly(this, "fraction", bigNumberify(fraction)); - defineReadOnly(this, "negative", !!boolean); - } -*/ -/* - toHexString(bitWidth?: number, decimalPlaces?: number, signed?: boolean): string { - if (bitWidth == null) { bitWidth = 128; } - if (decimalPlaces == null) { decimalPlaces = 18; } - if (signed == null) { signed = true; } - return null; - } - static fromValue(value: BigNumberish, decimalPlaces: number): FixedNumber { - let negative = false; - if (value.lt(constants.Zero)) { - negative = true; - value = value.abs(); - } - let tens = bigNumberify("1" + zeros(decimalPlaces)); - return new FixedNumber(value.divide(tens), value.mod(tens), negative); - } - let negative = false; - if (value.substring(0, 1) === "-") { - negative = true; - value = value.substring(1); - } - - if (value !== "." && value !== "") { - let comps = value.split("."); - if (comps.length === 1) { - return new FixedNumber(comps[0], 0, negative); - } else if (comps.length === 2) { - if (comps[0] === "") { comps[0] = "0"; } - if (comps[1] === "") { comps[1] = "0"; } - return new FixedNumber(comps[0], comps[1], negative); - } - } - - errors.throwError("invalid fixed-point value", errors.INVALID_ARGUMENT, { - argument: "value", - value: value - }); - - return null; -*/ - -//} - - // Normalize the hex string function toHex(value: string | BN.BN): string { diff --git a/packages/bignumber/src.ts/fixednumber.ts b/packages/bignumber/src.ts/fixednumber.ts index 482f1ab7..2cbf9a18 100644 --- a/packages/bignumber/src.ts/fixednumber.ts +++ b/packages/bignumber/src.ts/fixednumber.ts @@ -2,9 +2,8 @@ import { arrayify, BytesLike, hexZeroPad, isBytes } from "@ethersproject/bytes"; import * as errors from "@ethersproject/errors"; -import { defineReadOnly, isNamedInstance } from "@ethersproject/properties"; -import { BigNumber, BigNumberish } from "./bignumber"; +import { BigNumber, BigNumberish, isBigNumberish } from "./bignumber"; const _constructorGuard = { }; @@ -115,17 +114,18 @@ export class FixedFormat { readonly width: number; readonly decimals: number; readonly name: string; - readonly _multiplier: BigNumber; + readonly _multiplier: string; constructor(constructorGuard: any, signed: boolean, width: number, decimals: number) { - defineReadOnly(this, "signed", signed); - defineReadOnly(this, "width", width); - defineReadOnly(this, "decimals", decimals); + this.signed = signed; + this.width = width; + this.decimals = decimals; - let name = (signed ? "": "u") + "fixed" + String(width) + "x" + String(decimals); - defineReadOnly(this, "name", name); + this.name = (signed ? "": "u") + "fixed" + String(width) + "x" + String(decimals); - defineReadOnly(this, "_multiplier", getMultiplier(decimals)); + this._multiplier = getMultiplier(decimals); + + Object.freeze(this); } static from(value: any): FixedFormat { @@ -170,10 +170,6 @@ export class FixedFormat { return new FixedFormat(_constructorGuard, signed, width, decimals); } - - static isInstance(value: any): value is FixedFormat { - return isNamedInstance(this, value); - } } export class FixedNumber { @@ -181,13 +177,19 @@ export class FixedNumber { readonly _hex: string; readonly _value: string; + readonly _isFixedNumber: boolean; + constructor(constructorGuard: any, hex: string, value: string, format?: FixedFormat) { errors.checkNew(new.target, FixedNumber); - defineReadOnly(this, 'format', format); - defineReadOnly(this, '_hex', hex); - defineReadOnly(this, '_value', value); - } + this.format = format; + this._hex = hex; + this._value = value; + + this._isFixedNumber = true; + + Object.freeze(this); + } _checkFormat(other: FixedNumber): void { if (this.format.name !== other.format.name) { @@ -261,7 +263,7 @@ export class FixedNumber { static fromValue(value: BigNumber, decimals?: BigNumberish, format?: FixedFormat | string): FixedNumber { // If decimals looks more like a format, and there is no format, shift the parameters - if (format == null && decimals != null && (FixedFormat.isInstance(decimals) || typeof(decimals) === "string")) { + if (format == null && decimals != null && !isBigNumberish(decimals)) { format = decimals; decimals = null; } @@ -269,15 +271,14 @@ export class FixedNumber { if (decimals == null) { decimals = 0; } if (format == null) { format = "fixed"; } - let fixedFormat = (FixedFormat.isInstance(format) ? format: FixedFormat.from(format)); - return FixedNumber.fromString(formatFixed(value, decimals), fixedFormat); + return FixedNumber.fromString(formatFixed(value, decimals), FixedFormat.from(format)); } static fromString(value: string, format?: FixedFormat | string): FixedNumber { if (format == null) { format = "fixed"; } - let fixedFormat = (FixedFormat.isInstance(format) ? format: FixedFormat.from(format)); + let fixedFormat = FixedFormat.from(format); let numeric = parseFixed(value, fixedFormat.decimals); @@ -301,7 +302,7 @@ export class FixedNumber { static fromBytes(value: BytesLike, format?: FixedFormat | string): FixedNumber { if (format == null) { format = "fixed"; } - let fixedFormat = (FixedFormat.isInstance(format) ? format: FixedFormat.from(format)); + let fixedFormat = FixedFormat.from(format); if (arrayify(value).length > fixedFormat.width / 8) { throw new Error("overflow"); @@ -338,6 +339,6 @@ export class FixedNumber { } static isFixedNumber(value: any): value is FixedNumber { - return isNamedInstance(this, value); + return !!(value && value._isFixedNumber); } } diff --git a/packages/contracts/src.ts/index.ts b/packages/contracts/src.ts/index.ts index 667a9e31..a7b40b55 100644 --- a/packages/contracts/src.ts/index.ts +++ b/packages/contracts/src.ts/index.ts @@ -8,7 +8,7 @@ import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; import { BytesLike, concat, hexlify, isBytes, isHexString } from "@ethersproject/bytes"; import { Zero } from "@ethersproject/constants"; import * as errors from "@ethersproject/errors"; -import { defineReadOnly, deepCopy, isNamedInstance, resolveProperties, shallowCopy } from "@ethersproject/properties"; +import { defineReadOnly, deepCopy, resolveProperties, shallowCopy } from "@ethersproject/properties"; import { UnsignedTransaction } from "@ethersproject/transactions"; @@ -440,10 +440,10 @@ export class Contract { defineReadOnly(this, "interface", new.target.getInterface(contractInterface)); - if (isNamedInstance(Signer, signerOrProvider)) { - defineReadOnly(this, "provider", signerOrProvider.provider); + if (Signer.isSigner(signerOrProvider)) { + defineReadOnly(this, "provider", signerOrProvider.provider || null); defineReadOnly(this, "signer", signerOrProvider); - } else if (isNamedInstance(Provider, signerOrProvider)) { + } else if (Provider.isProvider(signerOrProvider)) { defineReadOnly(this, "provider", signerOrProvider); defineReadOnly(this, "signer", null); } else { @@ -518,7 +518,7 @@ export class Contract { } static getInterface(contractInterface: ContractInterface): Interface { - if (isNamedInstance(Interface, contractInterface)) { + if (Interface.isInterface(contractInterface)) { return contractInterface; } return new Interface(contractInterface); @@ -601,7 +601,7 @@ export class Contract { } static isIndexed(value: any): value is Indexed { - return isNamedInstance(Indexed, value); + return Indexed.isIndexed(value); } private _normalizeRunningEvent(runningEvent: RunningEvent): RunningEvent { @@ -845,7 +845,7 @@ export class ContractFactory { } // If we have a signer, make sure it is valid - if (signer && !isNamedInstance(Signer, signer)) { + if (signer && !Signer.isSigner(signer)) { errors.throwArgumentError("invalid signer", "signer", signer); } diff --git a/packages/json-wallets/src.ts/crowdsale.ts b/packages/json-wallets/src.ts/crowdsale.ts index 98f2bf86..2fc12d2d 100644 --- a/packages/json-wallets/src.ts/crowdsale.ts +++ b/packages/json-wallets/src.ts/crowdsale.ts @@ -19,8 +19,10 @@ export class CrowdsaleAccount extends Description implements ExternallyOwnedAcco readonly mnemonic?: string; readonly path?: string; - isType(value: any): value is CrowdsaleAccount { - return Description.isType(value); + readonly _isCrowdsaleAccount: boolean; + + isCrowdsaleAccount(value: any): value is CrowdsaleAccount { + return !!(value && value._isCrowdsaleAccount); } } @@ -63,6 +65,7 @@ export function decrypt(json: string, password: Bytes | string): ExternallyOwned let privateKey = keccak256(seedHexBytes); return new CrowdsaleAccount ({ + _isCrowdsaleAccount: true, address: ethaddr, privateKey: privateKey }); diff --git a/packages/json-wallets/src.ts/keystore.ts b/packages/json-wallets/src.ts/keystore.ts index 8d7e8fea..659cf287 100644 --- a/packages/json-wallets/src.ts/keystore.ts +++ b/packages/json-wallets/src.ts/keystore.ts @@ -24,8 +24,10 @@ export class KeystoreAccount extends Description implements ExternallyOwnedAccou readonly mnemonic?: string; readonly path?: string; - isType(value: any): value is KeystoreAccount { - return Description.isType(value); + readonly _isKeystoreAccount: boolean; + + isKeystoreAccount(value: any): value is KeystoreAccount { + return !!(value && value._isKeystoreAccount); } } @@ -95,6 +97,7 @@ export function decrypt(json: string, password: Bytes | string, progressCallback } catch (e) { } let account: any = { + _isKeystoreAccount: true, address: address, privateKey: hexlify(privateKey) }; diff --git a/packages/properties/src.ts/index.ts b/packages/properties/src.ts/index.ts index d7c3f558..2b1af228 100644 --- a/packages/properties/src.ts/index.ts +++ b/packages/properties/src.ts/index.ts @@ -10,47 +10,6 @@ export function defineReadOnly(object: any, name: string, value: any): void { }); } -// There are some issues with instanceof with npm link, so we use this -// to ensure types are what we expect. We use this for a little extra -// protection to make sure the correct types are being passed around. - -function getType(object: any) { - - let type = typeof(object); - if (type !== "function") { return null; } - - let types = [ ]; - let obj = object; - while (true) { - let type = obj.name; - if (!type) { break; } - types.push(type); - obj = Object.getPrototypeOf(obj); - } - return types.join(" "); -} - -function hasSuffix(text: string, suffix: string) { - return text.substring(text.length - suffix.length) === suffix; -} - -export function isNamedInstance(type: Function | string, value: any): value is T { - let name = getType(type); - if (!name) { return false; } - - // Not a string... - if (typeof(value) !== "string") { - - // Not an instance... - if (typeof(value) !== "object") { return false; } - - // Get the instance type - value = getType(value.constructor); - } - - return (name === value || hasSuffix(value, " " + name)); -} - export function resolveProperties(object: any): Promise { let result: any = {}; @@ -115,10 +74,8 @@ export function deepCopy(object: any, frozen?: boolean): any { if (typeof(object) === "object") { - // Some internal objects, which are already immutable - if (isNamedInstance("BigNumber", object)) { return object; } - if (isNamedInstance("Description", object)) { return object; } - if (isNamedInstance("Indexed", object)) { return object; } + // Immutable objects are safe to just use + if (Object.isFrozen(object)) { return object; } let result: { [ key: string ]: any } = {}; for (let key in object) { @@ -147,8 +104,4 @@ export class Description { } Object.freeze(this); } - - static isType(value: any): boolean { - return isNamedInstance(this, value); - } } diff --git a/packages/providers/src.ts/base-provider.ts b/packages/providers/src.ts/base-provider.ts index 1f3416d9..70f600a6 100644 --- a/packages/providers/src.ts/base-provider.ts +++ b/packages/providers/src.ts/base-provider.ts @@ -9,7 +9,7 @@ import { arrayify, hexDataLength, hexlify, hexValue, isHexString } from "@ethers import * as errors from "@ethersproject/errors"; import { namehash } from "@ethersproject/hash"; import { getNetwork, Network, Networkish } from "@ethersproject/networks"; -import { defineReadOnly, isNamedInstance, resolveProperties } from "@ethersproject/properties"; +import { defineReadOnly, resolveProperties } from "@ethersproject/properties"; import { Transaction } from "@ethersproject/transactions"; import { toUtf8String } from "@ethersproject/strings"; import { poll } from "@ethersproject/web"; @@ -77,7 +77,7 @@ function getEventTag(eventName: EventType): string { } else if (Array.isArray(eventName)) { return "filter:*:" + serializeTopics(eventName); - } else if (isNamedInstance(ForkEvent, eventName)) { + } else if (ForkEvent.isForkEvent(eventName)) { errors.warn("not implemented"); throw new Error("not implemented"); diff --git a/packages/signing-key/src.ts/index.ts b/packages/signing-key/src.ts/index.ts index fa931f1c..9db37730 100644 --- a/packages/signing-key/src.ts/index.ts +++ b/packages/signing-key/src.ts/index.ts @@ -22,7 +22,9 @@ export class SigningKey { readonly publicKey: string; readonly compressedPublicKey: string; - readonly address: string; + //readonly address: string; + + readonly _isSigningKey: boolean; constructor(privateKey: BytesLike) { defineReadOnly(this, "curve", "secp256k1"); @@ -33,6 +35,8 @@ export class SigningKey { defineReadOnly(this, "publicKey", "0x" + keyPair.getPublic(false, "hex")); defineReadOnly(this, "compressedPublicKey", "0x" + keyPair.getPublic(true, "hex")); + + defineReadOnly(this, "_isSigningKey", true); } _addPoint(other: BytesLike): string { @@ -56,6 +60,10 @@ export class SigningKey { let otherKeyPair = getCurve().keyFromPublic(arrayify(computePublicKey(otherKey))); return hexZeroPad("0x" + keyPair.derive(otherKeyPair.getPublic()).toString(16), 32); } + + static isSigningKey(value: any): value is SigningKey { + return !!(value && value._isSigningKey); + } } export function recoverPublicKey(digest: BytesLike, signature: SignatureLike): string { diff --git a/packages/wallet/src.ts/index.ts b/packages/wallet/src.ts/index.ts index 42dbc637..6b118a9f 100644 --- a/packages/wallet/src.ts/index.ts +++ b/packages/wallet/src.ts/index.ts @@ -8,7 +8,7 @@ import * as errors from "@ethersproject/errors"; import { hashMessage } from "@ethersproject/hash"; import { defaultPath, HDNode, entropyToMnemonic } from "@ethersproject/hdnode"; import { keccak256 } from "@ethersproject/keccak256"; -import { defineReadOnly, isNamedInstance, resolveProperties } from "@ethersproject/properties"; +import { defineReadOnly, resolveProperties } from "@ethersproject/properties"; import { randomBytes } from "@ethersproject/random"; import { SigningKey } from "@ethersproject/signing-key"; import { decryptJsonWallet, encryptKeystore, ProgressCallback } from "@ethersproject/json-wallets"; @@ -61,7 +61,10 @@ export class Wallet extends Signer implements ExternallyOwnedAccount { } else { - if (isNamedInstance(SigningKey, privateKey)) { + if (SigningKey.isSigningKey(privateKey)) { + if (privateKey.curve !== "secp256k1") { + errors.throwArgumentError("unsupported curve; must be secp256k1", "privateKey", "[REDACTED]"); + } defineReadOnly(this, "_signingKey", () => privateKey); } else { let signingKey = new SigningKey(privateKey); @@ -72,7 +75,7 @@ export class Wallet extends Signer implements ExternallyOwnedAccount { defineReadOnly(this, "address", computeAddress(this.publicKey)); } - if (provider && !isNamedInstance(Provider, provider)) { + if (provider && !Provider.isProvider(provider)) { errors.throwError("invalid provider", errors.INVALID_ARGUMENT, { argument: "provider", value: provider