Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction RLP tweaks #30

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions src/Transactions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ library Transactions {
uint64 nonce;
bytes data;
uint64 chainId;
bytes r;
bytes s;
bytes v;
}

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

items[0] = RLPWriter.writeUint(txStruct.nonce);
Expand All @@ -36,9 +35,9 @@ library Transactions {
}
items[4] = RLPWriter.writeUint(txStruct.value);
items[5] = RLPWriter.writeBytes(txStruct.data);
items[6] = RLPWriter.writeBytes(txStruct.v);
items[7] = RLPWriter.writeBytes(txStruct.r);
items[8] = RLPWriter.writeBytes(txStruct.s);
items[6] = RLPWriter.writeUint(txStruct.chainId);
items[7] = RLPWriter.writeBytes("");
items[8] = RLPWriter.writeBytes("");

return RLPWriter.writeList(items);
}
Expand All @@ -61,9 +60,7 @@ library Transactions {

txStruct.value = uint64(ls[4].toUint());
txStruct.data = ls[5].toBytes();
txStruct.v = ls[6].toBytes();
txStruct.r = ls[7].toBytes();
txStruct.s = ls[8].toBytes();
txStruct.chainId = uint64(ls[6].toUint());

return txStruct;
}
Expand Down
79 changes: 79 additions & 0 deletions src/protocols/Bundle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.13;

import "../suavelib/Suave.sol";
import "solady/src/utils/LibString.sol";

// https://docs.flashbots.net/flashbots-auction/advanced/rpc-endpoint#eth_sendbundle
library Bundle {
struct BundleObj {
uint64 blockNumber;
uint64 minTimestamp;
uint64 maxTimestamp;
bytes[] txns;
}

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) {
require(args.txns.length > 0, "Bundle: no txns");

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]),
'"'
);
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)
);
}
if (args.maxTimestamp > 0) {
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}'
);

Suave.HttpRequest memory request;
request.method = "POST";
request.body = body;
request.headers = new string[](1);
request.headers[0] = "Content-Type: application/json";
request.withFlashbotsSignature = true;

return request;
}
}
21 changes: 8 additions & 13 deletions test/Transactions.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@ contract TestTransactions is Test {
value: 10,
nonce: 0,
data: bytes(""),
chainId: 0,
v: abi.encodePacked(hex"1b"),
r: abi.encodePacked(hex"9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f"),
s: abi.encodePacked(hex"8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1")
chainId: 0
});

bytes memory expected = abi.encodePacked(
hex"f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1"
hex"df800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a80808080"
);
_testLegacyTransaction(txnWithToAddress, expected);

Expand All @@ -33,19 +30,17 @@ contract TestTransactions is Test {
value: 10,
nonce: 1,
data: abi.encodePacked(hex"02"),
chainId: 0,
v: abi.encodePacked(hex"1b"),
r: abi.encodePacked(hex"754a33a9c37cfcf61cd61939fd93f5fe194b7d1ee6ef07490e8c880f3bd0d87d"),
s: abi.encodePacked(hex"715bd50fa2c24e2ce0ea595025a44a39ac238558882f9f07dd885ddc51839419")
chainId: 0
});

expected = abi.encodePacked(
hex"f84b010a82c350800a021ba0754a33a9c37cfcf61cd61939fd93f5fe194b7d1ee6ef07490e8c880f3bd0d87da0715bd50fa2c24e2ce0ea595025a44a39ac238558882f9f07dd885ddc51839419"
);
expected = abi.encodePacked(hex"cb010a82c350800a02808080");
_testLegacyTransaction(txnWithoutToAddress, expected);
}

function _testLegacyTransaction(Transactions.Legacy memory legacyTxn, bytes memory expectedRlp) public {
function _testLegacyTransaction(
Transactions.Legacy memory legacyTxn,
bytes memory expectedRlp
) public {
bytes memory rlp = Transactions.encodeRLP(legacyTxn);
assertEq0(rlp, expectedRlp);

Expand Down
40 changes: 40 additions & 0 deletions test/protocols/Bundle.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "src/protocols/Bundle.sol";
import "src/suavelib/Suave.sol";

contract EthSendBundle is Test {
function testEthSendBundleEncode() public {
Bundle.BundleObj memory bundle;
bundle.blockNumber = 1;
bundle.txns = new bytes[](1);
bundle.txns[0] = hex"1234";

Suave.HttpRequest memory request = Bundle.encodeBundle(bundle);
assertEq(
string(request.body),
'{"jsonrpc":"2.0","method":"eth_sendBundle","params":[{"blockNumber": "0x01", "txs": ["0x1234"]}],"id":1}'
);
assertTrue(request.withFlashbotsSignature);

// encode with 'minTimestamp'
bundle.minTimestamp = 2;

Suave.HttpRequest memory request2 = Bundle.encodeBundle(bundle);
assertEq(
string(request2.body),
'{"jsonrpc":"2.0","method":"eth_sendBundle","params":[{"blockNumber": "0x01", "txs": ["0x1234"], "minTimestamp": 2}],"id":1}'
);

// encode with 'maxTimestamp'
bundle.maxTimestamp = 3;

Suave.HttpRequest memory request3 = Bundle.encodeBundle(bundle);
assertEq(
string(request3.body),
'{"jsonrpc":"2.0","method":"eth_sendBundle","params":[{"blockNumber": "0x01", "txs": ["0x1234"], "minTimestamp": 2, "maxTimestamp": 3}],"id":1}'
);
}
}
Loading