Skip to content

Commit

Permalink
Bundle json decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
jinmel committed Feb 22, 2024
1 parent e3ae5c1 commit 63ca911
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 0 deletions.
44 changes: 44 additions & 0 deletions src/protocols/Bundle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
pragma solidity ^0.8.13;

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

// https://docs.flashbots.net/flashbots-auction/advanced/rpc-endpoint#eth_sendbundle
library Bundle {
Expand All @@ -13,6 +15,9 @@ library Bundle {
bytes[] txns;
}

using JSONParserLib for string;
using JSONParserLib for JSONParserLib.Item;

function sendBundle(string memory url, BundleObj memory bundle) internal returns (bytes memory) {
Suave.HttpRequest memory request = encodeBundle(bundle);
request.url = url;
Expand Down Expand Up @@ -52,4 +57,43 @@ library Bundle {

return request;
}

function _stripQuotesAndPrefix(string memory s) internal pure returns (string memory) {
bytes memory strBytes = bytes(s);
bytes memory result = new bytes(strBytes.length - 4);
for (uint256 i = 3; i < strBytes.length - 1; i++) {
result[i - 3] = strBytes[i];
}
return string(result);
}

function decodeBundle(string memory bundleJson) public pure returns (Bundle.BundleObj memory) {
JSONParserLib.Item memory root = bundleJson.parse();
JSONParserLib.Item memory txnsNode = root.at('"txs"');
Bundle.BundleObj memory bundle;
require(txnsNode.isArray(), "Bundle: txs is not an array");
uint256 txnsLength = txnsNode.size();
bytes[] memory txns = new bytes[](txnsLength);

for (uint256 i = 0; i < txnsLength; i++) {
JSONParserLib.Item memory txnNode = txnsNode.at(i);
bytes memory txn = HexStrings.fromHexString(_stripQuotesAndPrefix(txnNode.value()));
txns[i] = txn;
}
bundle.txns = txns;

require(root.at('"blockNumber"').isString(), "Bundle: blockNumber is not a string");
bundle.blockNumber = uint64(root.at('"blockNumber"').value().decodeString().parseUintFromHex());

if (root.at('"minTimestamp"').isNumber()) {
bundle.minTimestamp = uint64(root.at('"minTimestamp"').value().parseUint());
}

if (root.at('"maxTimestamp"').isNumber()) {
bundle.maxTimestamp = uint64(root.at('"maxTimestamp"').value().parseUint());
}

bundle.txns = txns;
return bundle;
}
}
29 changes: 29 additions & 0 deletions src/utils/HexStrings.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Hex string to bytes parser. Used for decoding JSON encoded data.
library HexStrings {
function _fromHexChar(uint8 c) internal pure returns (uint8) {
if (bytes1(c) >= bytes1("0") && bytes1(c) <= bytes1("9")) {
return c - uint8(bytes1("0"));
}
if (bytes1(c) >= bytes1("a") && bytes1(c) <= bytes1("f")) {
return 10 + c - uint8(bytes1("a"));
}
if (bytes1(c) >= bytes1("A") && bytes1(c) <= bytes1("F")) {
return 10 + c - uint8(bytes1("A"));
}
revert("fail");
}

// Convert an hexadecimal string to raw bytes
function fromHexString(string memory s) internal pure returns (bytes memory) {
bytes memory ss = bytes(s);
require(ss.length % 2 == 0); // length must be even
bytes memory r = new bytes(ss.length / 2);
for (uint256 i = 0; i < ss.length / 2; ++i) {
r[i] = bytes1(_fromHexChar(uint8(ss[2 * i])) * 16 + _fromHexChar(uint8(ss[2 * i + 1])));
}
return r;
}
}
17 changes: 17 additions & 0 deletions test/protocols/Bundle.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "src/protocols/Bundle.sol";
import "src/suavelib/Suave.sol";
import "solady/src/utils/LibString.sol";

contract EthSendBundle is Test {
using LibString for *;

function testEthSendBundleEncode() public {
Bundle.BundleObj memory bundle;
bundle.blockNumber = 1;
Expand Down Expand Up @@ -37,4 +40,18 @@ contract EthSendBundle is Test {
'{"jsonrpc":"2.0","method":"eth_sendBundle","params":[{"blockNumber": "0x01", "txs": ["0x1234"], "minTimestamp": 2, "maxTimestamp": 3}],"id":1}'
);
}

function testBundleDecode() public {
string memory json = "{" '"blockNumber": "0xdead",' '"minTimestamp": 1625072400,'
'"maxTimestamp": 1625076000,' '"txs": [' '"0xdeadbeef",' '"0xc0ffee",' '"0x00aabb"' "]" "}";

Bundle.BundleObj memory bundle = Bundle.decodeBundle(json);
assertEq(bundle.blockNumber, 0xdead);
assertEq(bundle.minTimestamp, 1625072400);
assertEq(bundle.maxTimestamp, 1625076000);
assertEq(bundle.txns.length, 3);
assertEq(bundle.txns[0].toHexString(), "0xdeadbeef");
assertEq(bundle.txns[1].toHexString(), "0xc0ffee");
assertEq(bundle.txns[2].toHexString(), "0x00aabb");
}
}

0 comments on commit 63ca911

Please sign in to comment.