Skip to content

Commit

Permalink
Feat: EIP1559 (flashbots#36)
Browse files Browse the repository at this point in the history
* feat: add eip1559 structs

* feat:EIP1559 struct rlp encoding and decoding + test

* fix decodeRLP_EIP1559 and add test

* add decodeRLP_EIP1559Request
  • Loading branch information
haythemsellami authored Jan 30, 2024
1 parent 85adad6 commit eae3da6
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 33 deletions.
130 changes: 130 additions & 0 deletions src/Transactions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,32 @@ library Transactions {
uint256 chainId;
}

struct EIP1559 {
address to;
uint64 gas;
uint64 maxFeePerGas;
uint64 maxPriorityFeePerGas;
uint64 value;
uint64 nonce;
bytes data;
uint64 chainId;
bytes accessList;
bytes r;
bytes s;
bytes v;
}

struct EIP1559Request {
address to;
uint64 gas;
uint64 maxFeePerGas;
uint64 maxPriorityFeePerGas;
uint64 value;
uint64 nonce;
bytes data;
uint64 chainId;
}

function encodeRLP(EIP155 memory txStruct) internal pure returns (bytes memory) {
bytes[] memory items = new bytes[](9);

Expand Down Expand Up @@ -74,6 +100,46 @@ library Transactions {
return RLPWriter.writeList(items);
}

function encodeRLP(EIP1559 memory txStruct) internal pure returns (bytes memory) {
bytes[] memory items = new bytes[](12);

items[0] = RLPWriter.writeUint(txStruct.chainId);
items[1] = RLPWriter.writeUint(txStruct.nonce);
items[2] = RLPWriter.writeUint(txStruct.maxPriorityFeePerGas);
items[3] = RLPWriter.writeUint(txStruct.maxFeePerGas);
items[4] = RLPWriter.writeUint(txStruct.gas);

if (txStruct.to == address(0)) {
items[5] = RLPWriter.writeBytes(bytes(""));
} else {
items[5] = RLPWriter.writeAddress(txStruct.to);
}

items[6] = RLPWriter.writeUint(txStruct.value);
items[7] = RLPWriter.writeBytes(txStruct.data);

if (txStruct.accessList.length == 0) {
items[8] = hex"c0"; // Empty list encoding
} else {
items[8] = RLPWriter.writeBytes(txStruct.accessList);
}

items[9] = RLPWriter.writeBytes(txStruct.v);
items[10] = RLPWriter.writeBytes(txStruct.r);
items[11] = RLPWriter.writeBytes(txStruct.s);

bytes memory rlpTxn = RLPWriter.writeList(items);

bytes memory txn = new bytes(1 + rlpTxn.length);
txn[0] = 0x02;

for (uint256 i = 0; i < rlpTxn.length; ++i) {
txn[i + 1] = rlpTxn[i];
}

return txn;
}

function decodeRLP_EIP155(bytes memory rlp) internal pure returns (EIP155 memory) {
EIP155 memory txStruct;

Expand Down Expand Up @@ -121,4 +187,68 @@ library Transactions {

return txStruct;
}

function decodeRLP_EIP1559(bytes memory rlp) internal pure returns (EIP1559 memory) {
EIP1559 memory txStruct;

bytes memory rlpWithoutPrefix = new bytes(rlp.length - 1);

for (uint256 i = 0; i < rlp.length - 1; ++i) {
rlpWithoutPrefix[i] = rlp[i + 1];
}

RLPReader.RLPItem[] memory ls = rlpWithoutPrefix.toRlpItem().toList();
require(ls.length == 12, "invalid transaction");

txStruct.chainId = uint64(ls[0].toUint());
txStruct.nonce = uint64(ls[1].toUint());
txStruct.maxPriorityFeePerGas = uint64(ls[2].toUint());
txStruct.maxFeePerGas = uint64(ls[3].toUint());
txStruct.gas = uint64(ls[4].toUint());

if (ls[5].toRlpBytes().length == 1) {
txStruct.to = address(0);
} else {
txStruct.to = ls[5].toAddress();
}

txStruct.value = uint64(ls[6].toUint());
txStruct.data = ls[7].toBytes();
txStruct.accessList = ls[8].toBytes();
txStruct.v = ls[9].toBytes();
txStruct.r = ls[10].toBytes();
txStruct.s = ls[11].toBytes();

return txStruct;
}

function decodeRLP_EIP1559Request(bytes memory rlp) internal pure returns (EIP1559Request memory) {
EIP1559Request memory txStruct;

bytes memory rlpWithoutPrefix = new bytes(rlp.length - 1);

for (uint256 i = 0; i < rlp.length - 1; ++i) {
rlpWithoutPrefix[i] = rlp[i + 1];
}

RLPReader.RLPItem[] memory ls = rlpWithoutPrefix.toRlpItem().toList();
require(ls.length == 8, "invalid transaction");

txStruct.chainId = uint64(ls[0].toUint());
txStruct.nonce = uint64(ls[1].toUint());
txStruct.maxPriorityFeePerGas = uint64(ls[2].toUint());
txStruct.maxFeePerGas = uint64(ls[3].toUint());
txStruct.gas = uint64(ls[4].toUint());

if (ls[5].toRlpBytes().length == 1) {
txStruct.to = address(0);
} else {
txStruct.to = ls[5].toAddress();
}

txStruct.value = uint64(ls[6].toUint());
txStruct.data = ls[7].toBytes();

return txStruct;
}
}
42 changes: 9 additions & 33 deletions src/protocols/Bundle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,59 +13,35 @@ library Bundle {
bytes[] txns;
}

function sendBundle(
string memory url,
BundleObj memory bundle
) internal view returns (bytes memory) {
function sendBundle(string memory url, BundleObj memory bundle) internal view returns (bytes memory) {
Suave.HttpRequest memory request = encodeBundle(bundle);
request.url = url;
return Suave.doHTTPRequest(request);
}

function encodeBundle(
BundleObj memory args
) internal pure returns (Suave.HttpRequest memory) {
function encodeBundle(BundleObj memory args) internal pure returns (Suave.HttpRequest memory) {
require(args.txns.length > 0, "Bundle: no txns");

bytes memory params = abi.encodePacked(
'{"blockNumber": "',
LibString.toHexString(args.blockNumber),
'", "txs": ['
);
bytes memory params =
abi.encodePacked('{"blockNumber": "', LibString.toHexString(args.blockNumber), '", "txs": [');
for (uint256 i = 0; i < args.txns.length; i++) {
params = abi.encodePacked(
params,
'"',
LibString.toHexString(args.txns[i]),
'"'
);
params = abi.encodePacked(params, '"', LibString.toHexString(args.txns[i]), '"');
if (i < args.txns.length - 1) {
params = abi.encodePacked(params, ",");
} else {
params = abi.encodePacked(params, "]");
}
}
if (args.minTimestamp > 0) {
params = abi.encodePacked(
params,
', "minTimestamp": ',
LibString.toString(args.minTimestamp)
);
params = abi.encodePacked(params, ', "minTimestamp": ', LibString.toString(args.minTimestamp));
}
if (args.maxTimestamp > 0) {
params = abi.encodePacked(
params,
', "maxTimestamp": ',
LibString.toString(args.maxTimestamp)
);
params = abi.encodePacked(params, ', "maxTimestamp": ', LibString.toString(args.maxTimestamp));
}
params = abi.encodePacked(params, "}");

bytes memory body = abi.encodePacked(
'{"jsonrpc":"2.0","method":"eth_sendBundle","params":[',
params,
'],"id":1}'
);
bytes memory body =
abi.encodePacked('{"jsonrpc":"2.0","method":"eth_sendBundle","params":[', params, '],"id":1}');

Suave.HttpRequest memory request;
request.method = "POST";
Expand Down
59 changes: 59 additions & 0 deletions test/Transactions.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,54 @@ contract TestTransactions is Test {
_testEIP155Transaction(txnWithoutToAddress, expected);
}

function testEIP1559TransactionRLPEncoding() public {
Transactions.EIP1559 memory txnWithToAddress = Transactions.EIP1559({
to: address(0xaea46A60368A7bD060eec7DF8CBa43b7EF41Ad85),
gas: 64744,
maxFeePerGas: 74341019612,
maxPriorityFeePerGas: 74341019612,
value: 0,
nonce: 38,
data: abi.encodePacked(
hex"a9059cbb00000000000000000000000061b7b515c1ec603cf21463bcac992b60fd610ca900000000000000000000000000000000000000000000002dbf877cf6ec677800"
),
chainId: 1,
accessList: bytes(""),
v: bytes(""),
r: abi.encodePacked(hex"8ee28a85ac42174b9e10c49613c0cddcf5d5a5ecb90bd516f81b45a957a64fe2"),
s: abi.encodePacked(hex"05349c1076cc83990f425773d6b5995474782f1fccf1b2e43529ac54ac6ae144")
});

bytes memory expected = abi.encodePacked(
hex"02f8b1012685114f11efdc85114f11efdc82fce894aea46a60368a7bd060eec7df8cba43b7ef41ad8580b844a9059cbb00000000000000000000000061b7b515c1ec603cf21463bcac992b60fd610ca900000000000000000000000000000000000000000000002dbf877cf6ec677800c080a08ee28a85ac42174b9e10c49613c0cddcf5d5a5ecb90bd516f81b45a957a64fe2a005349c1076cc83990f425773d6b5995474782f1fccf1b2e43529ac54ac6ae144"
);

_testEIP1559Transaction(txnWithToAddress, expected);

Transactions.EIP1559 memory txnWithoutToAddress = Transactions.EIP1559({
to: address(0),
gas: 64744,
maxFeePerGas: 74341019612,
maxPriorityFeePerGas: 74341019612,
value: 0,
nonce: 38,
data: abi.encodePacked(
hex"a9059cbb00000000000000000000000061b7b515c1ec603cf21463bcac992b60fd610ca900000000000000000000000000000000000000000000002dbf877cf6ec677800"
),
chainId: 1,
accessList: bytes(""),
v: bytes(""),
r: abi.encodePacked(hex"8ee28a85ac42174b9e10c49613c0cddcf5d5a5ecb90bd516f81b45a957a64fe2"),
s: abi.encodePacked(hex"05349c1076cc83990f425773d6b5995474782f1fccf1b2e43529ac54ac6ae144")
});

expected = abi.encodePacked(
hex"02f89d012685114f11efdc85114f11efdc82fce88080b844a9059cbb00000000000000000000000061b7b515c1ec603cf21463bcac992b60fd610ca900000000000000000000000000000000000000000000002dbf877cf6ec677800c080a08ee28a85ac42174b9e10c49613c0cddcf5d5a5ecb90bd516f81b45a957a64fe2a005349c1076cc83990f425773d6b5995474782f1fccf1b2e43529ac54ac6ae144"
);

_testEIP1559Transaction(txnWithoutToAddress, expected);
}

function _testEIP155Transaction(Transactions.EIP155 memory legacyTxn, bytes memory expectedRlp) public {
bytes memory rlp = Transactions.encodeRLP(legacyTxn);
assertEq0(rlp, expectedRlp);
Expand All @@ -55,4 +103,15 @@ contract TestTransactions is Test {
bytes memory rlp1 = Transactions.encodeRLP(legacyTxn1);
assertEq0(rlp1, expectedRlp);
}

function _testEIP1559Transaction(Transactions.EIP1559 memory eip1559Txn, bytes memory expectedRlp) public {
bytes memory rlp = Transactions.encodeRLP(eip1559Txn);
assertEq0(rlp, expectedRlp);

Transactions.EIP1559 memory eip1559Txn1 = Transactions.decodeRLP_EIP1559(rlp);

// re-encode to validate that the decoding was correct
bytes memory rlp1 = Transactions.encodeRLP(eip1559Txn1);
assertEq0(rlp1, expectedRlp);
}
}

0 comments on commit eae3da6

Please sign in to comment.