Skip to content

Commit

Permalink
feat: update test vectors + decode eip4844 txs
Browse files Browse the repository at this point in the history
  • Loading branch information
merklefruit committed Sep 19, 2024
1 parent c1ce662 commit e82ce24
Show file tree
Hide file tree
Showing 24 changed files with 1,067 additions and 343 deletions.
16 changes: 11 additions & 5 deletions bolt-contracts/src/lib/TransactionDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ library TransactionDecoder {
bytes data;
bytes[] accessList;
uint256 maxFeePerBlobGas;
bytes[] blobVersionedHashes;
bytes32[] blobVersionedHashes;
bytes sig;
uint64 legacyV;
}
Expand Down Expand Up @@ -292,15 +292,15 @@ library TransactionDecoder {
RLPReader.RLPItem[] memory accessListItems = fields[8].readList();
transaction.accessList = new bytes[](accessListItems.length);
for (uint256 i = 0; i < accessListItems.length; i++) {
transaction.accessList[i] = accessListItems[i].readBytes();
transaction.accessList[i] = accessListItems[i].readRawBytes();
}

transaction.maxFeePerBlobGas = fields[9].readUint256();

RLPReader.RLPItem[] memory blobVersionedHashesItems = fields[10].readList();
transaction.blobVersionedHashes = new bytes[](blobVersionedHashesItems.length);
transaction.blobVersionedHashes = new bytes32[](blobVersionedHashesItems.length);
for (uint256 i = 0; i < blobVersionedHashesItems.length; i++) {
transaction.blobVersionedHashes[i] = blobVersionedHashesItems[i].readBytes();
transaction.blobVersionedHashes[i] = blobVersionedHashesItems[i].readBytes32();
}

if (fields.length == 11) {
Expand Down Expand Up @@ -437,7 +437,13 @@ library TransactionDecoder {
fields[8] = RLPWriter.writeList(accessList);

fields[9] = RLPWriter.writeUint(transaction.maxFeePerBlobGas);
fields[10] = RLPWriter.writeList(transaction.blobVersionedHashes);

bytes[] memory blobVersionedHashes = new bytes[](transaction.blobVersionedHashes.length);
for (uint256 i = 0; i < transaction.blobVersionedHashes.length; i++) {
// Decode bytes32 as uint256 (RLPWriter doesn't support bytes32 but they are equivalent)
blobVersionedHashes[i] = RLPWriter.writeUint(uint256(transaction.blobVersionedHashes[i]));
}
fields[10] = RLPWriter.writeList(blobVersionedHashes);

// EIP-2718 envelope
unsignedTx = abi.encodePacked(uint8(TxType.Eip4844), RLPWriter.writeList(fields));
Expand Down
65 changes: 51 additions & 14 deletions bolt-contracts/test/TransactionDecoder.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {Test, console} from "forge-std/Test.sol";
import {TransactionDecoder} from "../src/lib/TransactionDecoder.sol";
import {BytesUtils} from "../src/lib/BytesUtils.sol";

// Use a contract to expose internal library functions
// We use a contract to expose internal library functions
contract DecoderImpl {
function decodeEnveloped(
bytes memory raw
Expand All @@ -32,35 +32,40 @@ contract TransactionDecoderTest is Test {

DecoderImpl decoder;

uint8 constant TEST_CASE_COUNT = 20;

struct TestCase {
string name;
uint256 privateKey;
bytes unsignedLegacy;
bytes unsignedEip155;
bytes unsignedBerlin;
bytes unsignedLondon;
bytes unsignedCancun;
bytes signedLegacy;
bytes signedEip155;
bytes signedBerlin;
bytes signedLondon;
bytes signedCancun;
TransactionDecoder.Transaction transaction;
}

function setUp() public {
decoder = new DecoderImpl();
}

function testDecodeAllTestCases() public view {
// Cycle through all test cases and run them one by one
for (uint8 i = 0; i < TEST_CASE_COUNT; i++) {
function testDecodeAllTestCases() public {
uint256 i = 0;
while (true) {
string memory path = _getTestCasePath(i);
if (!vm.isFile(path)) break;

// Cycle through all test cases and run them one by one
_decodeTestCase(i);
i++;
}
}

function _decodeTestCase(
uint8 id
uint256 id
) internal view {
TestCase memory testCase = _readTestCase(id);

Expand All @@ -87,6 +92,21 @@ contract TransactionDecoderTest is Test {
_assertTransaction(TransactionDecoder.TxType.Eip1559, decodedSignedLondon, testCase.transaction, true);
assertEq(decodedSignedLondon.unsigned(), testCase.unsignedLondon);
assertEq(decodedSignedLondon.recoverSender(), vm.addr(testCase.privateKey));

// Type 3 with blob fields
TransactionDecoder.Transaction memory decodedSignedCancun = decoder.decodeEnveloped(testCase.signedCancun);
_assertTransaction(TransactionDecoder.TxType.Eip4844, decodedSignedCancun, testCase.transaction, true);
assertEq(decodedSignedCancun.unsigned(), testCase.unsignedCancun);
assertEq(decodedSignedCancun.recoverSender(), vm.addr(testCase.privateKey));
}

// Helper to get the path of a test case file based on its index
function _getTestCasePath(
uint256 id
) internal pure returns (string memory) {
// Location of the test cases on disk (relative to the project root)
// Example: ./test/testdata/transactions/random_10.json
return string.concat("./test/testdata/transactions/random_", vm.toString(id), ".json");
}

function _assertTransaction(
Expand All @@ -98,7 +118,7 @@ contract TransactionDecoderTest is Test {
assertEq(uint8(decoded.txType), uint8(txType));

if (!isEip155) {
// Note: Pre-EIP-155 transactions have a chainId of 0
// Pre-EIP-155 transactions have a chainId of 0
assertEq(decoded.chainId, 0);
} else {
assertEq(decoded.chainId, expected.chainId);
Expand All @@ -115,21 +135,29 @@ contract TransactionDecoderTest is Test {
}

if (uint8(txType) >= 1) {
// TODO: add support for parsing EIP-2930. This is not strictly needed right now.
// We keep access lists as opaque bytes for now, because we simply re-encode
// them to obtain the unsigned transaction. So we can't compare them directly.
// assertEq(decoded.accessList, expected.accessList);
}

if (uint8(txType) >= 2) {
assertEq(decoded.maxFeePerGas, expected.maxFeePerGas);
assertEq(decoded.maxPriorityFeePerGas, expected.maxPriorityFeePerGas);
}

if (uint8(txType) == 3) {
assertEq(decoded.maxFeePerBlobGas, expected.maxFeePerBlobGas);
assertEq(decoded.blobVersionedHashes.length, expected.blobVersionedHashes.length);
for (uint256 i = 0; i < decoded.blobVersionedHashes.length; i++) {
assertEq(decoded.blobVersionedHashes[i], expected.blobVersionedHashes[i]);
}
}
}

function _readTestCase(
uint8 id
uint256 id
) public view returns (TestCase memory) {
string memory base = "./test/testdata/transactions/random_";
string memory file = vm.readFile(string.concat(base, vm.toString(uint256(id)), ".json"));
string memory file = vm.readFile(_getTestCasePath(id));

TransactionDecoder.Transaction memory transaction = TransactionDecoder.Transaction({
chainId: uint64(_parseUintFromBytes(vm.parseJsonBytes(file, ".transaction.chainId"))),
Expand All @@ -141,12 +169,14 @@ contract TransactionDecoderTest is Test {
nonce: vm.parseJsonUint(file, ".transaction.nonce"),
to: vm.parseJsonAddress(file, ".transaction.to"),
value: _parseUintFromBytes(vm.parseJsonBytes(file, ".transaction.value")),
maxFeePerBlobGas: _parseUintFromBytes(vm.parseJsonBytes(file, ".transaction.maxFeePerBlobGas")),
blobVersionedHashes: vm.parseJsonBytes32Array(file, ".transaction.blobVersionedHashes"),
// Note: These fields aren't present in the test cases so they can be skipped.
// These are tested indirectly by the signature and preimage checks.
txType: TransactionDecoder.TxType.Legacy,
accessList: new bytes[](0),
maxFeePerBlobGas: 0, // TODO: add support for EIP-4844
blobVersionedHashes: new bytes[](0), // TODO: add support for EIP-4844
// Signature is checked by recovering the sender and comparing it to the pubkey
// derived from the private key in the test case.
sig: "",
// Note: these fields are just internal helpers for the decoder library.
legacyV: 0,
Expand All @@ -160,17 +190,24 @@ contract TransactionDecoderTest is Test {
unsignedEip155: vm.parseJsonBytes(file, ".unsignedEip155"),
unsignedBerlin: vm.parseJsonBytes(file, ".unsignedBerlin"),
unsignedLondon: vm.parseJsonBytes(file, ".unsignedLondon"),
unsignedCancun: vm.parseJsonBytes(file, ".unsignedCancun"),
signedLegacy: vm.parseJsonBytes(file, ".signedLegacy"),
signedEip155: vm.parseJsonBytes(file, ".signedEip155"),
signedBerlin: vm.parseJsonBytes(file, ".signedBerlin"),
signedLondon: vm.parseJsonBytes(file, ".signedLondon"),
signedCancun: vm.parseJsonBytes(file, ".signedCancun"),
transaction: transaction
});
}

// Helper to parse an uint from bytes padded to the left
function _parseUintFromBytes(
bytes memory data
) internal pure returns (uint256) {
return uint256(BytesUtils.toBytes32PadLeft(data));
}

function _parseOpaqueAccessList(
bytes memory data
) internal pure returns (bytes[] memory) {}
}
2 changes: 1 addition & 1 deletion bolt-contracts/test/testdata/transactions/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Ethereum transaction test cases

This directory contains test cases for Ethereum transactions taken from the
[Ethers.js test suite](https://github.com/ethers-io/ethers.js/blob/5aba4963e3e8ddfc912747076f5b7fe7a743cfe2/testcases/transaction.json.gz).
[Ethers.js test suite](https://github.com/ethers-io/ethers.js/blob/5aba4963e3e8ddfc912747076f5b7fe7a743cfe2/testcases/transactions.json.gz).
38 changes: 35 additions & 3 deletions bolt-contracts/test/testdata/transactions/random_0.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "random-0",
"transaction": {
"to": "0x6eb893e3466931517a04a17d153a6330c3f2f1dd",
"to": "0x6Eb893e3466931517a04a17D153a6330c3f2f1dD",
"nonce": 648,
"gasLimit": "0x9d",
"gasPrice": "0x237e",
Expand All @@ -10,15 +10,47 @@
"data": "0x889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33c",
"value": "0xc854",
"accessList": [],
"chainId": "0x8404bf1f"
"chainId": "0x8404bf1f",
"maxFeePerBlobGas": "0x2ddc4988",
"blobVersionedHashes": [
"0x01ecbc98026bec75cc99701e9dbf9d1a7edf9db4106e01c171822da8b1f7f392",
"0x016b567cab2d2fa35658d3bac34b81cf3cc667b9a9fbae9ed58ac961dfc5d96b"
]
},
"privateKey": "0x2bf558dce44ca98616ee629199215ae5401c97040664637c48e3b74e66bcb3ae",
"unsignedLegacy": "0xf85682028882237e819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33c",
"unsignedEip155": "0xf85d82028882237e819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33c848404bf1f8080",
"unsignedBerlin": "0x01f85c848404bf1f82028882237e819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33cc0",
"unsignedLondon": "0x02f862848404bf1f820288832c7e6384346d9246819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33cc0",
"unsignedCancun": "0x03f8ab848404bf1f820288832c7e6384346d9246819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33cc0842ddc4988f842a001ecbc98026bec75cc99701e9dbf9d1a7edf9db4106e01c171822da8b1f7f392a0016b567cab2d2fa35658d3bac34b81cf3cc667b9a9fbae9ed58ac961dfc5d96b",
"signedLegacy": "0xf89982028882237e819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33c1ba0ba61344dc955b2f0e5dbc3c65e023e1c718539465131acb8a51b2ef75620114aa03366e9f2294bf2eca7322f3954b9b38745c40602239e3d7fa693667206907518",
"signedEip155": "0xf89e82028882237e819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33c850108097e62a0f141fe1b7e2fc1ed5d2b6ea4f04f92053e18f07274e2bda1c6852438c1895229a075553a7ae158a3fd46f75b547e847b59e2876f9a42de7a26d016db33232516de",
"signedBerlin": "0x01f89f848404bf1f82028882237e819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33cc080a0775f29642af1045b40e5beae8e6bce2dc9e222023b7a50372be6824dbb7434fba05dacfff85752a0b9fd860bc751c17235a670d318a8b9494d664c1b87e33ac8dd",
"signedLondon": "0x02f8a5848404bf1f820288832c7e6384346d9246819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33cc080a0f1003f96c6c6620dd46db36d2ae9f12d363947eb0db088c678b6ad1cf494aa6fa06085b5abbf448de5d622dc820da590cfdb6bb77b41c6650962b998a941f8d701"
"signedLondon": "0x02f8a5848404bf1f820288832c7e6384346d9246819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33cc080a0f1003f96c6c6620dd46db36d2ae9f12d363947eb0db088c678b6ad1cf494aa6fa06085b5abbf448de5d622dc820da590cfdb6bb77b41c6650962b998a941f8d701",
"signedCancun": "0x03f8ee848404bf1f820288832c7e6384346d9246819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33cc0842ddc4988f842a001ecbc98026bec75cc99701e9dbf9d1a7edf9db4106e01c171822da8b1f7f392a0016b567cab2d2fa35658d3bac34b81cf3cc667b9a9fbae9ed58ac961dfc5d96b01a092109dabedb5dcd157eddaee3ce39c4c467589b338878b3434181d93ea09c099a0015f8b0ca76ad70e4a69a15078232b77871b8c59b30b2279066064dff3afef47",
"signatureLegacy": {
"r": "0xba61344dc955b2f0e5dbc3c65e023e1c718539465131acb8a51b2ef75620114a",
"s": "0x3366e9f2294bf2eca7322f3954b9b38745c40602239e3d7fa693667206907518",
"v": "0x1b"
},
"signatureEip155": {
"r": "0xf141fe1b7e2fc1ed5d2b6ea4f04f92053e18f07274e2bda1c6852438c1895229",
"s": "0x75553a7ae158a3fd46f75b547e847b59e2876f9a42de7a26d016db33232516de",
"v": "0x108097e62"
},
"signatureBerlin": {
"r": "0x775f29642af1045b40e5beae8e6bce2dc9e222023b7a50372be6824dbb7434fb",
"s": "0x5dacfff85752a0b9fd860bc751c17235a670d318a8b9494d664c1b87e33ac8dd",
"v": "0x0"
},
"signatureLondon": {
"r": "0xf1003f96c6c6620dd46db36d2ae9f12d363947eb0db088c678b6ad1cf494aa6f",
"s": "0x6085b5abbf448de5d622dc820da590cfdb6bb77b41c6650962b998a941f8d701",
"v": "0x0"
},
"signatureCancun": {
"r": "0x92109dabedb5dcd157eddaee3ce39c4c467589b338878b3434181d93ea09c099",
"s": "0x015f8b0ca76ad70e4a69a15078232b77871b8c59b30b2279066064dff3afef47",
"v": "0x1"
}
}
Loading

0 comments on commit e82ce24

Please sign in to comment.