Moved to assert instead of throw helpers.

This commit is contained in:
Richard Moore 2022-11-27 21:52:21 -05:00
parent fe342ca48a
commit d75cc3c023
2 changed files with 104 additions and 150 deletions

View File

@ -1,5 +1,5 @@
import { getAddress } from "../address/index.js"; import { getAddress } from "../address/index.js";
import { dataLength } from "../utils/index.js"; import { assertArgument, isHexString } from "../utils/index.js";
import type { AccessList, AccessListish } from "./index.js"; import type { AccessList, AccessListish } from "./index.js";
@ -7,11 +7,8 @@ import type { AccessList, AccessListish } from "./index.js";
function accessSetify(addr: string, storageKeys: Array<string>): { address: string,storageKeys: Array<string> } { function accessSetify(addr: string, storageKeys: Array<string>): { address: string,storageKeys: Array<string> } {
return { return {
address: getAddress(addr), address: getAddress(addr),
storageKeys: (storageKeys || []).map((storageKey, index) => { storageKeys: storageKeys.map((storageKey, index) => {
if (dataLength(storageKey) !== 32) { assertArgument(isHexString(storageKey, 32), "invalid slot", `storageKeys[${ index }]`, storageKey);
//logger.throwArgumentError("invalid access list storageKey", `accessList[${ addr }>
throw new Error("");
}
return storageKey.toLowerCase(); return storageKey.toLowerCase();
}) })
}; };
@ -21,16 +18,16 @@ export function accessListify(value: AccessListish): AccessList {
if (Array.isArray(value)) { if (Array.isArray(value)) {
return (<Array<[ string, Array<string>] | { address: string, storageKeys: Array<string>}>>value).map((set, index) => { return (<Array<[ string, Array<string>] | { address: string, storageKeys: Array<string>}>>value).map((set, index) => {
if (Array.isArray(set)) { if (Array.isArray(set)) {
if (set.length > 2) { assertArgument(set.length === 2, "invalid slot set", `value[${ index }]`, set);
//logger.throwArgumentError("access list expected to be [ address, storageKeys[>
throw new Error("");
}
return accessSetify(set[0], set[1]) return accessSetify(set[0], set[1])
} }
assertArgument(set != null && typeof(set) === "object", "invalid address-slot set", "value", value);
return accessSetify(set.address, set.storageKeys); return accessSetify(set.address, set.storageKeys);
}); });
} }
assertArgument(value != null && typeof(value) === "object", "invalid access list", "value", value);
const result: Array<{ address: string, storageKeys: Array<string> }> = Object.keys(value).map((addr) => { const result: Array<{ address: string, storageKeys: Array<string> }> = Object.keys(value).map((addr) => {
const storageKeys: Record<string, true> = value[addr].reduce((accum, storageKey) => { const storageKeys: Record<string, true> = value[addr].reduce((accum, storageKey) => {
accum[storageKey] = true; accum[storageKey] = true;

View File

@ -1,15 +1,15 @@
import { getAddress } from "../address/index.js"; import { getAddress } from "../address/index.js";
import { keccak256, Signature } from "../crypto/index.js"; import { keccak256, Signature, SigningKey } from "../crypto/index.js";
import { import {
concat, decodeRlp, encodeRlp, getBytes, getStore, getBigInt, getNumber, hexlify, concat, decodeRlp, encodeRlp, getBytes, getBigInt, getNumber, hexlify,
setStore, assertArgument, toArray, zeroPadValue assert, assertArgument, toArray, zeroPadValue
} from "../utils/index.js"; } from "../utils/index.js";
import { accessListify } from "./accesslist.js"; import { accessListify } from "./accesslist.js";
import { recoverAddress } from "./address.js"; import { recoverAddress } from "./address.js";
import type { BigNumberish, BytesLike, Freezable, Frozen } from "../utils/index.js"; import type { BigNumberish, BytesLike } from "../utils/index.js";
import type { SignatureLike } from "../crypto/index.js"; import type { SignatureLike } from "../crypto/index.js";
import type { AccessList, AccessListish } from "./index.js"; import type { AccessList, AccessListish } from "./index.js";
@ -52,19 +52,11 @@ function handleAddress(value: string): null | string {
return getAddress(value); return getAddress(value);
} }
function handleData(value: string, param: string): string {
try {
return hexlify(value);
} catch (error) {
assertArgument(false, "invalid data", param, value);
}
}
function handleAccessList(value: any, param: string): AccessList { function handleAccessList(value: any, param: string): AccessList {
try { try {
return accessListify(value); return accessListify(value);
} catch (error) { } catch (error: any) {
assertArgument(false, "invalid accessList", param, value); assertArgument(false, error.message, param, value);
} }
} }
@ -104,7 +96,7 @@ function _parseLegacy(data: Uint8Array): TransactionLike {
gasLimit: handleUint(fields[2], "gasLimit"), gasLimit: handleUint(fields[2], "gasLimit"),
to: handleAddress(fields[3]), to: handleAddress(fields[3]),
value: handleUint(fields[4], "value"), value: handleUint(fields[4], "value"),
data: handleData(fields[5], "dta"), data: hexlify(fields[5]),
chainId: BN_0 chainId: BN_0
}; };
@ -228,7 +220,7 @@ function _parseEip1559(data: Uint8Array): TransactionLike {
gasLimit: handleUint(fields[4], "gasLimit"), gasLimit: handleUint(fields[4], "gasLimit"),
to: handleAddress(fields[5]), to: handleAddress(fields[5]),
value: handleUint(fields[6], "value"), value: handleUint(fields[6], "value"),
data: handleData(fields[7], "data"), data: hexlify(fields[7]),
accessList: handleAccessList(fields[8], "accessList"), accessList: handleAccessList(fields[8], "accessList"),
}; };
@ -278,7 +270,7 @@ function _parseEip2930(data: Uint8Array): TransactionLike {
gasLimit: handleUint(fields[3], "gasLimit"), gasLimit: handleUint(fields[3], "gasLimit"),
to: handleAddress(fields[4]), to: handleAddress(fields[4]),
value: handleUint(fields[5], "value"), value: handleUint(fields[5], "value"),
data: handleData(fields[6], "data"), data: hexlify(fields[6]),
accessList: handleAccessList(fields[7], "accessList") accessList: handleAccessList(fields[7], "accessList")
}; };
@ -321,24 +313,22 @@ export interface SignedTransaction extends Transaction {
} }
export class Transaction implements Freezable<Transaction>, TransactionLike<string> { export class Transaction implements TransactionLike<string> {
#props: { #type: null | number;
type: null | number, #to: null | string;
to: null | string, #data: string;
data: string, #nonce: number;
nonce: number, #gasLimit: bigint;
gasLimit: bigint, #gasPrice: null | bigint;
gasPrice: null | bigint, #maxPriorityFeePerGas: null | bigint;
maxPriorityFeePerGas: null | bigint, #maxFeePerGas: null | bigint;
maxFeePerGas: null | bigint, #value: bigint;
value: bigint, #chainId: bigint;
chainId: bigint, #sig: null | Signature;
sig: null | Signature, #accessList: null | AccessList;
accessList: null | AccessList
};
// A type of null indicates the type will be populated automatically // A type of null indicates the type will be populated automatically
get type(): null | number { return getStore(this.#props, "type"); } get type(): null | number { return this.#type; }
get typeName(): null | string { get typeName(): null | string {
switch (this.type) { switch (this.type) {
case 0: return "legacy"; case 0: return "legacy";
@ -351,100 +341,107 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
set type(value: null | number | string) { set type(value: null | number | string) {
switch (value) { switch (value) {
case null: case null:
setStore(this.#props, "type", null); this.#type = null;
break; break;
case 0: case "legacy": case 0: case "legacy":
setStore(this.#props, "type", 0); this.#type = 0;
break; break;
case 1: case "berlin": case "eip-2930": case 1: case "berlin": case "eip-2930":
setStore(this.#props, "type", 1); this.#type = 1;
break; break;
case 2: case "london": case "eip-1559": case 2: case "london": case "eip-1559":
setStore(this.#props, "type", 2); this.#type = 2;
break; break;
default: default:
throw new Error(`unsupported transaction type`); assertArgument(false, "unsupported transaction type", "type", value);
} }
} }
get to(): null | string { return getStore(this.#props, "to"); } get to(): null | string { return this.#to; }
set to(value: null | string) { set to(value: null | string) {
setStore(this.#props, "to", (value == null) ? null: getAddress(value)); this.#to = (value == null) ? null: getAddress(value);
} }
get nonce(): number { return getStore(this.#props, "nonce"); } get nonce(): number { return this.#nonce; }
set nonce(value: BigNumberish) { setStore(this.#props, "nonce", getNumber(value, "value")); } set nonce(value: BigNumberish) { this.#nonce = getNumber(value, "value"); }
get gasLimit(): bigint { return getStore(this.#props, "gasLimit"); } get gasLimit(): bigint { return this.#gasLimit; }
set gasLimit(value: BigNumberish) { setStore(this.#props, "gasLimit", getBigInt(value)); } set gasLimit(value: BigNumberish) { this.#gasLimit = getBigInt(value); }
get gasPrice(): null | bigint { get gasPrice(): null | bigint {
const value = getStore(this.#props, "gasPrice"); const value = this.#gasPrice;
if (value == null && (this.type === 0 || this.type === 1)) { return BN_0; } if (value == null && (this.type === 0 || this.type === 1)) { return BN_0; }
return value; return value;
} }
set gasPrice(value: null | BigNumberish) { set gasPrice(value: null | BigNumberish) {
setStore(this.#props, "gasPrice", (value == null) ? null: getBigInt(value, "gasPrice")); this.#gasPrice = (value == null) ? null: getBigInt(value, "gasPrice");
} }
get maxPriorityFeePerGas(): null | bigint { get maxPriorityFeePerGas(): null | bigint {
const value = getStore(this.#props, "maxPriorityFeePerGas"); const value = this.#maxPriorityFeePerGas;
if (value == null && this.type === 2) { return BN_0; } if (value == null) {
if (this.type === 2) { return BN_0; }
return null;
}
return value; return value;
} }
set maxPriorityFeePerGas(value: null | BigNumberish) { set maxPriorityFeePerGas(value: null | BigNumberish) {
setStore(this.#props, "maxPriorityFeePerGas", (value == null) ? null: getBigInt(value, "maxPriorityFeePerGas")); this.#maxPriorityFeePerGas = (value == null) ? null: getBigInt(value, "maxPriorityFeePerGas");
} }
get maxFeePerGas(): null | bigint { get maxFeePerGas(): null | bigint {
const value = getStore(this.#props, "maxFeePerGas"); const value = this.#maxFeePerGas;
if (value == null && this.type === 2) { return BN_0; } if (value == null) {
if (this.type === 2) { return BN_0; }
return null;
}
return value; return value;
} }
set maxFeePerGas(value: null | BigNumberish) { set maxFeePerGas(value: null | BigNumberish) {
setStore(this.#props, "maxFeePerGas", (value == null) ? null: getBigInt(value, "maxFeePerGas")); this.#maxFeePerGas = (value == null) ? null: getBigInt(value, "maxFeePerGas");
} }
get data(): string { return getStore(this.#props, "data"); } get data(): string { return this.#data; }
set data(value: BytesLike) { setStore(this.#props, "data", hexlify(value)); } set data(value: BytesLike) { this.#data = hexlify(value); }
get value(): bigint { return getStore(this.#props, "value"); } get value(): bigint { return this.#value; }
set value(value: BigNumberish) { set value(value: BigNumberish) {
setStore(this.#props, "value", getBigInt(value, "value")); this.#value = getBigInt(value, "value");
} }
get chainId(): bigint { return getStore(this.#props, "chainId"); } get chainId(): bigint { return this.#chainId; }
set chainId(value: BigNumberish) { setStore(this.#props, "chainId", getBigInt(value)); } set chainId(value: BigNumberish) { this.#chainId = getBigInt(value); }
get signature(): null | Signature { return getStore(this.#props, "sig") || null; } get signature(): null | Signature { return this.#sig || null; }
set signature(value: null | SignatureLike) { set signature(value: null | SignatureLike) {
setStore(this.#props, "sig", (value == null) ? null: Signature.from(value)); this.#sig = (value == null) ? null: Signature.from(value);
} }
get accessList(): null | AccessList { get accessList(): null | AccessList {
const value = getStore(this.#props, "accessList") || null; const value = this.#accessList || null;
if (value == null && (this.type === 1 || this.type === 2)) { return [ ]; } if (value == null) {
if (this.type === 1 || this.type === 2) { return [ ]; }
return null;
}
return value; return value;
} }
set accessList(value: null | AccessListish) { set accessList(value: null | AccessListish) {
setStore(this.#props, "accessList", (value == null) ? null: accessListify(value)); this.#accessList = (value == null) ? null: accessListify(value);
} }
constructor() { constructor() {
this.#props = { this.#type = null;
type: null, this.#to = null;
to: null, this.#nonce = 0;
nonce: 0, this.#gasLimit = BigInt(0);
gasLimit: BigInt(0), this.#gasPrice = null;
gasPrice: null, this.#maxPriorityFeePerGas = null;
maxPriorityFeePerGas: null, this.#maxFeePerGas = null;
maxFeePerGas: null, this.#data = "0x";
data: "0x", this.#value = BigInt(0);
value: BigInt(0), this.#chainId = BigInt(0);
chainId: BigInt(0), this.#sig = null;
sig: null, this.#accessList = null;
accessList: null
};
} }
get hash(): null | string { get hash(): null | string {
@ -463,9 +460,7 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
get fromPublicKey(): null | string { get fromPublicKey(): null | string {
if (this.signature == null) { return null; } if (this.signature == null) { return null; }
throw new Error("@TODO"); return SigningKey.recoverPublicKey(this.unsignedHash, this.signature);
// use ecrecover
return "";
} }
isSigned(): this is SignedTransaction { isSigned(): this is SignedTransaction {
@ -473,16 +468,9 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
} }
get serialized(): string { get serialized(): string {
if (this.signature == null) { assert(this.signature != null, "cannot serialize unsigned transaction; maybe you meant .unsignedSerialized", "UNSUPPORTED_OPERATION", { operation: ".serialized"});
throw new Error("cannot serialize unsigned transaction; maybe you meant .unsignedSerialized");
}
const types = this.inferTypes(); switch (this.inferType()) {
if (types.length !== 1) {
throw new Error("cannot determine transaction type; specify type manually");
}
switch (types[0]) {
case 0: case 0:
return _serializeLegacy(this, this.signature); return _serializeLegacy(this, this.signature);
case 1: case 1:
@ -491,16 +479,11 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
return _serializeEip1559(this, this.signature); return _serializeEip1559(this, this.signature);
} }
throw new Error("unsupported type"); assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: ".serialized" });
} }
get unsignedSerialized(): string { get unsignedSerialized(): string {
const types = this.inferTypes(); switch (this.inferType()) {
if (types.length !== 1) {
throw new Error("cannot determine transaction type; specify type manually");
}
switch (types[0]) {
case 0: case 0:
return _serializeLegacy(this); return _serializeLegacy(this);
case 1: case 1:
@ -509,7 +492,15 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
return _serializeEip1559(this); return _serializeEip1559(this);
} }
throw new Error("unsupported type"); assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: ".unsignedSerialized" });
}
/**
* Return the most "likely" type; currently the highest
* supported transaction type
*/
inferType(): number {
return <number>(this.inferTypes().pop());
} }
// Validates properties and lists possible types this transaction adheres to // Validates properties and lists possible types this transaction adheres to
@ -525,22 +516,15 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
//} //}
if (this.maxFeePerGas != null && this.maxPriorityFeePerGas != null) { if (this.maxFeePerGas != null && this.maxPriorityFeePerGas != null) {
if (this.maxFeePerGas < this.maxPriorityFeePerGas) { assert(this.maxFeePerGas >= this.maxPriorityFeePerGas, "priorityFee cannot be more than maxFee", "BAD_DATA", { value: this });
throw new Error("priorityFee cannot be more than maxFee");
}
} }
//if (this.type === 2 && hasGasPrice) { //if (this.type === 2 && hasGasPrice) {
// throw new Error("eip-1559 transaction cannot have gasPrice"); // throw new Error("eip-1559 transaction cannot have gasPrice");
//} //}
if ((this.type === 0 || this.type === 1) && hasFee) { assert(!hasFee || (this.type !== 0 && this.type !== 1), "transaction type cannot have maxFeePerGas or maxPriorityFeePerGas", "BAD_DATA", { value: this });
throw new Error("transaction type cannot have maxFeePerGas or maxPriorityFeePerGas"); assert(this.type !== 0 || !hasAccessList, "legacy transaction cannot have accessList", "BAD_DATA", { value: this })
}
if (this.type === 0 && hasAccessList) {
throw new Error("legacy transaction cannot have accessList");
}
const types: Array<number> = [ ]; const types: Array<number> = [ ];
@ -583,26 +567,6 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
return Transaction.from(this); return Transaction.from(this);
} }
freeze(): Frozen<Transaction> {
if (this.#props.sig) {
this.#props.sig = <any>(this.#props.sig.clone().freeze());
}
if (this.#props.accessList) {
this.#props.accessList = <any>Object.freeze(this.#props.accessList.map((set) => {
Object.freeze(set.storageKeys);
return Object.freeze(set);
}));
}
Object.freeze(this.#props);
return this;
}
isFrozen(): boolean {
return Object.isFrozen(this.#props);
}
toJSON(): any { toJSON(): any {
const s = (v: null | bigint) => { const s = (v: null | bigint) => {
if (v == null) { return null; } if (v == null) { return null; }
@ -612,7 +576,7 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
return { return {
type: this.type, type: this.type,
to: this.to, to: this.to,
from: this.from, // from: this.from,
data: this.data, data: this.data,
nonce: this.nonce, nonce: this.nonce,
gasLimit: s(this.gasLimit), gasLimit: s(this.gasLimit),
@ -638,8 +602,7 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
case 1: return Transaction.from(_parseEip2930(payload)); case 1: return Transaction.from(_parseEip2930(payload));
case 2: return Transaction.from(_parseEip1559(payload)); case 2: return Transaction.from(_parseEip1559(payload));
} }
assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: "from" });
throw new Error("unsupported transaction type");
} }
const result = new Transaction(); const result = new Transaction();
@ -657,19 +620,13 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
if (tx.accessList != null) { result.accessList = tx.accessList; } if (tx.accessList != null) { result.accessList = tx.accessList; }
if (tx.hash != null) { if (tx.hash != null) {
if (result.isSigned()) { assertArgument(result.isSigned(), "unsigned transaction cannot have define hash", "tx", tx);
if (result.hash !== tx.hash) { throw new Error("hash mismatch"); } assertArgument(result.hash === tx.hash, "hash mismatch", "tx", tx);
} else {
throw new Error("unsigned transaction cannot have a hashs");
}
} }
if (tx.from != null) { if (tx.from != null) {
if (result.isSigned()) { assertArgument(result.isSigned(), "unsigned transaction cannot have define from", "tx", tx);
if (result.from.toLowerCase() !== (tx.from || "").toLowerCase()) { throw new Error("from mismatch"); } assertArgument(result.from.toLowerCase() === (tx.from || "").toLowerCase(), "from mismatch", "tx", tx);
} else {
throw new Error("unsigned transaction cannot have a from");
}
} }
return result; return result;