Moved to assert instead of throw helpers.
This commit is contained in:
parent
fe342ca48a
commit
d75cc3c023
@ -1,5 +1,5 @@
|
||||
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";
|
||||
|
||||
@ -7,11 +7,8 @@ import type { AccessList, AccessListish } from "./index.js";
|
||||
function accessSetify(addr: string, storageKeys: Array<string>): { address: string,storageKeys: Array<string> } {
|
||||
return {
|
||||
address: getAddress(addr),
|
||||
storageKeys: (storageKeys || []).map((storageKey, index) => {
|
||||
if (dataLength(storageKey) !== 32) {
|
||||
//logger.throwArgumentError("invalid access list storageKey", `accessList[${ addr }>
|
||||
throw new Error("");
|
||||
}
|
||||
storageKeys: storageKeys.map((storageKey, index) => {
|
||||
assertArgument(isHexString(storageKey, 32), "invalid slot", `storageKeys[${ index }]`, storageKey);
|
||||
return storageKey.toLowerCase();
|
||||
})
|
||||
};
|
||||
@ -21,16 +18,16 @@ export function accessListify(value: AccessListish): AccessList {
|
||||
if (Array.isArray(value)) {
|
||||
return (<Array<[ string, Array<string>] | { address: string, storageKeys: Array<string>}>>value).map((set, index) => {
|
||||
if (Array.isArray(set)) {
|
||||
if (set.length > 2) {
|
||||
//logger.throwArgumentError("access list expected to be [ address, storageKeys[>
|
||||
throw new Error("");
|
||||
}
|
||||
assertArgument(set.length === 2, "invalid slot set", `value[${ index }]`, set);
|
||||
return accessSetify(set[0], set[1])
|
||||
}
|
||||
assertArgument(set != null && typeof(set) === "object", "invalid address-slot set", "value", value);
|
||||
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 storageKeys: Record<string, true> = value[addr].reduce((accum, storageKey) => {
|
||||
accum[storageKey] = true;
|
||||
|
@ -1,15 +1,15 @@
|
||||
|
||||
import { getAddress } from "../address/index.js";
|
||||
import { keccak256, Signature } from "../crypto/index.js";
|
||||
import { keccak256, Signature, SigningKey } from "../crypto/index.js";
|
||||
import {
|
||||
concat, decodeRlp, encodeRlp, getBytes, getStore, getBigInt, getNumber, hexlify,
|
||||
setStore, assertArgument, toArray, zeroPadValue
|
||||
concat, decodeRlp, encodeRlp, getBytes, getBigInt, getNumber, hexlify,
|
||||
assert, assertArgument, toArray, zeroPadValue
|
||||
} from "../utils/index.js";
|
||||
|
||||
import { accessListify } from "./accesslist.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 { AccessList, AccessListish } from "./index.js";
|
||||
@ -52,19 +52,11 @@ function handleAddress(value: string): null | string {
|
||||
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 {
|
||||
try {
|
||||
return accessListify(value);
|
||||
} catch (error) {
|
||||
assertArgument(false, "invalid accessList", param, value);
|
||||
} catch (error: any) {
|
||||
assertArgument(false, error.message, param, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,7 +96,7 @@ function _parseLegacy(data: Uint8Array): TransactionLike {
|
||||
gasLimit: handleUint(fields[2], "gasLimit"),
|
||||
to: handleAddress(fields[3]),
|
||||
value: handleUint(fields[4], "value"),
|
||||
data: handleData(fields[5], "dta"),
|
||||
data: hexlify(fields[5]),
|
||||
chainId: BN_0
|
||||
};
|
||||
|
||||
@ -228,7 +220,7 @@ function _parseEip1559(data: Uint8Array): TransactionLike {
|
||||
gasLimit: handleUint(fields[4], "gasLimit"),
|
||||
to: handleAddress(fields[5]),
|
||||
value: handleUint(fields[6], "value"),
|
||||
data: handleData(fields[7], "data"),
|
||||
data: hexlify(fields[7]),
|
||||
accessList: handleAccessList(fields[8], "accessList"),
|
||||
};
|
||||
|
||||
@ -278,7 +270,7 @@ function _parseEip2930(data: Uint8Array): TransactionLike {
|
||||
gasLimit: handleUint(fields[3], "gasLimit"),
|
||||
to: handleAddress(fields[4]),
|
||||
value: handleUint(fields[5], "value"),
|
||||
data: handleData(fields[6], "data"),
|
||||
data: hexlify(fields[6]),
|
||||
accessList: handleAccessList(fields[7], "accessList")
|
||||
};
|
||||
|
||||
@ -321,24 +313,22 @@ export interface SignedTransaction extends Transaction {
|
||||
}
|
||||
|
||||
|
||||
export class Transaction implements Freezable<Transaction>, TransactionLike<string> {
|
||||
#props: {
|
||||
type: null | number,
|
||||
to: null | string,
|
||||
data: string,
|
||||
nonce: number,
|
||||
gasLimit: bigint,
|
||||
gasPrice: null | bigint,
|
||||
maxPriorityFeePerGas: null | bigint,
|
||||
maxFeePerGas: null | bigint,
|
||||
value: bigint,
|
||||
chainId: bigint,
|
||||
sig: null | Signature,
|
||||
accessList: null | AccessList
|
||||
};
|
||||
export class Transaction implements TransactionLike<string> {
|
||||
#type: null | number;
|
||||
#to: null | string;
|
||||
#data: string;
|
||||
#nonce: number;
|
||||
#gasLimit: bigint;
|
||||
#gasPrice: null | bigint;
|
||||
#maxPriorityFeePerGas: null | bigint;
|
||||
#maxFeePerGas: null | bigint;
|
||||
#value: bigint;
|
||||
#chainId: bigint;
|
||||
#sig: null | Signature;
|
||||
#accessList: null | AccessList;
|
||||
|
||||
// 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 {
|
||||
switch (this.type) {
|
||||
case 0: return "legacy";
|
||||
@ -351,100 +341,107 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
|
||||
set type(value: null | number | string) {
|
||||
switch (value) {
|
||||
case null:
|
||||
setStore(this.#props, "type", null);
|
||||
this.#type = null;
|
||||
break;
|
||||
case 0: case "legacy":
|
||||
setStore(this.#props, "type", 0);
|
||||
this.#type = 0;
|
||||
break;
|
||||
case 1: case "berlin": case "eip-2930":
|
||||
setStore(this.#props, "type", 1);
|
||||
this.#type = 1;
|
||||
break;
|
||||
case 2: case "london": case "eip-1559":
|
||||
setStore(this.#props, "type", 2);
|
||||
this.#type = 2;
|
||||
break;
|
||||
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) {
|
||||
setStore(this.#props, "to", (value == null) ? null: getAddress(value));
|
||||
this.#to = (value == null) ? null: getAddress(value);
|
||||
}
|
||||
|
||||
get nonce(): number { return getStore(this.#props, "nonce"); }
|
||||
set nonce(value: BigNumberish) { setStore(this.#props, "nonce", getNumber(value, "value")); }
|
||||
get nonce(): number { return this.#nonce; }
|
||||
set nonce(value: BigNumberish) { this.#nonce = getNumber(value, "value"); }
|
||||
|
||||
get gasLimit(): bigint { return getStore(this.#props, "gasLimit"); }
|
||||
set gasLimit(value: BigNumberish) { setStore(this.#props, "gasLimit", getBigInt(value)); }
|
||||
get gasLimit(): bigint { return this.#gasLimit; }
|
||||
set gasLimit(value: BigNumberish) { this.#gasLimit = getBigInt(value); }
|
||||
|
||||
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; }
|
||||
return value;
|
||||
}
|
||||
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 {
|
||||
const value = getStore(this.#props, "maxPriorityFeePerGas");
|
||||
if (value == null && this.type === 2) { return BN_0; }
|
||||
const value = this.#maxPriorityFeePerGas;
|
||||
if (value == null) {
|
||||
if (this.type === 2) { return BN_0; }
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
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 {
|
||||
const value = getStore(this.#props, "maxFeePerGas");
|
||||
if (value == null && this.type === 2) { return BN_0; }
|
||||
const value = this.#maxFeePerGas;
|
||||
if (value == null) {
|
||||
if (this.type === 2) { return BN_0; }
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
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"); }
|
||||
set data(value: BytesLike) { setStore(this.#props, "data", hexlify(value)); }
|
||||
get data(): string { return this.#data; }
|
||||
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) {
|
||||
setStore(this.#props, "value", getBigInt(value, "value"));
|
||||
this.#value = getBigInt(value, "value");
|
||||
}
|
||||
|
||||
get chainId(): bigint { return getStore(this.#props, "chainId"); }
|
||||
set chainId(value: BigNumberish) { setStore(this.#props, "chainId", getBigInt(value)); }
|
||||
get chainId(): bigint { return this.#chainId; }
|
||||
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) {
|
||||
setStore(this.#props, "sig", (value == null) ? null: Signature.from(value));
|
||||
this.#sig = (value == null) ? null: Signature.from(value);
|
||||
}
|
||||
|
||||
get accessList(): null | AccessList {
|
||||
const value = getStore(this.#props, "accessList") || null;
|
||||
if (value == null && (this.type === 1 || this.type === 2)) { return [ ]; }
|
||||
const value = this.#accessList || null;
|
||||
if (value == null) {
|
||||
if (this.type === 1 || this.type === 2) { return [ ]; }
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
set accessList(value: null | AccessListish) {
|
||||
setStore(this.#props, "accessList", (value == null) ? null: accessListify(value));
|
||||
this.#accessList = (value == null) ? null: accessListify(value);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.#props = {
|
||||
type: null,
|
||||
to: null,
|
||||
nonce: 0,
|
||||
gasLimit: BigInt(0),
|
||||
gasPrice: null,
|
||||
maxPriorityFeePerGas: null,
|
||||
maxFeePerGas: null,
|
||||
data: "0x",
|
||||
value: BigInt(0),
|
||||
chainId: BigInt(0),
|
||||
sig: null,
|
||||
accessList: null
|
||||
};
|
||||
this.#type = null;
|
||||
this.#to = null;
|
||||
this.#nonce = 0;
|
||||
this.#gasLimit = BigInt(0);
|
||||
this.#gasPrice = null;
|
||||
this.#maxPriorityFeePerGas = null;
|
||||
this.#maxFeePerGas = null;
|
||||
this.#data = "0x";
|
||||
this.#value = BigInt(0);
|
||||
this.#chainId = BigInt(0);
|
||||
this.#sig = null;
|
||||
this.#accessList = null;
|
||||
}
|
||||
|
||||
get hash(): null | string {
|
||||
@ -463,9 +460,7 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
|
||||
|
||||
get fromPublicKey(): null | string {
|
||||
if (this.signature == null) { return null; }
|
||||
throw new Error("@TODO");
|
||||
// use ecrecover
|
||||
return "";
|
||||
return SigningKey.recoverPublicKey(this.unsignedHash, this.signature);
|
||||
}
|
||||
|
||||
isSigned(): this is SignedTransaction {
|
||||
@ -473,16 +468,9 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
|
||||
}
|
||||
|
||||
get serialized(): string {
|
||||
if (this.signature == null) {
|
||||
throw new Error("cannot serialize unsigned transaction; maybe you meant .unsignedSerialized");
|
||||
}
|
||||
assert(this.signature != null, "cannot serialize unsigned transaction; maybe you meant .unsignedSerialized", "UNSUPPORTED_OPERATION", { operation: ".serialized"});
|
||||
|
||||
const types = this.inferTypes();
|
||||
if (types.length !== 1) {
|
||||
throw new Error("cannot determine transaction type; specify type manually");
|
||||
}
|
||||
|
||||
switch (types[0]) {
|
||||
switch (this.inferType()) {
|
||||
case 0:
|
||||
return _serializeLegacy(this, this.signature);
|
||||
case 1:
|
||||
@ -491,16 +479,11 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
|
||||
return _serializeEip1559(this, this.signature);
|
||||
}
|
||||
|
||||
throw new Error("unsupported type");
|
||||
assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: ".serialized" });
|
||||
}
|
||||
|
||||
get unsignedSerialized(): string {
|
||||
const types = this.inferTypes();
|
||||
if (types.length !== 1) {
|
||||
throw new Error("cannot determine transaction type; specify type manually");
|
||||
}
|
||||
|
||||
switch (types[0]) {
|
||||
switch (this.inferType()) {
|
||||
case 0:
|
||||
return _serializeLegacy(this);
|
||||
case 1:
|
||||
@ -509,7 +492,15 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
|
||||
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
|
||||
@ -525,22 +516,15 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
|
||||
//}
|
||||
|
||||
if (this.maxFeePerGas != null && this.maxPriorityFeePerGas != null) {
|
||||
if (this.maxFeePerGas < this.maxPriorityFeePerGas) {
|
||||
throw new Error("priorityFee cannot be more than maxFee");
|
||||
}
|
||||
assert(this.maxFeePerGas >= this.maxPriorityFeePerGas, "priorityFee cannot be more than maxFee", "BAD_DATA", { value: this });
|
||||
}
|
||||
|
||||
//if (this.type === 2 && hasGasPrice) {
|
||||
// throw new Error("eip-1559 transaction cannot have gasPrice");
|
||||
//}
|
||||
|
||||
if ((this.type === 0 || this.type === 1) && hasFee) {
|
||||
throw new Error("transaction type cannot have maxFeePerGas or maxPriorityFeePerGas");
|
||||
}
|
||||
|
||||
if (this.type === 0 && hasAccessList) {
|
||||
throw new Error("legacy transaction cannot have accessList");
|
||||
}
|
||||
assert(!hasFee || (this.type !== 0 && this.type !== 1), "transaction type cannot have maxFeePerGas or maxPriorityFeePerGas", "BAD_DATA", { value: this });
|
||||
assert(this.type !== 0 || !hasAccessList, "legacy transaction cannot have accessList", "BAD_DATA", { value: this })
|
||||
|
||||
const types: Array<number> = [ ];
|
||||
|
||||
@ -583,26 +567,6 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
|
||||
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 {
|
||||
const s = (v: null | bigint) => {
|
||||
if (v == null) { return null; }
|
||||
@ -612,7 +576,7 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
|
||||
return {
|
||||
type: this.type,
|
||||
to: this.to,
|
||||
from: this.from,
|
||||
// from: this.from,
|
||||
data: this.data,
|
||||
nonce: this.nonce,
|
||||
gasLimit: s(this.gasLimit),
|
||||
@ -638,8 +602,7 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
|
||||
case 1: return Transaction.from(_parseEip2930(payload));
|
||||
case 2: return Transaction.from(_parseEip1559(payload));
|
||||
}
|
||||
|
||||
throw new Error("unsupported transaction type");
|
||||
assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: "from" });
|
||||
}
|
||||
|
||||
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.hash != null) {
|
||||
if (result.isSigned()) {
|
||||
if (result.hash !== tx.hash) { throw new Error("hash mismatch"); }
|
||||
} else {
|
||||
throw new Error("unsigned transaction cannot have a hashs");
|
||||
}
|
||||
assertArgument(result.isSigned(), "unsigned transaction cannot have define hash", "tx", tx);
|
||||
assertArgument(result.hash === tx.hash, "hash mismatch", "tx", tx);
|
||||
}
|
||||
|
||||
if (tx.from != null) {
|
||||
if (result.isSigned()) {
|
||||
if (result.from.toLowerCase() !== (tx.from || "").toLowerCase()) { throw new Error("from mismatch"); }
|
||||
} else {
|
||||
throw new Error("unsigned transaction cannot have a from");
|
||||
}
|
||||
assertArgument(result.isSigned(), "unsigned transaction cannot have define from", "tx", tx);
|
||||
assertArgument(result.from.toLowerCase() === (tx.from || "").toLowerCase(), "from mismatch", "tx", tx);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
Loading…
Reference in New Issue
Block a user