Added EIP-4844 BLOb transactions (#4554).
This commit is contained in:
parent
a26ff77c21
commit
9c1e82e123
@ -31,6 +31,7 @@ link-eip-2098 [EIP-2098](https://eips.ethereum.org/EIPS/eip-2098)
|
|||||||
link-eip-2304 [EIP-2304](https://eips.ethereum.org/EIPS/eip-2304)
|
link-eip-2304 [EIP-2304](https://eips.ethereum.org/EIPS/eip-2304)
|
||||||
link-eip-2718 [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718)
|
link-eip-2718 [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718)
|
||||||
link-eip-2930 [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)
|
link-eip-2930 [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)
|
||||||
|
link-eip-4844 [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844)
|
||||||
|
|
||||||
# Open Standards
|
# Open Standards
|
||||||
link-base58 [Base58](https://en.bitcoinwiki.org/wiki/Base58)
|
link-base58 [Base58](https://en.bitcoinwiki.org/wiki/Base58)
|
||||||
|
@ -64,6 +64,15 @@ describe("Tests Unsigned Transaction Serializing", function() {
|
|||||||
assert.equal(tx.unsignedSerialized, test.unsignedLondon, "unsignedLondon");
|
assert.equal(tx.unsignedSerialized, test.unsignedLondon, "unsignedLondon");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const test of tests) {
|
||||||
|
if (!test.unsignedCancun) { continue; }
|
||||||
|
it(`serialized unsigned cancun transaction: ${ test.name }`, function() {
|
||||||
|
const txData = Object.assign({ }, test.transaction, { type: 3 });
|
||||||
|
const tx = Transaction.from(txData);
|
||||||
|
assert.equal(tx.unsignedSerialized, test.unsignedCancun, "unsignedCancun");
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Tests Signed Transaction Serializing", function() {
|
describe("Tests Signed Transaction Serializing", function() {
|
||||||
@ -127,6 +136,20 @@ describe("Tests Signed Transaction Serializing", function() {
|
|||||||
assert.equal(tx.serialized, test.signedLondon, "signedLondon");
|
assert.equal(tx.serialized, test.signedLondon, "signedLondon");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const test of tests) {
|
||||||
|
if (!test.signedCancun) { continue; }
|
||||||
|
|
||||||
|
it(`serialized signed Cancun transaction: ${ test.name }`, function() {
|
||||||
|
const txData = Object.assign({ }, test.transaction, {
|
||||||
|
type: 3,
|
||||||
|
signature: test.signatureCancun
|
||||||
|
});
|
||||||
|
|
||||||
|
const tx = Transaction.from(txData);
|
||||||
|
assert.equal(tx.serialized, test.signedCancun, "signedCancun");
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function assertTxUint(actual: null | bigint, _expected: undefined | string, name: string): void {
|
function assertTxUint(actual: null | bigint, _expected: undefined | string, name: string): void {
|
||||||
@ -227,6 +250,18 @@ describe("Tests Unsigned Transaction Parsing", function() {
|
|||||||
assertTxEqual(tx, expected);
|
assertTxEqual(tx, expected);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const test of tests) {
|
||||||
|
if (!test.unsignedCancun) { continue; }
|
||||||
|
it(`parses unsigned Cancun transaction: ${ test.name }`, function() {
|
||||||
|
const tx = Transaction.from(test.unsignedCancun);
|
||||||
|
|
||||||
|
const expected = addDefaults(test.transaction);
|
||||||
|
expected.gasPrice = null;
|
||||||
|
|
||||||
|
assertTxEqual(tx, expected);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Tests Signed Transaction Parsing", function() {
|
describe("Tests Signed Transaction Parsing", function() {
|
||||||
@ -277,6 +312,7 @@ describe("Tests Signed Transaction Parsing", function() {
|
|||||||
assert.equal(tx.isLegacy(), true, "isLegacy");
|
assert.equal(tx.isLegacy(), true, "isLegacy");
|
||||||
assert.equal(tx.isBerlin(), false, "isBerlin");
|
assert.equal(tx.isBerlin(), false, "isBerlin");
|
||||||
assert.equal(tx.isLondon(), false, "isLondon");
|
assert.equal(tx.isLondon(), false, "isLondon");
|
||||||
|
assert.equal(tx.isCancun(), false, "isCancun");
|
||||||
|
|
||||||
assert.ok(!!tx.signature, "signature:!null")
|
assert.ok(!!tx.signature, "signature:!null")
|
||||||
assert.equal(tx.signature.r, test.signatureEip155.r, "signature.r");
|
assert.equal(tx.signature.r, test.signatureEip155.r, "signature.r");
|
||||||
@ -303,6 +339,7 @@ describe("Tests Signed Transaction Parsing", function() {
|
|||||||
assert.equal(tx.isLegacy(), false, "isLegacy");
|
assert.equal(tx.isLegacy(), false, "isLegacy");
|
||||||
assert.equal(tx.isBerlin(), true, "isBerlin");
|
assert.equal(tx.isBerlin(), true, "isBerlin");
|
||||||
assert.equal(tx.isLondon(), false, "isLondon");
|
assert.equal(tx.isLondon(), false, "isLondon");
|
||||||
|
assert.equal(tx.isCancun(), false, "isCancun");
|
||||||
|
|
||||||
assert.ok(!!tx.signature, "signature:!null")
|
assert.ok(!!tx.signature, "signature:!null")
|
||||||
assert.equal(tx.signature.r, test.signatureBerlin.r, "signature.r");
|
assert.equal(tx.signature.r, test.signatureBerlin.r, "signature.r");
|
||||||
@ -328,6 +365,7 @@ describe("Tests Signed Transaction Parsing", function() {
|
|||||||
assert.equal(tx.isLegacy(), false, "isLegacy");
|
assert.equal(tx.isLegacy(), false, "isLegacy");
|
||||||
assert.equal(tx.isBerlin(), false, "isBerlin");
|
assert.equal(tx.isBerlin(), false, "isBerlin");
|
||||||
assert.equal(tx.isLondon(), true, "isLondon");
|
assert.equal(tx.isLondon(), true, "isLondon");
|
||||||
|
assert.equal(tx.isCancun(), false, "isCancun");
|
||||||
|
|
||||||
assert.ok(!!tx.signature, "signature:!null")
|
assert.ok(!!tx.signature, "signature:!null")
|
||||||
assert.equal(tx.signature.r, test.signatureLondon.r, "signature.r");
|
assert.equal(tx.signature.r, test.signatureLondon.r, "signature.r");
|
||||||
@ -339,6 +377,34 @@ describe("Tests Signed Transaction Parsing", function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const test of tests) {
|
||||||
|
if (!test.signedCancun) { continue; }
|
||||||
|
it(`parses signed Cancun transaction: ${ test.name }`, function() {
|
||||||
|
let tx = Transaction.from(test.signedCancun);
|
||||||
|
|
||||||
|
const expected = addDefaults(test.transaction);
|
||||||
|
expected.gasPrice = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
assertTxEqual(tx, expected);
|
||||||
|
|
||||||
|
assert.equal(tx.typeName, "eip-4844", "typeName");
|
||||||
|
assert.equal(tx.isLegacy(), false, "isLegacy");
|
||||||
|
assert.equal(tx.isBerlin(), false, "isBerlin");
|
||||||
|
assert.equal(tx.isLondon(), false, "isLondon");
|
||||||
|
assert.equal(tx.isCancun(), true, "isCancun");
|
||||||
|
|
||||||
|
assert.ok(!!tx.signature, "signature:!null")
|
||||||
|
assert.equal(tx.signature.r, test.signatureCancun.r, "signature.r");
|
||||||
|
assert.equal(tx.signature.s, test.signatureCancun.s, "signature.s");
|
||||||
|
assert.equal(tx.signature.yParity, parseInt(test.signatureCancun.v), "signature.v");
|
||||||
|
|
||||||
|
// Test cloning
|
||||||
|
tx = tx.clone();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Tests Transaction Parameters", function() {
|
describe("Tests Transaction Parameters", function() {
|
||||||
|
@ -217,11 +217,14 @@ export interface TestCaseTransaction {
|
|||||||
signedBerlin: string;
|
signedBerlin: string;
|
||||||
unsignedLondon: string;
|
unsignedLondon: string;
|
||||||
signedLondon: string;
|
signedLondon: string;
|
||||||
|
unsignedCancun: string;
|
||||||
|
signedCancun: string;
|
||||||
|
|
||||||
signatureLegacy: TestCaseTransactionSig;
|
signatureLegacy: TestCaseTransactionSig;
|
||||||
signatureEip155: TestCaseTransactionSig;
|
signatureEip155: TestCaseTransactionSig;
|
||||||
signatureBerlin: TestCaseTransactionSig;
|
signatureBerlin: TestCaseTransactionSig;
|
||||||
signatureLondon: TestCaseTransactionSig;
|
signatureLondon: TestCaseTransactionSig;
|
||||||
|
signatureCancun: TestCaseTransactionSig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
|
||||||
import { getAddress } from "../address/index.js";
|
import { getAddress } from "../address/index.js";
|
||||||
|
import { ZeroAddress } from "../constants/addresses.js";
|
||||||
import { keccak256, Signature, SigningKey } from "../crypto/index.js";
|
import { keccak256, Signature, SigningKey } from "../crypto/index.js";
|
||||||
import {
|
import {
|
||||||
concat, decodeRlp, encodeRlp, getBytes, getBigInt, getNumber, hexlify,
|
concat, decodeRlp, encodeRlp, getBytes, getBigInt, getNumber, hexlify,
|
||||||
assert, assertArgument, toBeArray, zeroPadValue
|
assert, assertArgument, isHexString, toBeArray, zeroPadValue
|
||||||
} from "../utils/index.js";
|
} from "../utils/index.js";
|
||||||
|
|
||||||
import { accessListify } from "./accesslist.js";
|
import { accessListify } from "./accesslist.js";
|
||||||
@ -22,6 +23,7 @@ const BN_28 = BigInt(28)
|
|||||||
const BN_35 = BigInt(35);
|
const BN_35 = BigInt(35);
|
||||||
const BN_MAX_UINT = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
const BN_MAX_UINT = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A **TransactionLike** is an object which is appropriate as a loose
|
* A **TransactionLike** is an object which is appropriate as a loose
|
||||||
* input for many operations which will populate missing properties of
|
* input for many operations which will populate missing properties of
|
||||||
@ -97,6 +99,16 @@ export interface TransactionLike<A = string> {
|
|||||||
* The access list for berlin and london transactions.
|
* The access list for berlin and london transactions.
|
||||||
*/
|
*/
|
||||||
accessList?: null | AccessListish;
|
accessList?: null | AccessListish;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum fee per blob gas (see [[link-eip-4844]]).
|
||||||
|
*/
|
||||||
|
maxFeePerBlobGas?: null | BigNumberish;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The versioned hashes (see [[link-eip-4844]]).
|
||||||
|
*/
|
||||||
|
blobVersionedHashes?: null | Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAddress(value: string): null | string {
|
function handleAddress(value: string): null | string {
|
||||||
@ -135,6 +147,14 @@ function formatAccessList(value: AccessListish): Array<[ string, Array<string> ]
|
|||||||
return accessListify(value).map((set) => [ set.address, set.storageKeys ]);
|
return accessListify(value).map((set) => [ set.address, set.storageKeys ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatHashes(value: Array<string>, param: string): Array<string> {
|
||||||
|
assertArgument(Array.isArray(value), `invalid ${ param }`, "value", value);
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
assertArgument(isHexString(value[i], 32), "invalid ${ param } hash", `value[${ i }]`, value[i]);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
function _parseLegacy(data: Uint8Array): TransactionLike {
|
function _parseLegacy(data: Uint8Array): TransactionLike {
|
||||||
const fields: any = decodeRlp(data);
|
const fields: any = decodeRlp(data);
|
||||||
|
|
||||||
@ -186,13 +206,15 @@ function _parseLegacy(data: Uint8Array): TransactionLike {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _serializeLegacy(tx: Transaction, sig?: Signature): string {
|
function _serializeLegacy(tx: Transaction, sig?: Signature): string {
|
||||||
|
assertArgument(tx.isLegacy(), "internal check failed; !legacy", "tx", tx);
|
||||||
|
|
||||||
const fields: Array<any> = [
|
const fields: Array<any> = [
|
||||||
formatNumber(tx.nonce || 0, "nonce"),
|
formatNumber(tx.nonce, "nonce"),
|
||||||
formatNumber(tx.gasPrice || 0, "gasPrice"),
|
formatNumber(tx.gasPrice, "gasPrice"),
|
||||||
formatNumber(tx.gasLimit || 0, "gasLimit"),
|
formatNumber(tx.gasLimit, "gasLimit"),
|
||||||
((tx.to != null) ? getAddress(tx.to): "0x"),
|
(tx.to || "0x"),
|
||||||
formatNumber(tx.value || 0, "value"),
|
formatNumber(tx.value, "value"),
|
||||||
(tx.data || "0x"),
|
tx.data,
|
||||||
];
|
];
|
||||||
|
|
||||||
let chainId = BN_0;
|
let chainId = BN_0;
|
||||||
@ -265,14 +287,12 @@ function _parseEip1559(data: Uint8Array): TransactionLike {
|
|||||||
assertArgument(Array.isArray(fields) && (fields.length === 9 || fields.length === 12),
|
assertArgument(Array.isArray(fields) && (fields.length === 9 || fields.length === 12),
|
||||||
"invalid field count for transaction type: 2", "data", hexlify(data));
|
"invalid field count for transaction type: 2", "data", hexlify(data));
|
||||||
|
|
||||||
const maxPriorityFeePerGas = handleUint(fields[2], "maxPriorityFeePerGas");
|
|
||||||
const maxFeePerGas = handleUint(fields[3], "maxFeePerGas");
|
|
||||||
const tx: TransactionLike = {
|
const tx: TransactionLike = {
|
||||||
type: 2,
|
type: 2,
|
||||||
chainId: handleUint(fields[0], "chainId"),
|
chainId: handleUint(fields[0], "chainId"),
|
||||||
nonce: handleNumber(fields[1], "nonce"),
|
nonce: handleNumber(fields[1], "nonce"),
|
||||||
maxPriorityFeePerGas: maxPriorityFeePerGas,
|
maxPriorityFeePerGas: handleUint(fields[2], "maxPriorityFeePerGas"),
|
||||||
maxFeePerGas: maxFeePerGas,
|
maxFeePerGas: handleUint(fields[3], "maxFeePerGas"),
|
||||||
gasPrice: null,
|
gasPrice: null,
|
||||||
gasLimit: handleUint(fields[4], "gasLimit"),
|
gasLimit: handleUint(fields[4], "gasLimit"),
|
||||||
to: handleAddress(fields[5]),
|
to: handleAddress(fields[5]),
|
||||||
@ -291,17 +311,19 @@ function _parseEip1559(data: Uint8Array): TransactionLike {
|
|||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _serializeEip1559(tx: TransactionLike, sig?: Signature): string {
|
function _serializeEip1559(tx: Transaction, sig?: Signature): string {
|
||||||
|
assertArgument(tx.isLondon(), "internal check failed; !london", "tx", tx);
|
||||||
|
|
||||||
const fields: Array<any> = [
|
const fields: Array<any> = [
|
||||||
formatNumber(tx.chainId || 0, "chainId"),
|
formatNumber(tx.chainId, "chainId"),
|
||||||
formatNumber(tx.nonce || 0, "nonce"),
|
formatNumber(tx.nonce, "nonce"),
|
||||||
formatNumber(tx.maxPriorityFeePerGas || 0, "maxPriorityFeePerGas"),
|
formatNumber(tx.maxPriorityFeePerGas, "maxPriorityFeePerGas"),
|
||||||
formatNumber(tx.maxFeePerGas || 0, "maxFeePerGas"),
|
formatNumber(tx.maxFeePerGas, "maxFeePerGas"),
|
||||||
formatNumber(tx.gasLimit || 0, "gasLimit"),
|
formatNumber(tx.gasLimit, "gasLimit"),
|
||||||
((tx.to != null) ? getAddress(tx.to): "0x"),
|
(tx.to || "0x"),
|
||||||
formatNumber(tx.value || 0, "value"),
|
formatNumber(tx.value, "value"),
|
||||||
(tx.data || "0x"),
|
tx.data,
|
||||||
(formatAccessList(tx.accessList || []))
|
formatAccessList(tx.accessList)
|
||||||
];
|
];
|
||||||
|
|
||||||
if (sig) {
|
if (sig) {
|
||||||
@ -341,16 +363,18 @@ function _parseEip2930(data: Uint8Array): TransactionLike {
|
|||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _serializeEip2930(tx: TransactionLike, sig?: Signature): string {
|
function _serializeEip2930(tx: Transaction, sig?: Signature): string {
|
||||||
|
assertArgument(tx.isBerlin(), "internal check failed; !berlin", "tx", tx);
|
||||||
|
|
||||||
const fields: any = [
|
const fields: any = [
|
||||||
formatNumber(tx.chainId || 0, "chainId"),
|
formatNumber(tx.chainId, "chainId"),
|
||||||
formatNumber(tx.nonce || 0, "nonce"),
|
formatNumber(tx.nonce, "nonce"),
|
||||||
formatNumber(tx.gasPrice || 0, "gasPrice"),
|
formatNumber(tx.gasPrice, "gasPrice"),
|
||||||
formatNumber(tx.gasLimit || 0, "gasLimit"),
|
formatNumber(tx.gasLimit, "gasLimit"),
|
||||||
((tx.to != null) ? getAddress(tx.to): "0x"),
|
(tx.to || "0x"),
|
||||||
formatNumber(tx.value || 0, "value"),
|
formatNumber(tx.value, "value"),
|
||||||
(tx.data || "0x"),
|
tx.data,
|
||||||
(formatAccessList(tx.accessList || []))
|
formatAccessList(tx.accessList)
|
||||||
];
|
];
|
||||||
|
|
||||||
if (sig) {
|
if (sig) {
|
||||||
@ -362,6 +386,71 @@ function _serializeEip2930(tx: TransactionLike, sig?: Signature): string {
|
|||||||
return concat([ "0x01", encodeRlp(fields)]);
|
return concat([ "0x01", encodeRlp(fields)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _parseEip4844(data: Uint8Array): TransactionLike {
|
||||||
|
const fields: any = decodeRlp(getBytes(data).slice(1));
|
||||||
|
|
||||||
|
assertArgument(Array.isArray(fields) && (fields.length === 11 || fields.length === 14),
|
||||||
|
"invalid field count for transaction type: 3", "data", hexlify(data));
|
||||||
|
|
||||||
|
const tx: TransactionLike = {
|
||||||
|
type: 3,
|
||||||
|
chainId: handleUint(fields[0], "chainId"),
|
||||||
|
nonce: handleNumber(fields[1], "nonce"),
|
||||||
|
maxPriorityFeePerGas: handleUint(fields[2], "maxPriorityFeePerGas"),
|
||||||
|
maxFeePerGas: handleUint(fields[3], "maxFeePerGas"),
|
||||||
|
gasPrice: null,
|
||||||
|
gasLimit: handleUint(fields[4], "gasLimit"),
|
||||||
|
to: handleAddress(fields[5]),
|
||||||
|
value: handleUint(fields[6], "value"),
|
||||||
|
data: hexlify(fields[7]),
|
||||||
|
accessList: handleAccessList(fields[8], "accessList"),
|
||||||
|
maxFeePerBlobGas: handleUint(fields[9], "maxFeePerBlobGas"),
|
||||||
|
blobVersionedHashes: fields[10]
|
||||||
|
};
|
||||||
|
|
||||||
|
assertArgument(tx.to != null, "invalid address for transaction type: 3", "data", data);
|
||||||
|
|
||||||
|
assertArgument(Array.isArray(tx.blobVersionedHashes), "invalid blobVersionedHashes: must be an array", "data", data);
|
||||||
|
for (let i = 0; i < tx.blobVersionedHashes.length; i++) {
|
||||||
|
assertArgument(isHexString(tx.blobVersionedHashes[i], 32), `invalid blobVersionedHash at index ${ i }: must be length 32`, "data", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsigned EIP-4844 Transaction
|
||||||
|
if (fields.length === 11) { return tx; }
|
||||||
|
|
||||||
|
tx.hash = keccak256(data);
|
||||||
|
|
||||||
|
_parseEipSignature(tx, fields.slice(11));
|
||||||
|
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _serializeEip4844(tx: Transaction, sig?: Signature): string {
|
||||||
|
assertArgument(tx.isCancun(), "internal check failed; !cancun", "tx", tx);
|
||||||
|
|
||||||
|
const fields: Array<any> = [
|
||||||
|
formatNumber(tx.chainId, "chainId"),
|
||||||
|
formatNumber(tx.nonce, "nonce"),
|
||||||
|
formatNumber(tx.maxPriorityFeePerGas, "maxPriorityFeePerGas"),
|
||||||
|
formatNumber(tx.maxFeePerGas, "maxFeePerGas"),
|
||||||
|
formatNumber(tx.gasLimit, "gasLimit"),
|
||||||
|
tx.to,
|
||||||
|
formatNumber(tx.value, "value"),
|
||||||
|
tx.data,
|
||||||
|
(formatAccessList(tx.accessList)),
|
||||||
|
formatNumber(tx.maxFeePerBlobGas, "maxFeePerBlobGas"),
|
||||||
|
formatHashes(tx.blobVersionedHashes, "blobVersionedHashes")
|
||||||
|
];
|
||||||
|
|
||||||
|
if (sig) {
|
||||||
|
fields.push(formatNumber(sig.yParity, "yParity"));
|
||||||
|
fields.push(toBeArray(sig.r));
|
||||||
|
fields.push(toBeArray(sig.s));
|
||||||
|
}
|
||||||
|
|
||||||
|
return concat([ "0x03", encodeRlp(fields)]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A **Transaction** describes an operation to be executed on
|
* A **Transaction** describes an operation to be executed on
|
||||||
* Ethereum by an Externally Owned Account (EOA). It includes
|
* Ethereum by an Externally Owned Account (EOA). It includes
|
||||||
@ -388,6 +477,8 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
#chainId: bigint;
|
#chainId: bigint;
|
||||||
#sig: null | Signature;
|
#sig: null | Signature;
|
||||||
#accessList: null | AccessList;
|
#accessList: null | AccessList;
|
||||||
|
#maxFeePerBlobGas: null | bigint;
|
||||||
|
#blobVersionedHashes: null | Array<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The transaction type.
|
* The transaction type.
|
||||||
@ -410,6 +501,9 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
case 2: case "london": case "eip-1559":
|
case 2: case "london": case "eip-1559":
|
||||||
this.#type = 2;
|
this.#type = 2;
|
||||||
break;
|
break;
|
||||||
|
case 3: case "cancun": case "eip-4844":
|
||||||
|
this.#type = 3;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
assertArgument(false, "unsupported transaction type", "type", value);
|
assertArgument(false, "unsupported transaction type", "type", value);
|
||||||
}
|
}
|
||||||
@ -423,6 +517,7 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
case 0: return "legacy";
|
case 0: return "legacy";
|
||||||
case 1: return "eip-2930";
|
case 1: return "eip-2930";
|
||||||
case 2: return "eip-1559";
|
case 2: return "eip-1559";
|
||||||
|
case 3: return "eip-4844";
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -432,7 +527,11 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
* The ``to`` address for the transaction or ``null`` if the
|
* The ``to`` address for the transaction or ``null`` if the
|
||||||
* transaction is an ``init`` transaction.
|
* transaction is an ``init`` transaction.
|
||||||
*/
|
*/
|
||||||
get to(): null | string { return this.#to; }
|
get to(): null | string {
|
||||||
|
const value = this.#to;
|
||||||
|
if (value == null && this.type === 3) { return ZeroAddress; }
|
||||||
|
return value;
|
||||||
|
}
|
||||||
set to(value: null | string) {
|
set to(value: null | string) {
|
||||||
this.#to = (value == null) ? null: getAddress(value);
|
this.#to = (value == null) ? null: getAddress(value);
|
||||||
}
|
}
|
||||||
@ -471,7 +570,7 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
get maxPriorityFeePerGas(): null | bigint {
|
get maxPriorityFeePerGas(): null | bigint {
|
||||||
const value = this.#maxPriorityFeePerGas;
|
const value = this.#maxPriorityFeePerGas;
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
if (this.type === 2) { return BN_0; }
|
if (this.type === 2 || this.type === 3) { return BN_0; }
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
@ -487,7 +586,7 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
get maxFeePerGas(): null | bigint {
|
get maxFeePerGas(): null | bigint {
|
||||||
const value = this.#maxFeePerGas;
|
const value = this.#maxFeePerGas;
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
if (this.type === 2) { return BN_0; }
|
if (this.type === 2 || this.type === 3) { return BN_0; }
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
@ -534,7 +633,11 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
get accessList(): null | AccessList {
|
get accessList(): null | AccessList {
|
||||||
const value = this.#accessList || null;
|
const value = this.#accessList || null;
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
if (this.type === 1 || this.type === 2) { return [ ]; }
|
if (this.type === 1 || this.type === 2 || this.type === 3) {
|
||||||
|
// @TODO: in v7, this should assign the value or become
|
||||||
|
// a live object itself, otherwise mutation is inconsistent
|
||||||
|
return [ ];
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
@ -543,6 +646,39 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
this.#accessList = (value == null) ? null: accessListify(value);
|
this.#accessList = (value == null) ? null: accessListify(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The max fee per blob gas for Cancun transactions.
|
||||||
|
*/
|
||||||
|
get maxFeePerBlobGas(): null | bigint {
|
||||||
|
const value = this.#maxFeePerBlobGas;
|
||||||
|
if (value == null && this.type === 3) { return BN_0; }
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
set maxFeePerBlobGas(value: null | BigNumberish) {
|
||||||
|
this.#maxFeePerBlobGas = (value == null) ? null: getBigInt(value, "maxFeePerBlobGas");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BLOB versioned hashes for Cancun transactions.
|
||||||
|
*/
|
||||||
|
get blobVersionedHashes(): null | Array<string> {
|
||||||
|
// @TODO: Mutation is inconsistent; if unset, the returned value
|
||||||
|
// cannot mutate the object, if set it can
|
||||||
|
let value = this.#blobVersionedHashes;
|
||||||
|
if (value == null && this.type === 3) { return [ ]; }
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
set blobVersionedHashes(value: null | Array<string>) {
|
||||||
|
if (value != null) {
|
||||||
|
assertArgument(Array.isArray(value), "blobVersionedHashes must be an Array", "value", value);
|
||||||
|
value = value.slice();
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
assertArgument(isHexString(value[i], 32), "invalid blobVersionedHash", `value[${ i }]`, value[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.#blobVersionedHashes = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Transaction with default values.
|
* Creates a new Transaction with default values.
|
||||||
*/
|
*/
|
||||||
@ -550,15 +686,17 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
this.#type = null;
|
this.#type = null;
|
||||||
this.#to = null;
|
this.#to = null;
|
||||||
this.#nonce = 0;
|
this.#nonce = 0;
|
||||||
this.#gasLimit = BigInt(0);
|
this.#gasLimit = BN_0;
|
||||||
this.#gasPrice = null;
|
this.#gasPrice = null;
|
||||||
this.#maxPriorityFeePerGas = null;
|
this.#maxPriorityFeePerGas = null;
|
||||||
this.#maxFeePerGas = null;
|
this.#maxFeePerGas = null;
|
||||||
this.#data = "0x";
|
this.#data = "0x";
|
||||||
this.#value = BigInt(0);
|
this.#value = BN_0;
|
||||||
this.#chainId = BigInt(0);
|
this.#chainId = BN_0;
|
||||||
this.#sig = null;
|
this.#sig = null;
|
||||||
this.#accessList = null;
|
this.#accessList = null;
|
||||||
|
this.#maxFeePerBlobGas = null;
|
||||||
|
this.#blobVersionedHashes = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -602,7 +740,6 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
* transaction are non-null.
|
* transaction are non-null.
|
||||||
*/
|
*/
|
||||||
isSigned(): this is (Transaction & { type: number, typeName: string, from: string, signature: Signature }) {
|
isSigned(): this is (Transaction & { type: number, typeName: string, from: string, signature: Signature }) {
|
||||||
//isSigned(): this is SignedTransaction {
|
|
||||||
return this.signature != null;
|
return this.signature != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -622,6 +759,8 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
return _serializeEip2930(this, this.signature);
|
return _serializeEip2930(this, this.signature);
|
||||||
case 2:
|
case 2:
|
||||||
return _serializeEip1559(this, this.signature);
|
return _serializeEip1559(this, this.signature);
|
||||||
|
case 3:
|
||||||
|
return _serializeEip4844(this, this.signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: ".serialized" });
|
assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: ".serialized" });
|
||||||
@ -641,6 +780,8 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
return _serializeEip2930(this);
|
return _serializeEip2930(this);
|
||||||
case 2:
|
case 2:
|
||||||
return _serializeEip1559(this);
|
return _serializeEip1559(this);
|
||||||
|
case 3:
|
||||||
|
return _serializeEip4844(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: ".unsignedSerialized" });
|
assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: ".unsignedSerialized" });
|
||||||
@ -651,7 +792,13 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
* supported transaction type.
|
* supported transaction type.
|
||||||
*/
|
*/
|
||||||
inferType(): number {
|
inferType(): number {
|
||||||
return <number>(this.inferTypes().pop());
|
const types = this.inferTypes();
|
||||||
|
|
||||||
|
// Prefer London (EIP-1559) over Cancun (BLOb)
|
||||||
|
if (types.indexOf(2) >= 0) { return 2; }
|
||||||
|
|
||||||
|
// Return the highest inferred type
|
||||||
|
return <number>(types.pop());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -664,6 +811,7 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
const hasGasPrice = this.gasPrice != null;
|
const hasGasPrice = this.gasPrice != null;
|
||||||
const hasFee = (this.maxFeePerGas != null || this.maxPriorityFeePerGas != null);
|
const hasFee = (this.maxFeePerGas != null || this.maxPriorityFeePerGas != null);
|
||||||
const hasAccessList = (this.accessList != null);
|
const hasAccessList = (this.accessList != null);
|
||||||
|
const hasBlob = (this.#maxFeePerBlobGas != null || this.#blobVersionedHashes);
|
||||||
|
|
||||||
//if (hasGasPrice && hasFee) {
|
//if (hasGasPrice && hasFee) {
|
||||||
// throw new Error("transaction cannot have gasPrice and maxFeePerGas");
|
// throw new Error("transaction cannot have gasPrice and maxFeePerGas");
|
||||||
@ -695,10 +843,13 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
} else if (hasAccessList) {
|
} else if (hasAccessList) {
|
||||||
types.push(1);
|
types.push(1);
|
||||||
types.push(2);
|
types.push(2);
|
||||||
|
} else if (hasBlob && this.to) {
|
||||||
|
types.push(3);
|
||||||
} else {
|
} else {
|
||||||
types.push(0);
|
types.push(0);
|
||||||
types.push(1);
|
types.push(1);
|
||||||
types.push(2);
|
types.push(2);
|
||||||
|
types.push(3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -736,10 +887,21 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
* This provides a Type Guard that the related properties are
|
* This provides a Type Guard that the related properties are
|
||||||
* non-null.
|
* non-null.
|
||||||
*/
|
*/
|
||||||
isLondon(): this is (Transaction & { type: 2, accessList: AccessList, maxFeePerGas: bigint, maxPriorityFeePerGas: bigint}) {
|
isLondon(): this is (Transaction & { type: 2, accessList: AccessList, maxFeePerGas: bigint, maxPriorityFeePerGas: bigint }) {
|
||||||
return (this.type === 2);
|
return (this.type === 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this transaction is an [[link-eip-4844]] BLOB
|
||||||
|
* transaction.
|
||||||
|
*
|
||||||
|
* This provides a Type Guard that the related properties are
|
||||||
|
* non-null.
|
||||||
|
*/
|
||||||
|
isCancun(): this is (Transaction & { type: 3, to: string, accessList: AccessList, maxFeePerGas: bigint, maxPriorityFeePerGas: bigint, maxFeePerBlobGas: bigint, blobVersionedHashes: Array<string> }) {
|
||||||
|
return (this.type === 3);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a copy of this transaciton.
|
* Create a copy of this transaciton.
|
||||||
*/
|
*/
|
||||||
@ -790,6 +952,7 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
switch(payload[0]) {
|
switch(payload[0]) {
|
||||||
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));
|
||||||
|
case 3: return Transaction.from(_parseEip4844(payload));
|
||||||
}
|
}
|
||||||
assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: "from" });
|
assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: "from" });
|
||||||
}
|
}
|
||||||
@ -802,11 +965,13 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
if (tx.gasPrice != null) { result.gasPrice = tx.gasPrice; }
|
if (tx.gasPrice != null) { result.gasPrice = tx.gasPrice; }
|
||||||
if (tx.maxPriorityFeePerGas != null) { result.maxPriorityFeePerGas = tx.maxPriorityFeePerGas; }
|
if (tx.maxPriorityFeePerGas != null) { result.maxPriorityFeePerGas = tx.maxPriorityFeePerGas; }
|
||||||
if (tx.maxFeePerGas != null) { result.maxFeePerGas = tx.maxFeePerGas; }
|
if (tx.maxFeePerGas != null) { result.maxFeePerGas = tx.maxFeePerGas; }
|
||||||
|
if (tx.maxFeePerBlobGas != null) { result.maxFeePerBlobGas = tx.maxFeePerBlobGas; }
|
||||||
if (tx.data != null) { result.data = tx.data; }
|
if (tx.data != null) { result.data = tx.data; }
|
||||||
if (tx.value != null) { result.value = tx.value; }
|
if (tx.value != null) { result.value = tx.value; }
|
||||||
if (tx.chainId != null) { result.chainId = tx.chainId; }
|
if (tx.chainId != null) { result.chainId = tx.chainId; }
|
||||||
if (tx.signature != null) { result.signature = Signature.from(tx.signature); }
|
if (tx.signature != null) { result.signature = Signature.from(tx.signature); }
|
||||||
if (tx.accessList != null) { result.accessList = tx.accessList; }
|
if (tx.accessList != null) { result.accessList = tx.accessList; }
|
||||||
|
if (tx.blobVersionedHashes != null) { result.blobVersionedHashes = tx.blobVersionedHashes; }
|
||||||
|
|
||||||
if (tx.hash != null) {
|
if (tx.hash != null) {
|
||||||
assertArgument(result.isSigned(), "unsigned transaction cannot define hash", "tx", tx);
|
assertArgument(result.isSigned(), "unsigned transaction cannot define hash", "tx", tx);
|
||||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user