From e681011881ea1ed8d08ac70f1ccf710f9b80e5af Mon Sep 17 00:00:00 2001 From: Jinsuk Park Date: Fri, 23 Feb 2024 15:33:28 -0800 Subject: [PATCH 01/15] Suavex rpc request library --- src/protocols/Suavex.sol | 91 ++++++ src/utils/JsonWriter.sol | 549 ++++++++++++++++++++++++++++++++++++ test/protocols/Suavex.t.sol | 49 ++++ 3 files changed, 689 insertions(+) create mode 100644 src/protocols/Suavex.sol create mode 100644 src/utils/JsonWriter.sol create mode 100644 test/protocols/Suavex.t.sol diff --git a/src/protocols/Suavex.sol b/src/protocols/Suavex.sol new file mode 100644 index 0000000..daa2862 --- /dev/null +++ b/src/protocols/Suavex.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "../suavelib/Suave.sol"; +import "../utils/JsonWriter.sol"; +import "./Bundle.sol"; +import "solady/src/utils/LibString.sol"; + +library Suavex { + using JsonWriter for JsonWriter.Json; + using LibString for *; + + function buildEthBlock(string memory url, Suave.BuildBlockArgs memory args, Bundle.BundleObj[] memory bundles) + public + returns (bytes memory) + { + string memory params = encodeBuildEthBlock(args, bundles); + bytes memory body = encodeRpcRequest("suavex_buildEthBlockFromBundles", params, 1); + Suave.HttpRequest memory request; + request.url = url; + request.method = "POST"; + request.body = body; + request.headers = new string[](1); + request.headers[0] = "Content-Type: application/json"; + request.withFlashbotsSignature = true; + return Suave.doHTTPRequest(request); + } + + function encodeBuildEthBlock(Suave.BuildBlockArgs memory args, Bundle.BundleObj[] memory bundles) + internal + pure + returns (string memory) + { + JsonWriter.Json memory writer; + + writer = writer.writeStartObject(); + + // args + writer = writer.writeStartObject("args"); + + writer = writer.writeUintProperty("slot", args.slot); + writer = writer.writeBytesProperty("proposerPubkey", args.proposerPubkey); + writer = writer.writeStringProperty("parent", abi.encodePacked(args.parent).toHexString()); + writer = writer.writeUintProperty("timestamp", args.timestamp); + writer = writer.writeAddressProperty("feeRecipient", args.feeRecipient); + writer = writer.writeUintProperty("gasLimit", args.gasLimit); + writer = writer.writeStringProperty("random", abi.encodePacked(args.random).toHexString()); + + // args - withdrawals + writer = writer.writeStartArray("withdrawals"); + for (uint256 i = 0; i < args.withdrawals.length; i++) { + writer = writer.writeStartObject(); + writer = writer.writeStringProperty("index", args.withdrawals[i].index.toHexString()); + writer = writer.writeStringProperty("validator", args.withdrawals[i].validator.toHexString()); + writer = writer.writeAddressProperty("address", args.withdrawals[i].Address); + writer = writer.writeStringProperty("amount", args.withdrawals[i].amount.toHexString()); + writer = writer.writeEndObject(); + } + writer = writer.writeEndArray(); + + writer = writer.writeBytesProperty("extra", args.extra); + writer = writer.writeBytesProperty("beaconRoot", abi.encodePacked(args.beaconRoot)); + writer = writer.writeBooleanProperty("fillPending", args.fillPending); + + writer = writer.writeEndObject(); + + // bundles + writer = writer.writeStartArray("bundles"); + + for (uint256 i = 0; i < bundles.length; i++) { + writer = writer.writeStartObject(); + writer = writer.writeStringProperty("blockNumber", bundles[i].blockNumber.toHexString()); + writer = writer.writeStringProperty("minTimestamp", bundles[i].minTimestamp.toHexString()); + writer = writer.writeStringProperty("maxTimestamp", bundles[i].maxTimestamp.toHexString()); + + writer = writer.writeStartArray("txs"); + for (uint256 j = 0; j < bundles[i].txns.length; j++) { + writer = writer.writeStringValue(bundles[i].txns[j].toHexString()); + } + writer = writer.writeEndArray(); + writer = writer.writeEndObject(); + } + writer = writer.writeEndArray(); + writer = writer.writeEndObject(); + return writer.value; + } + + function encodeRpcRequest(string memory method, string memory params, uint256 id) internal pure returns (bytes memory) { + return abi.encodePacked('{"jsonrpc":"2.0","method":"', method, '","params":[', params, '],"id":', LibString.toString(id), '}'); + } +} diff --git a/src/utils/JsonWriter.sol b/src/utils/JsonWriter.sol new file mode 100644 index 0000000..dd344c3 --- /dev/null +++ b/src/utils/JsonWriter.sol @@ -0,0 +1,549 @@ +//SPDX-License-Identifier: MIT +// Copied from: https://github.com/bmeredith/solidity-json-writer +pragma solidity ^0.8.0; + +import "solady/src/utils/Base64.sol"; + +library JsonWriter { + using JsonWriter for string; + + struct Json { + int256 depthBitTracker; + string value; + } + + bytes1 constant BACKSLASH = bytes1(uint8(92)); + bytes1 constant BACKSPACE = bytes1(uint8(8)); + bytes1 constant CARRIAGE_RETURN = bytes1(uint8(13)); + bytes1 constant DOUBLE_QUOTE = bytes1(uint8(34)); + bytes1 constant FORM_FEED = bytes1(uint8(12)); + bytes1 constant FRONTSLASH = bytes1(uint8(47)); + bytes1 constant HORIZONTAL_TAB = bytes1(uint8(9)); + bytes1 constant NEWLINE = bytes1(uint8(10)); + + string constant TRUE = "true"; + string constant FALSE = "false"; + bytes1 constant OPEN_BRACE = "{"; + bytes1 constant CLOSED_BRACE = "}"; + bytes1 constant OPEN_BRACKET = "["; + bytes1 constant CLOSED_BRACKET = "]"; + bytes1 constant LIST_SEPARATOR = ","; + + int256 constant MAX_INT256 = type(int256).max; + + /** + * @dev Writes the beginning of a JSON array. + */ + function writeStartArray(Json memory json) internal pure returns (Json memory) { + return writeStart(json, OPEN_BRACKET); + } + + /** + * @dev Writes the beginning of a JSON array with a property name as the key. + */ + function writeStartArray(Json memory json, string memory propertyName) internal pure returns (Json memory) { + return writeStart(json, propertyName, OPEN_BRACKET); + } + + /** + * @dev Writes the beginning of a JSON object. + */ + function writeStartObject(Json memory json) internal pure returns (Json memory) { + return writeStart(json, OPEN_BRACE); + } + + /** + * @dev Writes the beginning of a JSON object with a property name as the key. + */ + function writeStartObject(Json memory json, string memory propertyName) internal pure returns (Json memory) { + return writeStart(json, propertyName, OPEN_BRACE); + } + + /** + * @dev Writes the end of a JSON array. + */ + function writeEndArray(Json memory json) internal pure returns (Json memory) { + return writeEnd(json, CLOSED_BRACKET); + } + + /** + * @dev Writes the end of a JSON object. + */ + function writeEndObject(Json memory json) internal pure returns (Json memory) { + return writeEnd(json, CLOSED_BRACE); + } + + /** + * @dev Writes the property name and bytes value (as a JSON string) as part of a name/value pair of a JSON object. + */ + function writeBytesProperty(Json memory json, string memory propertyName, bytes memory value) + internal + pure + returns (Json memory) + { + if (json.depthBitTracker < 0) { + json.value = string( + abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": "', Base64.encode(value), '"') + ); + } else { + json.value = string(abi.encodePacked(json.value, '"', propertyName, '": "', Base64.encode(value), '"')); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the bytes value (as a JSON string) as an element of a JSON array. + */ + function writeBytesValue(Json memory json, bytes memory value) internal pure returns (Json memory) { + if (json.depthBitTracker < 0) { + json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', Base64.encode(value), '"')); + } else { + json.value = string(abi.encodePacked(json.value, '"', Base64.encode(value), '"')); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the property name and address value (as a JSON string) as part of a name/value pair of a JSON object. + */ + function writeAddressProperty(Json memory json, string memory propertyName, address value) + internal + pure + returns (Json memory) + { + if (json.depthBitTracker < 0) { + json.value = string( + abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": "', addressToString(value), '"') + ); + } else { + json.value = string(abi.encodePacked(json.value, '"', propertyName, '": "', addressToString(value), '"')); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the address value (as a JSON string) as an element of a JSON array. + */ + function writeAddressValue(Json memory json, address value) internal pure returns (Json memory) { + if (json.depthBitTracker < 0) { + json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', addressToString(value), '"')); + } else { + json.value = string(abi.encodePacked(json.value, '"', addressToString(value), '"')); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the property name and boolean value (as a JSON literal "true" or "false") as part of a name/value pair of a JSON object. + */ + function writeBooleanProperty(Json memory json, string memory propertyName, bool value) + internal + pure + returns (Json memory) + { + string memory strValue; + if (value) { + strValue = TRUE; + } else { + strValue = FALSE; + } + + if (json.depthBitTracker < 0) { + json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": ', strValue)); + } else { + json.value = string(abi.encodePacked(json.value, '"', propertyName, '": ', strValue)); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the boolean value (as a JSON literal "true" or "false") as an element of a JSON array. + */ + function writeBooleanValue(Json memory json, bool value) internal pure returns (Json memory) { + string memory strValue; + if (value) { + strValue = TRUE; + } else { + strValue = FALSE; + } + + if (json.depthBitTracker < 0) { + json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, strValue)); + } else { + json.value = string(abi.encodePacked(json.value, strValue)); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the property name and int value (as a JSON number) as part of a name/value pair of a JSON object. + */ + function writeIntProperty(Json memory json, string memory propertyName, int256 value) + internal + pure + returns (Json memory) + { + if (json.depthBitTracker < 0) { + json.value = + string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": ', intToString(value))); + } else { + json.value = string(abi.encodePacked(json.value, '"', propertyName, '": ', intToString(value))); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the int value (as a JSON number) as an element of a JSON array. + */ + function writeIntValue(Json memory json, int256 value) internal pure returns (Json memory) { + if (json.depthBitTracker < 0) { + json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, intToString(value))); + } else { + json.value = string(abi.encodePacked(json.value, intToString(value))); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the property name and value of null as part of a name/value pair of a JSON object. + */ + function writeNullProperty(Json memory json, string memory propertyName) internal pure returns (Json memory) { + if (json.depthBitTracker < 0) { + json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": null')); + } else { + json.value = string(abi.encodePacked(json.value, '"', propertyName, '": null')); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the value of null as an element of a JSON array. + */ + function writeNullValue(Json memory json) internal pure returns (Json memory) { + if (json.depthBitTracker < 0) { + json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, "null")); + } else { + json.value = string(abi.encodePacked(json.value, "null")); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the string text value (as a JSON string) as an element of a JSON array. + */ + function writeStringProperty(Json memory json, string memory propertyName, string memory value) + internal + pure + returns (Json memory) + { + string memory jsonEscapedString = escapeJsonString(value); + if (json.depthBitTracker < 0) { + json.value = + string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": "', jsonEscapedString, '"')); + } else { + json.value = string(abi.encodePacked(json.value, '"', propertyName, '": "', jsonEscapedString, '"')); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the property name and string text value (as a JSON string) as part of a name/value pair of a JSON object. + */ + function writeStringValue(Json memory json, string memory value) internal pure returns (Json memory) { + string memory jsonEscapedString = escapeJsonString(value); + if (json.depthBitTracker < 0) { + json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', jsonEscapedString, '"')); + } else { + json.value = string(abi.encodePacked(json.value, '"', jsonEscapedString, '"')); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the property name and uint value (as a JSON number) as part of a name/value pair of a JSON object. + */ + function writeUintProperty(Json memory json, string memory propertyName, uint256 value) + internal + pure + returns (Json memory) + { + if (json.depthBitTracker < 0) { + json.value = + string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": ', uintToString(value))); + } else { + json.value = string(abi.encodePacked(json.value, '"', propertyName, '": ', uintToString(value))); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the uint value (as a JSON number) as an element of a JSON array. + */ + function writeUintValue(Json memory json, uint256 value) internal pure returns (Json memory) { + if (json.depthBitTracker < 0) { + json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, uintToString(value))); + } else { + json.value = string(abi.encodePacked(json.value, uintToString(value))); + } + + json.depthBitTracker = setListSeparatorFlag(json); + + return json; + } + + /** + * @dev Writes the beginning of a JSON array or object based on the token parameter. + */ + function writeStart(Json memory json, bytes1 token) private pure returns (Json memory) { + if (json.depthBitTracker < 0) { + json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, token)); + } else { + json.value = string(abi.encodePacked(json.value, token)); + } + + json.depthBitTracker &= MAX_INT256; + json.depthBitTracker++; + + return json; + } + + /** + * @dev Writes the beginning of a JSON array or object based on the token parameter with a property name as the key. + */ + function writeStart(Json memory json, string memory propertyName, bytes1 token) + private + pure + returns (Json memory) + { + if (json.depthBitTracker < 0) { + json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": ', token)); + } else { + json.value = string(abi.encodePacked(json.value, '"', propertyName, '": ', token)); + } + + json.depthBitTracker &= MAX_INT256; + json.depthBitTracker++; + + return json; + } + + /** + * @dev Writes the end of a JSON array or object based on the token parameter. + */ + function writeEnd(Json memory json, bytes1 token) private pure returns (Json memory) { + json.value = string(abi.encodePacked(json.value, token)); + json.depthBitTracker = setListSeparatorFlag(json); + + if (getCurrentDepth(json) != 0) { + json.depthBitTracker--; + } + + return json; + } + + /** + * @dev Escapes any characters that required by JSON to be escaped. + */ + function escapeJsonString(string memory value) private pure returns (string memory str) { + bytes memory b = bytes(value); + bool foundEscapeChars; + + for (uint256 i; i < b.length; i++) { + if (b[i] == BACKSLASH) { + foundEscapeChars = true; + break; + } else if (b[i] == DOUBLE_QUOTE) { + foundEscapeChars = true; + break; + } else if (b[i] == FRONTSLASH) { + foundEscapeChars = true; + break; + } else if (b[i] == HORIZONTAL_TAB) { + foundEscapeChars = true; + break; + } else if (b[i] == FORM_FEED) { + foundEscapeChars = true; + break; + } else if (b[i] == NEWLINE) { + foundEscapeChars = true; + break; + } else if (b[i] == CARRIAGE_RETURN) { + foundEscapeChars = true; + break; + } else if (b[i] == BACKSPACE) { + foundEscapeChars = true; + break; + } + } + + if (!foundEscapeChars) { + return value; + } + + for (uint256 i; i < b.length; i++) { + if (b[i] == BACKSLASH) { + str = string(abi.encodePacked(str, "\\\\")); + } else if (b[i] == DOUBLE_QUOTE) { + str = string(abi.encodePacked(str, '\\"')); + } else if (b[i] == FRONTSLASH) { + str = string(abi.encodePacked(str, "\\/")); + } else if (b[i] == HORIZONTAL_TAB) { + str = string(abi.encodePacked(str, "\\t")); + } else if (b[i] == FORM_FEED) { + str = string(abi.encodePacked(str, "\\f")); + } else if (b[i] == NEWLINE) { + str = string(abi.encodePacked(str, "\\n")); + } else if (b[i] == CARRIAGE_RETURN) { + str = string(abi.encodePacked(str, "\\r")); + } else if (b[i] == BACKSPACE) { + str = string(abi.encodePacked(str, "\\b")); + } else { + str = string(abi.encodePacked(str, b[i])); + } + } + + return str; + } + + /** + * @dev Tracks the recursive depth of the nested objects / arrays within the JSON text + * written so far. This provides the depth of the current token. + */ + function getCurrentDepth(Json memory json) private pure returns (int256) { + return json.depthBitTracker & MAX_INT256; + } + + /** + * @dev The highest order bit of json.depthBitTracker is used to discern whether we are writing the first item in a list or not. + * if (json.depthBitTracker >> 255) == 1, add a list separator before writing the item + * else, no list separator is needed since we are writing the first item. + */ + function setListSeparatorFlag(Json memory json) private pure returns (int256) { + return json.depthBitTracker | (int256(1) << 255); + } + + /** + * @dev Converts an address to a string. + */ + function addressToString(address _address) internal pure returns (string memory) { + bytes32 value = bytes32(uint256(uint160(_address))); + bytes16 alphabet = "0123456789abcdef"; + + bytes memory str = new bytes(42); + str[0] = "0"; + str[1] = "x"; + for (uint256 i; i < 20; i++) { + str[2 + i * 2] = alphabet[uint8(value[i + 12] >> 4)]; + str[3 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)]; + } + + return string(str); + } + + /** + * @dev Converts an int to a string. + */ + function intToString(int256 i) internal pure returns (string memory) { + if (i == 0) { + return "0"; + } + + if (i == type(int256).min) { + // hard-coded since int256 min value can't be converted to unsigned + return "-57896044618658097711785492504343953926634992332820282019728792003956564819968"; + } + + bool negative = i < 0; + uint256 len; + uint256 j; + if (!negative) { + j = uint256(i); + } else { + j = uint256(-i); + ++len; // make room for '-' sign + } + + uint256 l = j; + while (j != 0) { + len++; + j /= 10; + } + + bytes memory bstr = new bytes(len); + uint256 k = len; + while (l != 0) { + bstr[--k] = bytes1((48 + uint8(l - (l / 10) * 10))); + l /= 10; + } + + if (negative) { + bstr[0] = "-"; // prepend '-' + } + + return string(bstr); + } + + /** + * @dev Converts a uint to a string. + */ + function uintToString(uint256 _i) internal pure returns (string memory) { + if (_i == 0) { + return "0"; + } + + uint256 j = _i; + uint256 len; + while (j != 0) { + len++; + j /= 10; + } + + bytes memory bstr = new bytes(len); + uint256 k = len; + while (_i != 0) { + bstr[--k] = bytes1((48 + uint8(_i - (_i / 10) * 10))); + _i /= 10; + } + + return string(bstr); + } +} diff --git a/test/protocols/Suavex.t.sol b/test/protocols/Suavex.t.sol new file mode 100644 index 0000000..d0e0ad5 --- /dev/null +++ b/test/protocols/Suavex.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "src/protocols/Suavex.sol"; +import "src/suavelib/Suave.sol"; +import "solady/src/utils/JSONParserLib.sol"; + +contract SuavexTest is Test { + using JSONParserLib for string; + + function testBuildEthBlockEncode() public { + Bundle.BundleObj memory bundle; + bundle.blockNumber = 1; + bundle.minTimestamp = 2; + bundle.maxTimestamp = 3; + bundle.txns = new bytes[](1); + bundle.txns[0] = hex"1234"; + + Suave.BuildBlockArgs memory args; + + args.slot = 1; + args.proposerPubkey = hex"1234"; + args.parent = hex"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + args.timestamp = 1; + args.feeRecipient = address(0); + args.gasLimit = 1; + args.random = hex"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + args.withdrawals = new Suave.Withdrawal[](1); + + Suave.Withdrawal memory withdrawal; + withdrawal.index = 1; + withdrawal.validator = 1; + withdrawal.Address = address(0); + withdrawal.amount = 1238912748128; + + args.withdrawals[0] = withdrawal; + + args.extra = hex"1234"; + args.beaconRoot = hex"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"; + args.fillPending = false; + + Bundle.BundleObj[] memory bundles = new Bundle.BundleObj[](1); + bundles[0] = bundle; + string memory result = Suavex.encodeBuildEthBlock(args, bundles); + assertEq(result, '{"args": {"slot": 1,"proposerPubkey": "EjQ=","parent": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","timestamp": 1,"feeRecipient": "0x0000000000000000000000000000000000000000","gasLimit": 1,"random": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","withdrawals": [{"index": "0x01","validator": "0x01","address": "0x0000000000000000000000000000000000000000","amount": "0x012074f44a60"}],"extra": "EjQ=","beaconRoot": "zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMw=","fillPending": false},"bundles": [{"blockNumber": "0x01","minTimestamp": "0x02","maxTimestamp": "0x03","txs": ["0x1234"]}]}'); + } +} From 2ba7c7b9b9b07353b3484955b8fcf5e79c121aa2 Mon Sep 17 00:00:00 2001 From: Jinsuk Park Date: Fri, 23 Feb 2024 16:15:15 -0800 Subject: [PATCH 02/15] Add suavex_call --- src/protocols/Suavex.sol | 30 ++++++++++++++++++++++++++---- test/protocols/Suavex.t.sol | 11 +++++++++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/protocols/Suavex.sol b/src/protocols/Suavex.sol index daa2862..7286ca3 100644 --- a/src/protocols/Suavex.sol +++ b/src/protocols/Suavex.sol @@ -10,11 +10,11 @@ library Suavex { using JsonWriter for JsonWriter.Json; using LibString for *; - function buildEthBlock(string memory url, Suave.BuildBlockArgs memory args, Bundle.BundleObj[] memory bundles) - public + function buildEthBlockFromBundles(string memory url, Suave.BuildBlockArgs memory args, Bundle.BundleObj[] memory bundles) + internal returns (bytes memory) { - string memory params = encodeBuildEthBlock(args, bundles); + string memory params = encodeBuildEthBlockFromBundles(args, bundles); bytes memory body = encodeRpcRequest("suavex_buildEthBlockFromBundles", params, 1); Suave.HttpRequest memory request; request.url = url; @@ -26,7 +26,7 @@ library Suavex { return Suave.doHTTPRequest(request); } - function encodeBuildEthBlock(Suave.BuildBlockArgs memory args, Bundle.BundleObj[] memory bundles) + function encodeBuildEthBlockFromBundles(Suave.BuildBlockArgs memory args, Bundle.BundleObj[] memory bundles) internal pure returns (string memory) @@ -85,6 +85,28 @@ library Suavex { return writer.value; } + function call(string memory url, address contractAddr, bytes memory input) internal returns (bytes memory) { + string memory params = encodeCall(contractAddr, input); + bytes memory body = encodeRpcRequest("suavex_call", params, 1); + Suave.HttpRequest memory request; + request.url = url; + request.method = "POST"; + request.body = body; + request.headers = new string[](1); + request.headers[0] = "Content-Type: application/json"; + request.withFlashbotsSignature = true; + return Suave.doHTTPRequest(request); + } + + function encodeCall(address contractAddr, bytes memory input) internal pure returns (string memory) { + JsonWriter.Json memory writer; + writer = writer.writeStartObject(); + writer = writer.writeStringProperty("contractAddr", contractAddr.toHexString()); + writer = writer.writeBytesProperty("input", input); + writer = writer.writeEndObject(); + return writer.value; + } + function encodeRpcRequest(string memory method, string memory params, uint256 id) internal pure returns (bytes memory) { return abi.encodePacked('{"jsonrpc":"2.0","method":"', method, '","params":[', params, '],"id":', LibString.toString(id), '}'); } diff --git a/test/protocols/Suavex.t.sol b/test/protocols/Suavex.t.sol index d0e0ad5..d7305d4 100644 --- a/test/protocols/Suavex.t.sol +++ b/test/protocols/Suavex.t.sol @@ -10,7 +10,7 @@ import "solady/src/utils/JSONParserLib.sol"; contract SuavexTest is Test { using JSONParserLib for string; - function testBuildEthBlockEncode() public { + function testEncodeBuildEthBlockFromBundles() public { Bundle.BundleObj memory bundle; bundle.blockNumber = 1; bundle.minTimestamp = 2; @@ -43,7 +43,14 @@ contract SuavexTest is Test { Bundle.BundleObj[] memory bundles = new Bundle.BundleObj[](1); bundles[0] = bundle; - string memory result = Suavex.encodeBuildEthBlock(args, bundles); + string memory result = Suavex.encodeBuildEthBlockFromBundles(args, bundles); assertEq(result, '{"args": {"slot": 1,"proposerPubkey": "EjQ=","parent": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","timestamp": 1,"feeRecipient": "0x0000000000000000000000000000000000000000","gasLimit": 1,"random": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","withdrawals": [{"index": "0x01","validator": "0x01","address": "0x0000000000000000000000000000000000000000","amount": "0x012074f44a60"}],"extra": "EjQ=","beaconRoot": "zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMw=","fillPending": false},"bundles": [{"blockNumber": "0x01","minTimestamp": "0x02","maxTimestamp": "0x03","txs": ["0x1234"]}]}'); } + + function testEncodeCall() public { + address contractAddr = address(0); + bytes memory input = abi.encodeWithSignature("someMethod(uint256,string,address)", 1, "hello", address(1)); + string memory result = Suavex.encodeCall(contractAddr, input); + assertEq(result, '{"contractAddr": "0x0000000000000000000000000000000000000000","input": "NS8NrQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFaGVsbG8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC="}'); + } } From e597e69202ac64421bb35b2f84c371d5dba99258 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Thu, 29 Feb 2024 00:39:55 -0800 Subject: [PATCH 03/15] new fn: Bundle.simParams; encodes bundleObj for simBundle --- src/protocols/Bundle.sol | 32 ++++++++++++++++++++++++++++++++ test/protocols/Bundle.t.sol | 25 +++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index 68051f1..eb0dcfc 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -11,6 +11,8 @@ library Bundle { uint64 minTimestamp; uint64 maxTimestamp; bytes[] txns; + bytes32[] revertingHashes; + uint256 refundPercent; } function sendBundle(string memory url, BundleObj memory bundle) internal returns (bytes memory) { @@ -19,6 +21,36 @@ library Bundle { return Suave.doHTTPRequest(request); } + function simParams(BundleObj memory args) internal pure returns (bytes memory params) { + params = abi.encodePacked( + '{"blockNumber": "', + LibString.toHexString(args.blockNumber), + '", "refundPercent": ', + LibString.toString(args.refundPercent) + ); + if (args.revertingHashes.length > 0) { + params = abi.encodePacked(params, ', "revertingHashes": ['); + for (uint256 i = 0; i < args.revertingHashes.length; i++) { + params = abi.encodePacked(params, '"', LibString.toHexString(uint256(args.revertingHashes[i])), '"'); + if (i < args.revertingHashes.length - 1) { + params = abi.encodePacked(params, ","); + } else { + params = abi.encodePacked(params, "]"); + } + } + } + params = abi.encodePacked(params, ', "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 { + // end object with txs + params = abi.encodePacked(params, "]}"); + } + } + } + function encodeBundle(BundleObj memory args) internal pure returns (Suave.HttpRequest memory) { require(args.txns.length > 0, "Bundle: no txns"); diff --git a/test/protocols/Bundle.t.sol b/test/protocols/Bundle.t.sol index c684ff6..040d10b 100644 --- a/test/protocols/Bundle.t.sol +++ b/test/protocols/Bundle.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; -import "src/protocols/Bundle.sol"; -import "src/suavelib/Suave.sol"; +import "../../src/protocols/Bundle.sol"; +import "../../src/suavelib/Suave.sol"; contract EthSendBundle is Test { function testEthSendBundleEncode() public { @@ -37,4 +37,25 @@ contract EthSendBundle is Test { '{"jsonrpc":"2.0","method":"eth_sendBundle","params":[{"blockNumber": "0x01", "txs": ["0x1234"], "minTimestamp": 2, "maxTimestamp": 3}],"id":1}' ); } + + function testSimBundleParams() public { + Bundle.BundleObj memory bundle; + bundle.blockNumber = 1; + bundle.txns = new bytes[](1); + bundle.txns[0] = hex"1234"; + bundle.refundPercent = 50; + + bytes memory params = Bundle.simParams(bundle); + assertEq(string(params), '{"blockNumber": "0x01", "refundPercent": 50, "txs": ["0x1234"]}'); + + // encode with 'revertingHashes' + bundle.revertingHashes = new bytes32[](1); + bundle.revertingHashes[0] = keccak256("hashem"); + + bytes memory params2 = Bundle.simParams(bundle); + assertEq( + string(params2), + '{"blockNumber": "0x01", "refundPercent": 50, "revertingHashes": ["0x0a80df9c7574c9524999e774c05a27acf214618b45f4948b88ad1083e13a871a"], "txs": ["0x1234"]}' + ); + } } From 13b76278489ee8fba373ed8a9986fe969b2478a4 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Thu, 29 Feb 2024 00:48:26 -0800 Subject: [PATCH 04/15] add simulateBundle helper to Bundle lib (sugar) --- src/protocols/Bundle.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index eb0dcfc..2f3da0a 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -21,7 +21,12 @@ library Bundle { return Suave.doHTTPRequest(request); } - function simParams(BundleObj memory args) internal pure returns (bytes memory params) { + function simulateBundle(BundleObj memory bundle) internal returns (uint64 egp) { + bytes memory simParams = encodeSimParams(bundle); + egp = Suave.simulateBundle(simParams); + } + + function encodeSimParams(BundleObj memory args) internal pure returns (bytes memory params) { params = abi.encodePacked( '{"blockNumber": "', LibString.toHexString(args.blockNumber), From f3e00f2702f14b214c48ae93446339001460514a Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:55:01 -0700 Subject: [PATCH 05/15] use Json writer in Bundle.encodeSendBundle --- src/protocols/Bundle.sol | 78 +++++++++++++++++++++++++++---------- test/protocols/Bundle.t.sol | 16 ++++---- 2 files changed, 66 insertions(+), 28 deletions(-) diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index 6575d38..c161e1d 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import "../suavelib/Suave.sol"; import "../utils/HexStrings.sol"; +import "../utils/JsonWriter.sol"; import "solady/src/utils/LibString.sol"; import "solady/src/utils/JSONParserLib.sol"; @@ -10,28 +11,37 @@ import "solady/src/utils/JSONParserLib.sol"; library Bundle { struct BundleObj { uint64 blockNumber; + bytes[] txns; + // sendBundle exclusive: uint64 minTimestamp; uint64 maxTimestamp; - bytes[] txns; bytes32[] revertingHashes; + string replacementUuid; + // SBundle (sim) exclusive uint256 refundPercent; } using JSONParserLib for string; using JSONParserLib for JSONParserLib.Item; + using JsonWriter for JsonWriter.Json; + using LibString for *; function sendBundle(string memory url, BundleObj memory bundle) internal returns (bytes memory) { - Suave.HttpRequest memory request = encodeBundle(bundle); + Suave.HttpRequest memory request = encodeSendBundle(bundle); request.url = url; return Suave.doHTTPRequest(request); } function simulateBundle(BundleObj memory bundle) internal returns (uint64 egp) { - bytes memory simParams = encodeSimParams(bundle); + bytes memory simParams = encodeSimBundle(bundle); egp = Suave.simulateBundle(simParams); } - function encodeSimParams(BundleObj memory args) internal pure returns (bytes memory params) { + /** + * Encodes an [RpcSBundle](https://github.com/flashbots/suave-geth/blob/main/core/types/sbundle.go#L21-L27) + * for the precompile `Suave.simulateBundle`. + */ + function encodeSimBundle(BundleObj memory args) internal pure returns (bytes memory params) { params = abi.encodePacked( '{"blockNumber": "', LibString.toHexString(args.blockNumber), @@ -61,33 +71,61 @@ library Bundle { } } - function encodeBundle(BundleObj memory args) internal pure returns (Suave.HttpRequest memory) { + function encodeSendBundle(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": ['); + JsonWriter.Json memory writer; + // {... + writer = writer.writeStartObject(); + // {jsonrpc: "2.0" + writer = writer.writeStringProperty("jsonrpc", "2.0"); + // {method: "eth_sendBundle" + writer = writer.writeStringProperty("method", "eth_sendBundle"); + // {id: 1 + writer = writer.writeUintProperty("id", 1); + + // {params: [... + writer = writer.writeStartArray("params"); + // params: [{... + writer = writer.writeStartObject(); + // {blockNumber: "0x..."} + writer = writer.writeStringProperty("blockNumber", args.blockNumber.toHexString()); + // {txs: [...txns] + writer = writer.writeStartArray("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, "]"); - } + writer = writer.writeStringValue(args.txns[i].toHexString()); } + writer = writer.writeEndArray(); + // {minTimestamp? if (args.minTimestamp > 0) { - params = abi.encodePacked(params, ', "minTimestamp": ', LibString.toString(args.minTimestamp)); + writer = writer.writeUintProperty("minTimestamp", args.minTimestamp); } + // {maxTimestamp? if (args.maxTimestamp > 0) { - params = abi.encodePacked(params, ', "maxTimestamp": ', LibString.toString(args.maxTimestamp)); + writer = writer.writeUintProperty("maxTimestamp", args.maxTimestamp); } - params = abi.encodePacked(params, "}"); - - bytes memory body = - abi.encodePacked('{"jsonrpc":"2.0","method":"eth_sendBundle","params":[', params, '],"id":1}'); + // {revertingHashes? + if (args.revertingHashes.length > 0) { + writer = writer.writeStartArray("revertingHashes"); + for (uint256 i = 0; i < args.revertingHashes.length; i++) { + writer = writer.writeBytesValue(abi.encodePacked(args.revertingHashes[i])); + } + writer = writer.writeEndArray(); + } + // {replacementUuid? + if (abi.encodePacked(args.replacementUuid).length > 0) { + writer = writer.writeStringProperty("replacementUuid", args.replacementUuid); + } + // end params object + writer = writer.writeEndObject(); + // end params array + writer = writer.writeEndArray(); + // end body object + writer = writer.writeEndObject(); Suave.HttpRequest memory request; request.method = "POST"; - request.body = body; + request.body = abi.encodePacked(writer.value); request.headers = new string[](1); request.headers[0] = "Content-Type: application/json"; request.withFlashbotsSignature = true; diff --git a/test/protocols/Bundle.t.sol b/test/protocols/Bundle.t.sol index 8686aff..e0c1171 100644 --- a/test/protocols/Bundle.t.sol +++ b/test/protocols/Bundle.t.sol @@ -15,29 +15,29 @@ contract EthSendBundle is Test { bundle.txns = new bytes[](1); bundle.txns[0] = hex"1234"; - Suave.HttpRequest memory request = Bundle.encodeBundle(bundle); + Suave.HttpRequest memory request = Bundle.encodeSendBundle(bundle); assertEq( string(request.body), - '{"jsonrpc":"2.0","method":"eth_sendBundle","params":[{"blockNumber": "0x01", "txs": ["0x1234"]}],"id":1}' + '{"jsonrpc": "2.0","method": "eth_sendBundle","id": 1,"params": [{"blockNumber": "0x01","txs": ["0x1234"]}]}' ); assertTrue(request.withFlashbotsSignature); // encode with 'minTimestamp' bundle.minTimestamp = 2; - Suave.HttpRequest memory request2 = Bundle.encodeBundle(bundle); + Suave.HttpRequest memory request2 = Bundle.encodeSendBundle(bundle); assertEq( string(request2.body), - '{"jsonrpc":"2.0","method":"eth_sendBundle","params":[{"blockNumber": "0x01", "txs": ["0x1234"], "minTimestamp": 2}],"id":1}' + '{"jsonrpc": "2.0","method": "eth_sendBundle","id": 1,"params": [{"blockNumber": "0x01","txs": ["0x1234"],"minTimestamp": 2}]}' ); // encode with 'maxTimestamp' bundle.maxTimestamp = 3; - Suave.HttpRequest memory request3 = Bundle.encodeBundle(bundle); + Suave.HttpRequest memory request3 = Bundle.encodeSendBundle(bundle); assertEq( string(request3.body), - '{"jsonrpc":"2.0","method":"eth_sendBundle","params":[{"blockNumber": "0x01", "txs": ["0x1234"], "minTimestamp": 2, "maxTimestamp": 3}],"id":1}' + '{"jsonrpc": "2.0","method": "eth_sendBundle","id": 1,"params": [{"blockNumber": "0x01","txs": ["0x1234"],"minTimestamp": 2,"maxTimestamp": 3}]}' ); } @@ -48,14 +48,14 @@ contract EthSendBundle is Test { bundle.txns[0] = hex"1234"; bundle.refundPercent = 50; - bytes memory params = Bundle.encodeSimParams(bundle); + bytes memory params = Bundle.encodeSimBundle(bundle); assertEq(string(params), '{"blockNumber": "0x01", "refundPercent": 50, "txs": ["0x1234"]}'); // encode with 'revertingHashes' bundle.revertingHashes = new bytes32[](1); bundle.revertingHashes[0] = keccak256("hashem"); - bytes memory params2 = Bundle.encodeSimParams(bundle); + bytes memory params2 = Bundle.encodeSimBundle(bundle); assertEq( string(params2), '{"blockNumber": "0x01", "refundPercent": 50, "revertingHashes": ["0x0a80df9c7574c9524999e774c05a27acf214618b45f4948b88ad1083e13a871a"], "txs": ["0x1234"]}' From 371a20e288910ebd6898fda61f4ca420687b1786 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Tue, 12 Mar 2024 20:02:45 -0700 Subject: [PATCH 06/15] use Json writer in encodeSimBundle --- src/protocols/Bundle.sol | 55 +++++++++++++------------------------ test/protocols/Bundle.t.sol | 6 ++-- 2 files changed, 22 insertions(+), 39 deletions(-) diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index c161e1d..4f7534c 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -42,69 +42,56 @@ library Bundle { * for the precompile `Suave.simulateBundle`. */ function encodeSimBundle(BundleObj memory args) internal pure returns (bytes memory params) { - params = abi.encodePacked( - '{"blockNumber": "', - LibString.toHexString(args.blockNumber), - '", "refundPercent": ', - LibString.toString(args.refundPercent) - ); + require(args.txns.length > 0, "Bundle: no txns"); + JsonWriter.Json memory writer; + + writer = writer.writeStartObject(); + writer = writer.writeStringProperty("blockNumber", args.blockNumber.toHexString()); + if (args.refundPercent > 0) { + writer = writer.writeUintProperty("refundPercent", args.refundPercent); + } if (args.revertingHashes.length > 0) { - params = abi.encodePacked(params, ', "revertingHashes": ['); + writer = writer.writeStartArray("revertingHashes"); for (uint256 i = 0; i < args.revertingHashes.length; i++) { - params = abi.encodePacked(params, '"', LibString.toHexString(uint256(args.revertingHashes[i])), '"'); - if (i < args.revertingHashes.length - 1) { - params = abi.encodePacked(params, ","); - } else { - params = abi.encodePacked(params, "]"); - } + writer = writer.writeStringValue(uint256(args.revertingHashes[i]).toHexString()); } + writer = writer.writeEndArray(); } - params = abi.encodePacked(params, ', "txs": ['); + writer = writer.writeStartArray("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 { - // end object with txs - params = abi.encodePacked(params, "]}"); - } + writer = writer.writeStringValue(args.txns[i].toHexString()); } + writer = writer.writeEndArray(); + writer = writer.writeEndObject(); + + params = abi.encodePacked(writer.value); } function encodeSendBundle(BundleObj memory args) internal pure returns (Suave.HttpRequest memory) { require(args.txns.length > 0, "Bundle: no txns"); JsonWriter.Json memory writer; - // {... + // body writer = writer.writeStartObject(); - // {jsonrpc: "2.0" writer = writer.writeStringProperty("jsonrpc", "2.0"); - // {method: "eth_sendBundle" writer = writer.writeStringProperty("method", "eth_sendBundle"); - // {id: 1 writer = writer.writeUintProperty("id", 1); - // {params: [... + // params writer = writer.writeStartArray("params"); - // params: [{... writer = writer.writeStartObject(); - // {blockNumber: "0x..."} writer = writer.writeStringProperty("blockNumber", args.blockNumber.toHexString()); - // {txs: [...txns] writer = writer.writeStartArray("txs"); for (uint256 i = 0; i < args.txns.length; i++) { writer = writer.writeStringValue(args.txns[i].toHexString()); } writer = writer.writeEndArray(); - // {minTimestamp? if (args.minTimestamp > 0) { writer = writer.writeUintProperty("minTimestamp", args.minTimestamp); } - // {maxTimestamp? if (args.maxTimestamp > 0) { writer = writer.writeUintProperty("maxTimestamp", args.maxTimestamp); } - // {revertingHashes? if (args.revertingHashes.length > 0) { writer = writer.writeStartArray("revertingHashes"); for (uint256 i = 0; i < args.revertingHashes.length; i++) { @@ -112,15 +99,11 @@ library Bundle { } writer = writer.writeEndArray(); } - // {replacementUuid? if (abi.encodePacked(args.replacementUuid).length > 0) { writer = writer.writeStringProperty("replacementUuid", args.replacementUuid); } - // end params object writer = writer.writeEndObject(); - // end params array writer = writer.writeEndArray(); - // end body object writer = writer.writeEndObject(); Suave.HttpRequest memory request; diff --git a/test/protocols/Bundle.t.sol b/test/protocols/Bundle.t.sol index e0c1171..7dff208 100644 --- a/test/protocols/Bundle.t.sol +++ b/test/protocols/Bundle.t.sol @@ -41,7 +41,7 @@ contract EthSendBundle is Test { ); } - function testSimBundleParams() public { + function testEncodeSimBundle() public { Bundle.BundleObj memory bundle; bundle.blockNumber = 1; bundle.txns = new bytes[](1); @@ -49,7 +49,7 @@ contract EthSendBundle is Test { bundle.refundPercent = 50; bytes memory params = Bundle.encodeSimBundle(bundle); - assertEq(string(params), '{"blockNumber": "0x01", "refundPercent": 50, "txs": ["0x1234"]}'); + assertEq(string(params), '{"blockNumber": "0x01","refundPercent": 50,"txs": ["0x1234"]}'); // encode with 'revertingHashes' bundle.revertingHashes = new bytes32[](1); @@ -58,7 +58,7 @@ contract EthSendBundle is Test { bytes memory params2 = Bundle.encodeSimBundle(bundle); assertEq( string(params2), - '{"blockNumber": "0x01", "refundPercent": 50, "revertingHashes": ["0x0a80df9c7574c9524999e774c05a27acf214618b45f4948b88ad1083e13a871a"], "txs": ["0x1234"]}' + '{"blockNumber": "0x01","refundPercent": 50,"revertingHashes": ["0x0a80df9c7574c9524999e774c05a27acf214618b45f4948b88ad1083e13a871a"],"txs": ["0x1234"]}' ); } From 151b2ccdd28e618470970326b5d671d3af15f82d Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Tue, 12 Mar 2024 20:04:29 -0700 Subject: [PATCH 07/15] clean up comments --- src/protocols/Bundle.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index 4f7534c..575de9a 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -39,7 +39,7 @@ library Bundle { /** * Encodes an [RpcSBundle](https://github.com/flashbots/suave-geth/blob/main/core/types/sbundle.go#L21-L27) - * for the precompile `Suave.simulateBundle`. + * for the `Suave.simulateBundle`. */ function encodeSimBundle(BundleObj memory args) internal pure returns (bytes memory params) { require(args.txns.length > 0, "Bundle: no txns"); @@ -67,6 +67,9 @@ library Bundle { params = abi.encodePacked(writer.value); } + /** + * Encodes a bundle into an RPC request to `eth_sendBundle`. + */ function encodeSendBundle(BundleObj memory args) internal pure returns (Suave.HttpRequest memory) { require(args.txns.length > 0, "Bundle: no txns"); From cbc1c3ca019443d6504085a04056c9b80a12bf32 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:07:27 -0700 Subject: [PATCH 08/15] fix doc comment grammer --- src/protocols/Bundle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index 575de9a..c912707 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -39,7 +39,7 @@ library Bundle { /** * Encodes an [RpcSBundle](https://github.com/flashbots/suave-geth/blob/main/core/types/sbundle.go#L21-L27) - * for the `Suave.simulateBundle`. + * for `Suave.simulateBundle`. */ function encodeSimBundle(BundleObj memory args) internal pure returns (bytes memory params) { require(args.txns.length > 0, "Bundle: no txns"); From 9f3d20ccddc74f0f4fc531a0c584f361c96bfd37 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:27:34 -0700 Subject: [PATCH 09/15] fix RefundPercent param --- src/protocols/Bundle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index c912707..0db1357 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -48,7 +48,7 @@ library Bundle { writer = writer.writeStartObject(); writer = writer.writeStringProperty("blockNumber", args.blockNumber.toHexString()); if (args.refundPercent > 0) { - writer = writer.writeUintProperty("refundPercent", args.refundPercent); + writer = writer.writeUintProperty("percent", args.refundPercent); } if (args.revertingHashes.length > 0) { writer = writer.writeStartArray("revertingHashes"); From ceb6c4dd276092e609e8a875ff03cdf5cf41bb52 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Wed, 13 Mar 2024 12:20:50 -0700 Subject: [PATCH 10/15] swap args in Bundle.sendBundle; for 'using for' syntax --- src/protocols/Bundle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index 0db1357..3d76a99 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -26,7 +26,7 @@ library Bundle { using JsonWriter for JsonWriter.Json; using LibString for *; - function sendBundle(string memory url, BundleObj memory bundle) internal returns (bytes memory) { + function sendBundle(BundleObj memory bundle, string memory url) internal returns (bytes memory) { Suave.HttpRequest memory request = encodeSendBundle(bundle); request.url = url; return Suave.doHTTPRequest(request); From c3187b5c14db1b67d609c5511e6fad8eb270947f Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Wed, 13 Mar 2024 12:40:32 -0700 Subject: [PATCH 11/15] fix failing test in Bundle.t.sol --- test/protocols/Bundle.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/protocols/Bundle.t.sol b/test/protocols/Bundle.t.sol index 7dff208..dde5032 100644 --- a/test/protocols/Bundle.t.sol +++ b/test/protocols/Bundle.t.sol @@ -49,7 +49,7 @@ contract EthSendBundle is Test { bundle.refundPercent = 50; bytes memory params = Bundle.encodeSimBundle(bundle); - assertEq(string(params), '{"blockNumber": "0x01","refundPercent": 50,"txs": ["0x1234"]}'); + assertEq(string(params), '{"blockNumber": "0x01","percent": 50,"txs": ["0x1234"]}'); // encode with 'revertingHashes' bundle.revertingHashes = new bytes32[](1); @@ -58,7 +58,7 @@ contract EthSendBundle is Test { bytes memory params2 = Bundle.encodeSimBundle(bundle); assertEq( string(params2), - '{"blockNumber": "0x01","refundPercent": 50,"revertingHashes": ["0x0a80df9c7574c9524999e774c05a27acf214618b45f4948b88ad1083e13a871a"],"txs": ["0x1234"]}' + '{"blockNumber": "0x01","percent": 50,"revertingHashes": ["0x0a80df9c7574c9524999e774c05a27acf214618b45f4948b88ad1083e13a871a"],"txs": ["0x1234"]}' ); } From 1edb7abb6dd551950f47351f8ee18badf241761b Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:04:46 -0700 Subject: [PATCH 12/15] remove JSON lib from bundle protocol --- src/protocols/Bundle.sol | 72 +++-- src/protocols/Suavex.sol | 113 -------- src/utils/JsonWriter.sol | 549 ------------------------------------ test/protocols/Bundle.t.sol | 10 +- test/protocols/Suavex.t.sol | 56 ---- 5 files changed, 39 insertions(+), 761 deletions(-) delete mode 100644 src/protocols/Suavex.sol delete mode 100644 src/utils/JsonWriter.sol delete mode 100644 test/protocols/Suavex.t.sol diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index 956668e..a2d645e 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.13; import "../suavelib/Suave.sol"; import "../utils/HexStrings.sol"; -import "../utils/JsonWriter.sol"; import "solady/src/utils/LibString.sol"; import "solady/src/utils/JSONParserLib.sol"; @@ -28,7 +27,6 @@ library Bundle { using JSONParserLib for string; using JSONParserLib for JSONParserLib.Item; - using JsonWriter for JsonWriter.Json; using LibString for *; /// @notice send a bundle to the Flashbots relay. @@ -52,28 +50,29 @@ library Bundle { */ function encodeSimBundle(BundleObj memory args) internal pure returns (bytes memory params) { require(args.txns.length > 0, "Bundle: no txns"); - JsonWriter.Json memory writer; - writer = writer.writeStartObject(); - writer = writer.writeStringProperty("blockNumber", args.blockNumber.toHexString()); + params = abi.encodePacked('{"blockNumber": "', args.blockNumber.toHexString(), '", '); if (args.refundPercent > 0) { - writer = writer.writeUintProperty("percent", args.refundPercent); + params = abi.encodePacked(params, '"percent": ', args.refundPercent.toString(), ", "); } if (args.revertingHashes.length > 0) { - writer = writer.writeStartArray("revertingHashes"); + params = abi.encodePacked(params, '"revertingHashes": ['); for (uint256 i = 0; i < args.revertingHashes.length; i++) { - writer = writer.writeStringValue(uint256(args.revertingHashes[i]).toHexString()); + params = abi.encodePacked(params, '"', uint256(args.revertingHashes[i]).toHexString(), '"'); + if (i < args.revertingHashes.length - 1) { + params = abi.encodePacked(params, ", "); + } } - writer = writer.writeEndArray(); + params = abi.encodePacked(params, "], "); } - writer = writer.writeStartArray("txs"); + params = abi.encodePacked(params, '"txs": ['); for (uint256 i = 0; i < args.txns.length; i++) { - writer = writer.writeStringValue(args.txns[i].toHexString()); + params = abi.encodePacked(params, '"', args.txns[i].toHexString(), '"'); + if (i < args.txns.length - 1) { + params = abi.encodePacked(params, ", "); + } } - writer = writer.writeEndArray(); - writer = writer.writeEndObject(); - - params = abi.encodePacked(writer.value); + params = abi.encodePacked(params, "]}"); } /** @@ -82,45 +81,42 @@ library Bundle { function encodeSendBundle(BundleObj memory args) internal pure returns (Suave.HttpRequest memory) { require(args.txns.length > 0, "Bundle: no txns"); - JsonWriter.Json memory writer; // body - writer = writer.writeStartObject(); - writer = writer.writeStringProperty("jsonrpc", "2.0"); - writer = writer.writeStringProperty("method", "eth_sendBundle"); - writer = writer.writeUintProperty("id", 1); + bytes memory body = abi.encodePacked('{"jsonrpc": "2.0", "method": "eth_sendBundle", "id": 1, "params": [{'); // params - writer = writer.writeStartArray("params"); - writer = writer.writeStartObject(); - writer = writer.writeStringProperty("blockNumber", args.blockNumber.toHexString()); - writer = writer.writeStartArray("txs"); - for (uint256 i = 0; i < args.txns.length; i++) { - writer = writer.writeStringValue(args.txns[i].toHexString()); - } - writer = writer.writeEndArray(); + body = abi.encodePacked(body, '"blockNumber": "', args.blockNumber.toHexString(), '", '); if (args.minTimestamp > 0) { - writer = writer.writeUintProperty("minTimestamp", args.minTimestamp); + body = abi.encodePacked(body, '"minTimestamp": ', args.minTimestamp.toString(), ", "); } if (args.maxTimestamp > 0) { - writer = writer.writeUintProperty("maxTimestamp", args.maxTimestamp); + body = abi.encodePacked(body, '"maxTimestamp": ', args.maxTimestamp.toString(), ", "); } if (args.revertingHashes.length > 0) { - writer = writer.writeStartArray("revertingHashes"); + body = abi.encodePacked(body, '"revertingHashes": ['); for (uint256 i = 0; i < args.revertingHashes.length; i++) { - writer = writer.writeBytesValue(abi.encodePacked(args.revertingHashes[i])); + body = abi.encodePacked(body, '"', abi.encodePacked(args.revertingHashes[i]), '"'); + if (i < args.revertingHashes.length - 1) { + body = abi.encodePacked(body, ", "); + } } - writer = writer.writeEndArray(); + body = abi.encodePacked(body, "], "); } if (abi.encodePacked(args.replacementUuid).length > 0) { - writer = writer.writeStringProperty("replacementUuid", args.replacementUuid); + body = abi.encodePacked(body, '"replacementUuid": "', args.replacementUuid, '", '); + } + body = abi.encodePacked(body, '"txs": ['); + for (uint256 i = 0; i < args.txns.length; i++) { + body = abi.encodePacked(body, '"', args.txns[i].toHexString(), '"'); + if (i < args.txns.length - 1) { + body = abi.encodePacked(body, ", "); + } } - writer = writer.writeEndObject(); - writer = writer.writeEndArray(); - writer = writer.writeEndObject(); + body = abi.encodePacked(body, "]}]}"); Suave.HttpRequest memory request; request.method = "POST"; - request.body = abi.encodePacked(writer.value); + request.body = abi.encodePacked(body); request.headers = new string[](1); request.headers[0] = "Content-Type: application/json"; request.withFlashbotsSignature = true; diff --git a/src/protocols/Suavex.sol b/src/protocols/Suavex.sol deleted file mode 100644 index 7286ca3..0000000 --- a/src/protocols/Suavex.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "../suavelib/Suave.sol"; -import "../utils/JsonWriter.sol"; -import "./Bundle.sol"; -import "solady/src/utils/LibString.sol"; - -library Suavex { - using JsonWriter for JsonWriter.Json; - using LibString for *; - - function buildEthBlockFromBundles(string memory url, Suave.BuildBlockArgs memory args, Bundle.BundleObj[] memory bundles) - internal - returns (bytes memory) - { - string memory params = encodeBuildEthBlockFromBundles(args, bundles); - bytes memory body = encodeRpcRequest("suavex_buildEthBlockFromBundles", params, 1); - Suave.HttpRequest memory request; - request.url = url; - request.method = "POST"; - request.body = body; - request.headers = new string[](1); - request.headers[0] = "Content-Type: application/json"; - request.withFlashbotsSignature = true; - return Suave.doHTTPRequest(request); - } - - function encodeBuildEthBlockFromBundles(Suave.BuildBlockArgs memory args, Bundle.BundleObj[] memory bundles) - internal - pure - returns (string memory) - { - JsonWriter.Json memory writer; - - writer = writer.writeStartObject(); - - // args - writer = writer.writeStartObject("args"); - - writer = writer.writeUintProperty("slot", args.slot); - writer = writer.writeBytesProperty("proposerPubkey", args.proposerPubkey); - writer = writer.writeStringProperty("parent", abi.encodePacked(args.parent).toHexString()); - writer = writer.writeUintProperty("timestamp", args.timestamp); - writer = writer.writeAddressProperty("feeRecipient", args.feeRecipient); - writer = writer.writeUintProperty("gasLimit", args.gasLimit); - writer = writer.writeStringProperty("random", abi.encodePacked(args.random).toHexString()); - - // args - withdrawals - writer = writer.writeStartArray("withdrawals"); - for (uint256 i = 0; i < args.withdrawals.length; i++) { - writer = writer.writeStartObject(); - writer = writer.writeStringProperty("index", args.withdrawals[i].index.toHexString()); - writer = writer.writeStringProperty("validator", args.withdrawals[i].validator.toHexString()); - writer = writer.writeAddressProperty("address", args.withdrawals[i].Address); - writer = writer.writeStringProperty("amount", args.withdrawals[i].amount.toHexString()); - writer = writer.writeEndObject(); - } - writer = writer.writeEndArray(); - - writer = writer.writeBytesProperty("extra", args.extra); - writer = writer.writeBytesProperty("beaconRoot", abi.encodePacked(args.beaconRoot)); - writer = writer.writeBooleanProperty("fillPending", args.fillPending); - - writer = writer.writeEndObject(); - - // bundles - writer = writer.writeStartArray("bundles"); - - for (uint256 i = 0; i < bundles.length; i++) { - writer = writer.writeStartObject(); - writer = writer.writeStringProperty("blockNumber", bundles[i].blockNumber.toHexString()); - writer = writer.writeStringProperty("minTimestamp", bundles[i].minTimestamp.toHexString()); - writer = writer.writeStringProperty("maxTimestamp", bundles[i].maxTimestamp.toHexString()); - - writer = writer.writeStartArray("txs"); - for (uint256 j = 0; j < bundles[i].txns.length; j++) { - writer = writer.writeStringValue(bundles[i].txns[j].toHexString()); - } - writer = writer.writeEndArray(); - writer = writer.writeEndObject(); - } - writer = writer.writeEndArray(); - writer = writer.writeEndObject(); - return writer.value; - } - - function call(string memory url, address contractAddr, bytes memory input) internal returns (bytes memory) { - string memory params = encodeCall(contractAddr, input); - bytes memory body = encodeRpcRequest("suavex_call", params, 1); - Suave.HttpRequest memory request; - request.url = url; - request.method = "POST"; - request.body = body; - request.headers = new string[](1); - request.headers[0] = "Content-Type: application/json"; - request.withFlashbotsSignature = true; - return Suave.doHTTPRequest(request); - } - - function encodeCall(address contractAddr, bytes memory input) internal pure returns (string memory) { - JsonWriter.Json memory writer; - writer = writer.writeStartObject(); - writer = writer.writeStringProperty("contractAddr", contractAddr.toHexString()); - writer = writer.writeBytesProperty("input", input); - writer = writer.writeEndObject(); - return writer.value; - } - - function encodeRpcRequest(string memory method, string memory params, uint256 id) internal pure returns (bytes memory) { - return abi.encodePacked('{"jsonrpc":"2.0","method":"', method, '","params":[', params, '],"id":', LibString.toString(id), '}'); - } -} diff --git a/src/utils/JsonWriter.sol b/src/utils/JsonWriter.sol deleted file mode 100644 index dd344c3..0000000 --- a/src/utils/JsonWriter.sol +++ /dev/null @@ -1,549 +0,0 @@ -//SPDX-License-Identifier: MIT -// Copied from: https://github.com/bmeredith/solidity-json-writer -pragma solidity ^0.8.0; - -import "solady/src/utils/Base64.sol"; - -library JsonWriter { - using JsonWriter for string; - - struct Json { - int256 depthBitTracker; - string value; - } - - bytes1 constant BACKSLASH = bytes1(uint8(92)); - bytes1 constant BACKSPACE = bytes1(uint8(8)); - bytes1 constant CARRIAGE_RETURN = bytes1(uint8(13)); - bytes1 constant DOUBLE_QUOTE = bytes1(uint8(34)); - bytes1 constant FORM_FEED = bytes1(uint8(12)); - bytes1 constant FRONTSLASH = bytes1(uint8(47)); - bytes1 constant HORIZONTAL_TAB = bytes1(uint8(9)); - bytes1 constant NEWLINE = bytes1(uint8(10)); - - string constant TRUE = "true"; - string constant FALSE = "false"; - bytes1 constant OPEN_BRACE = "{"; - bytes1 constant CLOSED_BRACE = "}"; - bytes1 constant OPEN_BRACKET = "["; - bytes1 constant CLOSED_BRACKET = "]"; - bytes1 constant LIST_SEPARATOR = ","; - - int256 constant MAX_INT256 = type(int256).max; - - /** - * @dev Writes the beginning of a JSON array. - */ - function writeStartArray(Json memory json) internal pure returns (Json memory) { - return writeStart(json, OPEN_BRACKET); - } - - /** - * @dev Writes the beginning of a JSON array with a property name as the key. - */ - function writeStartArray(Json memory json, string memory propertyName) internal pure returns (Json memory) { - return writeStart(json, propertyName, OPEN_BRACKET); - } - - /** - * @dev Writes the beginning of a JSON object. - */ - function writeStartObject(Json memory json) internal pure returns (Json memory) { - return writeStart(json, OPEN_BRACE); - } - - /** - * @dev Writes the beginning of a JSON object with a property name as the key. - */ - function writeStartObject(Json memory json, string memory propertyName) internal pure returns (Json memory) { - return writeStart(json, propertyName, OPEN_BRACE); - } - - /** - * @dev Writes the end of a JSON array. - */ - function writeEndArray(Json memory json) internal pure returns (Json memory) { - return writeEnd(json, CLOSED_BRACKET); - } - - /** - * @dev Writes the end of a JSON object. - */ - function writeEndObject(Json memory json) internal pure returns (Json memory) { - return writeEnd(json, CLOSED_BRACE); - } - - /** - * @dev Writes the property name and bytes value (as a JSON string) as part of a name/value pair of a JSON object. - */ - function writeBytesProperty(Json memory json, string memory propertyName, bytes memory value) - internal - pure - returns (Json memory) - { - if (json.depthBitTracker < 0) { - json.value = string( - abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": "', Base64.encode(value), '"') - ); - } else { - json.value = string(abi.encodePacked(json.value, '"', propertyName, '": "', Base64.encode(value), '"')); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the bytes value (as a JSON string) as an element of a JSON array. - */ - function writeBytesValue(Json memory json, bytes memory value) internal pure returns (Json memory) { - if (json.depthBitTracker < 0) { - json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', Base64.encode(value), '"')); - } else { - json.value = string(abi.encodePacked(json.value, '"', Base64.encode(value), '"')); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the property name and address value (as a JSON string) as part of a name/value pair of a JSON object. - */ - function writeAddressProperty(Json memory json, string memory propertyName, address value) - internal - pure - returns (Json memory) - { - if (json.depthBitTracker < 0) { - json.value = string( - abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": "', addressToString(value), '"') - ); - } else { - json.value = string(abi.encodePacked(json.value, '"', propertyName, '": "', addressToString(value), '"')); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the address value (as a JSON string) as an element of a JSON array. - */ - function writeAddressValue(Json memory json, address value) internal pure returns (Json memory) { - if (json.depthBitTracker < 0) { - json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', addressToString(value), '"')); - } else { - json.value = string(abi.encodePacked(json.value, '"', addressToString(value), '"')); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the property name and boolean value (as a JSON literal "true" or "false") as part of a name/value pair of a JSON object. - */ - function writeBooleanProperty(Json memory json, string memory propertyName, bool value) - internal - pure - returns (Json memory) - { - string memory strValue; - if (value) { - strValue = TRUE; - } else { - strValue = FALSE; - } - - if (json.depthBitTracker < 0) { - json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": ', strValue)); - } else { - json.value = string(abi.encodePacked(json.value, '"', propertyName, '": ', strValue)); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the boolean value (as a JSON literal "true" or "false") as an element of a JSON array. - */ - function writeBooleanValue(Json memory json, bool value) internal pure returns (Json memory) { - string memory strValue; - if (value) { - strValue = TRUE; - } else { - strValue = FALSE; - } - - if (json.depthBitTracker < 0) { - json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, strValue)); - } else { - json.value = string(abi.encodePacked(json.value, strValue)); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the property name and int value (as a JSON number) as part of a name/value pair of a JSON object. - */ - function writeIntProperty(Json memory json, string memory propertyName, int256 value) - internal - pure - returns (Json memory) - { - if (json.depthBitTracker < 0) { - json.value = - string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": ', intToString(value))); - } else { - json.value = string(abi.encodePacked(json.value, '"', propertyName, '": ', intToString(value))); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the int value (as a JSON number) as an element of a JSON array. - */ - function writeIntValue(Json memory json, int256 value) internal pure returns (Json memory) { - if (json.depthBitTracker < 0) { - json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, intToString(value))); - } else { - json.value = string(abi.encodePacked(json.value, intToString(value))); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the property name and value of null as part of a name/value pair of a JSON object. - */ - function writeNullProperty(Json memory json, string memory propertyName) internal pure returns (Json memory) { - if (json.depthBitTracker < 0) { - json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": null')); - } else { - json.value = string(abi.encodePacked(json.value, '"', propertyName, '": null')); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the value of null as an element of a JSON array. - */ - function writeNullValue(Json memory json) internal pure returns (Json memory) { - if (json.depthBitTracker < 0) { - json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, "null")); - } else { - json.value = string(abi.encodePacked(json.value, "null")); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the string text value (as a JSON string) as an element of a JSON array. - */ - function writeStringProperty(Json memory json, string memory propertyName, string memory value) - internal - pure - returns (Json memory) - { - string memory jsonEscapedString = escapeJsonString(value); - if (json.depthBitTracker < 0) { - json.value = - string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": "', jsonEscapedString, '"')); - } else { - json.value = string(abi.encodePacked(json.value, '"', propertyName, '": "', jsonEscapedString, '"')); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the property name and string text value (as a JSON string) as part of a name/value pair of a JSON object. - */ - function writeStringValue(Json memory json, string memory value) internal pure returns (Json memory) { - string memory jsonEscapedString = escapeJsonString(value); - if (json.depthBitTracker < 0) { - json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', jsonEscapedString, '"')); - } else { - json.value = string(abi.encodePacked(json.value, '"', jsonEscapedString, '"')); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the property name and uint value (as a JSON number) as part of a name/value pair of a JSON object. - */ - function writeUintProperty(Json memory json, string memory propertyName, uint256 value) - internal - pure - returns (Json memory) - { - if (json.depthBitTracker < 0) { - json.value = - string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": ', uintToString(value))); - } else { - json.value = string(abi.encodePacked(json.value, '"', propertyName, '": ', uintToString(value))); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the uint value (as a JSON number) as an element of a JSON array. - */ - function writeUintValue(Json memory json, uint256 value) internal pure returns (Json memory) { - if (json.depthBitTracker < 0) { - json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, uintToString(value))); - } else { - json.value = string(abi.encodePacked(json.value, uintToString(value))); - } - - json.depthBitTracker = setListSeparatorFlag(json); - - return json; - } - - /** - * @dev Writes the beginning of a JSON array or object based on the token parameter. - */ - function writeStart(Json memory json, bytes1 token) private pure returns (Json memory) { - if (json.depthBitTracker < 0) { - json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, token)); - } else { - json.value = string(abi.encodePacked(json.value, token)); - } - - json.depthBitTracker &= MAX_INT256; - json.depthBitTracker++; - - return json; - } - - /** - * @dev Writes the beginning of a JSON array or object based on the token parameter with a property name as the key. - */ - function writeStart(Json memory json, string memory propertyName, bytes1 token) - private - pure - returns (Json memory) - { - if (json.depthBitTracker < 0) { - json.value = string(abi.encodePacked(json.value, LIST_SEPARATOR, '"', propertyName, '": ', token)); - } else { - json.value = string(abi.encodePacked(json.value, '"', propertyName, '": ', token)); - } - - json.depthBitTracker &= MAX_INT256; - json.depthBitTracker++; - - return json; - } - - /** - * @dev Writes the end of a JSON array or object based on the token parameter. - */ - function writeEnd(Json memory json, bytes1 token) private pure returns (Json memory) { - json.value = string(abi.encodePacked(json.value, token)); - json.depthBitTracker = setListSeparatorFlag(json); - - if (getCurrentDepth(json) != 0) { - json.depthBitTracker--; - } - - return json; - } - - /** - * @dev Escapes any characters that required by JSON to be escaped. - */ - function escapeJsonString(string memory value) private pure returns (string memory str) { - bytes memory b = bytes(value); - bool foundEscapeChars; - - for (uint256 i; i < b.length; i++) { - if (b[i] == BACKSLASH) { - foundEscapeChars = true; - break; - } else if (b[i] == DOUBLE_QUOTE) { - foundEscapeChars = true; - break; - } else if (b[i] == FRONTSLASH) { - foundEscapeChars = true; - break; - } else if (b[i] == HORIZONTAL_TAB) { - foundEscapeChars = true; - break; - } else if (b[i] == FORM_FEED) { - foundEscapeChars = true; - break; - } else if (b[i] == NEWLINE) { - foundEscapeChars = true; - break; - } else if (b[i] == CARRIAGE_RETURN) { - foundEscapeChars = true; - break; - } else if (b[i] == BACKSPACE) { - foundEscapeChars = true; - break; - } - } - - if (!foundEscapeChars) { - return value; - } - - for (uint256 i; i < b.length; i++) { - if (b[i] == BACKSLASH) { - str = string(abi.encodePacked(str, "\\\\")); - } else if (b[i] == DOUBLE_QUOTE) { - str = string(abi.encodePacked(str, '\\"')); - } else if (b[i] == FRONTSLASH) { - str = string(abi.encodePacked(str, "\\/")); - } else if (b[i] == HORIZONTAL_TAB) { - str = string(abi.encodePacked(str, "\\t")); - } else if (b[i] == FORM_FEED) { - str = string(abi.encodePacked(str, "\\f")); - } else if (b[i] == NEWLINE) { - str = string(abi.encodePacked(str, "\\n")); - } else if (b[i] == CARRIAGE_RETURN) { - str = string(abi.encodePacked(str, "\\r")); - } else if (b[i] == BACKSPACE) { - str = string(abi.encodePacked(str, "\\b")); - } else { - str = string(abi.encodePacked(str, b[i])); - } - } - - return str; - } - - /** - * @dev Tracks the recursive depth of the nested objects / arrays within the JSON text - * written so far. This provides the depth of the current token. - */ - function getCurrentDepth(Json memory json) private pure returns (int256) { - return json.depthBitTracker & MAX_INT256; - } - - /** - * @dev The highest order bit of json.depthBitTracker is used to discern whether we are writing the first item in a list or not. - * if (json.depthBitTracker >> 255) == 1, add a list separator before writing the item - * else, no list separator is needed since we are writing the first item. - */ - function setListSeparatorFlag(Json memory json) private pure returns (int256) { - return json.depthBitTracker | (int256(1) << 255); - } - - /** - * @dev Converts an address to a string. - */ - function addressToString(address _address) internal pure returns (string memory) { - bytes32 value = bytes32(uint256(uint160(_address))); - bytes16 alphabet = "0123456789abcdef"; - - bytes memory str = new bytes(42); - str[0] = "0"; - str[1] = "x"; - for (uint256 i; i < 20; i++) { - str[2 + i * 2] = alphabet[uint8(value[i + 12] >> 4)]; - str[3 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)]; - } - - return string(str); - } - - /** - * @dev Converts an int to a string. - */ - function intToString(int256 i) internal pure returns (string memory) { - if (i == 0) { - return "0"; - } - - if (i == type(int256).min) { - // hard-coded since int256 min value can't be converted to unsigned - return "-57896044618658097711785492504343953926634992332820282019728792003956564819968"; - } - - bool negative = i < 0; - uint256 len; - uint256 j; - if (!negative) { - j = uint256(i); - } else { - j = uint256(-i); - ++len; // make room for '-' sign - } - - uint256 l = j; - while (j != 0) { - len++; - j /= 10; - } - - bytes memory bstr = new bytes(len); - uint256 k = len; - while (l != 0) { - bstr[--k] = bytes1((48 + uint8(l - (l / 10) * 10))); - l /= 10; - } - - if (negative) { - bstr[0] = "-"; // prepend '-' - } - - return string(bstr); - } - - /** - * @dev Converts a uint to a string. - */ - function uintToString(uint256 _i) internal pure returns (string memory) { - if (_i == 0) { - return "0"; - } - - uint256 j = _i; - uint256 len; - while (j != 0) { - len++; - j /= 10; - } - - bytes memory bstr = new bytes(len); - uint256 k = len; - while (_i != 0) { - bstr[--k] = bytes1((48 + uint8(_i - (_i / 10) * 10))); - _i /= 10; - } - - return string(bstr); - } -} diff --git a/test/protocols/Bundle.t.sol b/test/protocols/Bundle.t.sol index dde5032..6008406 100644 --- a/test/protocols/Bundle.t.sol +++ b/test/protocols/Bundle.t.sol @@ -18,7 +18,7 @@ contract EthSendBundle is Test { Suave.HttpRequest memory request = Bundle.encodeSendBundle(bundle); assertEq( string(request.body), - '{"jsonrpc": "2.0","method": "eth_sendBundle","id": 1,"params": [{"blockNumber": "0x01","txs": ["0x1234"]}]}' + '{"jsonrpc": "2.0", "method": "eth_sendBundle", "id": 1, "params": [{"blockNumber": "0x01", "txs": ["0x1234"]}]}' ); assertTrue(request.withFlashbotsSignature); @@ -28,7 +28,7 @@ contract EthSendBundle is Test { Suave.HttpRequest memory request2 = Bundle.encodeSendBundle(bundle); assertEq( string(request2.body), - '{"jsonrpc": "2.0","method": "eth_sendBundle","id": 1,"params": [{"blockNumber": "0x01","txs": ["0x1234"],"minTimestamp": 2}]}' + '{"jsonrpc": "2.0", "method": "eth_sendBundle", "id": 1, "params": [{"blockNumber": "0x01", "minTimestamp": 2, "txs": ["0x1234"]}]}' ); // encode with 'maxTimestamp' @@ -37,7 +37,7 @@ contract EthSendBundle is Test { Suave.HttpRequest memory request3 = Bundle.encodeSendBundle(bundle); assertEq( string(request3.body), - '{"jsonrpc": "2.0","method": "eth_sendBundle","id": 1,"params": [{"blockNumber": "0x01","txs": ["0x1234"],"minTimestamp": 2,"maxTimestamp": 3}]}' + '{"jsonrpc": "2.0", "method": "eth_sendBundle", "id": 1, "params": [{"blockNumber": "0x01", "minTimestamp": 2, "maxTimestamp": 3, "txs": ["0x1234"]}]}' ); } @@ -49,7 +49,7 @@ contract EthSendBundle is Test { bundle.refundPercent = 50; bytes memory params = Bundle.encodeSimBundle(bundle); - assertEq(string(params), '{"blockNumber": "0x01","percent": 50,"txs": ["0x1234"]}'); + assertEq(string(params), '{"blockNumber": "0x01", "percent": 50, "txs": ["0x1234"]}'); // encode with 'revertingHashes' bundle.revertingHashes = new bytes32[](1); @@ -58,7 +58,7 @@ contract EthSendBundle is Test { bytes memory params2 = Bundle.encodeSimBundle(bundle); assertEq( string(params2), - '{"blockNumber": "0x01","percent": 50,"revertingHashes": ["0x0a80df9c7574c9524999e774c05a27acf214618b45f4948b88ad1083e13a871a"],"txs": ["0x1234"]}' + '{"blockNumber": "0x01", "percent": 50, "revertingHashes": ["0x0a80df9c7574c9524999e774c05a27acf214618b45f4948b88ad1083e13a871a"], "txs": ["0x1234"]}' ); } diff --git a/test/protocols/Suavex.t.sol b/test/protocols/Suavex.t.sol deleted file mode 100644 index d7305d4..0000000 --- a/test/protocols/Suavex.t.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import "forge-std/console.sol"; -import "src/protocols/Suavex.sol"; -import "src/suavelib/Suave.sol"; -import "solady/src/utils/JSONParserLib.sol"; - -contract SuavexTest is Test { - using JSONParserLib for string; - - function testEncodeBuildEthBlockFromBundles() public { - Bundle.BundleObj memory bundle; - bundle.blockNumber = 1; - bundle.minTimestamp = 2; - bundle.maxTimestamp = 3; - bundle.txns = new bytes[](1); - bundle.txns[0] = hex"1234"; - - Suave.BuildBlockArgs memory args; - - args.slot = 1; - args.proposerPubkey = hex"1234"; - args.parent = hex"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - args.timestamp = 1; - args.feeRecipient = address(0); - args.gasLimit = 1; - args.random = hex"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; - args.withdrawals = new Suave.Withdrawal[](1); - - Suave.Withdrawal memory withdrawal; - withdrawal.index = 1; - withdrawal.validator = 1; - withdrawal.Address = address(0); - withdrawal.amount = 1238912748128; - - args.withdrawals[0] = withdrawal; - - args.extra = hex"1234"; - args.beaconRoot = hex"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"; - args.fillPending = false; - - Bundle.BundleObj[] memory bundles = new Bundle.BundleObj[](1); - bundles[0] = bundle; - string memory result = Suavex.encodeBuildEthBlockFromBundles(args, bundles); - assertEq(result, '{"args": {"slot": 1,"proposerPubkey": "EjQ=","parent": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","timestamp": 1,"feeRecipient": "0x0000000000000000000000000000000000000000","gasLimit": 1,"random": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","withdrawals": [{"index": "0x01","validator": "0x01","address": "0x0000000000000000000000000000000000000000","amount": "0x012074f44a60"}],"extra": "EjQ=","beaconRoot": "zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMw=","fillPending": false},"bundles": [{"blockNumber": "0x01","minTimestamp": "0x02","maxTimestamp": "0x03","txs": ["0x1234"]}]}'); - } - - function testEncodeCall() public { - address contractAddr = address(0); - bytes memory input = abi.encodeWithSignature("someMethod(uint256,string,address)", 1, "hello", address(1)); - string memory result = Suavex.encodeCall(contractAddr, input); - assertEq(result, '{"contractAddr": "0x0000000000000000000000000000000000000000","input": "NS8NrQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFaGVsbG8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC="}'); - } -} From bcb90a3b8beeb8065856089c2aa8f3aa3c2baa95 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:17:57 -0700 Subject: [PATCH 13/15] fix doc-comments in Bundle.sol --- src/protocols/Bundle.sol | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index a2d645e..1a4c86d 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -10,9 +10,12 @@ import "solady/src/utils/JSONParserLib.sol"; library Bundle { /// @notice BundleObj is a struct that represents a bundle to be sent to the Flashbots relay. /// @param blockNumber the block number at which the bundle should be executed. + /// @param txns the transactions to be included in the bundle. /// @param minTimestamp the minimum timestamp at which the bundle should be executed. /// @param maxTimestamp the maximum timestamp at which the bundle should be executed. - /// @param txns the transactions to be included in the bundle. + /// @param revertingHashes the hashes of the transactions that the bundle should allow to revert. + /// @param replacementUuid the UUID of the bundle submission that should be replaced by this bundle. This argument must have been passed to the bundle being replaced. + /// @param refundPercent (mev-share) percentage of gas fees that should be refunded to the tx originator. struct BundleObj { uint64 blockNumber; bytes[] txns; @@ -44,10 +47,9 @@ library Bundle { egp = Suave.simulateBundle(simParams); } - /** - * Encodes an [RpcSBundle](https://github.com/flashbots/suave-geth/blob/main/core/types/sbundle.go#L21-L27) - * for `Suave.simulateBundle`. - */ + /// @notice Encodes an [RpcSBundle](https://github.com/flashbots/suave-geth/blob/main/core/types/sbundle.go#L21-L27) for `Suave.simulateBundle`. + /// @param args the bundle to encode. + /// @return params the encoded simulateBundle payload. function encodeSimBundle(BundleObj memory args) internal pure returns (bytes memory params) { require(args.txns.length > 0, "Bundle: no txns"); @@ -75,9 +77,9 @@ library Bundle { params = abi.encodePacked(params, "]}"); } - /** - * Encodes a bundle into an RPC request to `eth_sendBundle`. - */ + /// @notice Encodes a bundle into an RPC request to `eth_sendBundle`. + /// @param args the bundle to encode. + /// @return request the encoded HTTP request. function encodeSendBundle(BundleObj memory args) internal pure returns (Suave.HttpRequest memory) { require(args.txns.length > 0, "Bundle: no txns"); From a3af85abf703d86405709a2b63c5520805f465e9 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:20:20 -0700 Subject: [PATCH 14/15] add missing doc-comment --- src/protocols/Bundle.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index 1a4c86d..d5ea014 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -42,6 +42,9 @@ library Bundle { return Suave.doHTTPRequest(request); } + /// @notice simulate a bundle using the Flashbots bundle API. + /// @param bundle the bundle to simulate. + /// @return egp the simulated effective gas price of the bundle. function simulateBundle(BundleObj memory bundle) internal returns (uint64 egp) { bytes memory simParams = encodeSimBundle(bundle); egp = Suave.simulateBundle(simParams); From c1b8b10eefcc9d1fbc9ec27b3d822fc1231e30fd Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:06:24 -0700 Subject: [PATCH 15/15] fix leading-0 error in bundle blockNumber json encoding --- src/protocols/Bundle.sol | 112 +++++++++++++++++++++++++++++++-------- 1 file changed, 91 insertions(+), 21 deletions(-) diff --git a/src/protocols/Bundle.sol b/src/protocols/Bundle.sol index d5ea014..a201281 100644 --- a/src/protocols/Bundle.sol +++ b/src/protocols/Bundle.sol @@ -36,7 +36,10 @@ library Bundle { /// @param bundle the bundle to send. /// @param url the URL of the Flashbots relay. /// @return response raw bytes response from the Flashbots relay. - function sendBundle(BundleObj memory bundle, string memory url) internal returns (bytes memory) { + function sendBundle( + BundleObj memory bundle, + string memory url + ) internal returns (bytes memory) { Suave.HttpRequest memory request = encodeSendBundle(bundle); request.url = url; return Suave.doHTTPRequest(request); @@ -45,7 +48,9 @@ library Bundle { /// @notice simulate a bundle using the Flashbots bundle API. /// @param bundle the bundle to simulate. /// @return egp the simulated effective gas price of the bundle. - function simulateBundle(BundleObj memory bundle) internal returns (uint64 egp) { + function simulateBundle( + BundleObj memory bundle + ) internal returns (uint64 egp) { bytes memory simParams = encodeSimBundle(bundle); egp = Suave.simulateBundle(simParams); } @@ -53,17 +58,33 @@ library Bundle { /// @notice Encodes an [RpcSBundle](https://github.com/flashbots/suave-geth/blob/main/core/types/sbundle.go#L21-L27) for `Suave.simulateBundle`. /// @param args the bundle to encode. /// @return params the encoded simulateBundle payload. - function encodeSimBundle(BundleObj memory args) internal pure returns (bytes memory params) { + function encodeSimBundle( + BundleObj memory args + ) internal pure returns (bytes memory params) { require(args.txns.length > 0, "Bundle: no txns"); - params = abi.encodePacked('{"blockNumber": "', args.blockNumber.toHexString(), '", '); + params = abi.encodePacked( + '{"blockNumber": "', + args.blockNumber.toMinimalHexString(), + '", ' + ); if (args.refundPercent > 0) { - params = abi.encodePacked(params, '"percent": ', args.refundPercent.toString(), ", "); + params = abi.encodePacked( + params, + '"percent": ', + args.refundPercent.toString(), + ", " + ); } if (args.revertingHashes.length > 0) { params = abi.encodePacked(params, '"revertingHashes": ['); for (uint256 i = 0; i < args.revertingHashes.length; i++) { - params = abi.encodePacked(params, '"', uint256(args.revertingHashes[i]).toHexString(), '"'); + params = abi.encodePacked( + params, + '"', + uint256(args.revertingHashes[i]).toHexString(), + '"' + ); if (i < args.revertingHashes.length - 1) { params = abi.encodePacked(params, ", "); } @@ -72,7 +93,12 @@ library Bundle { } params = abi.encodePacked(params, '"txs": ['); for (uint256 i = 0; i < args.txns.length; i++) { - params = abi.encodePacked(params, '"', args.txns[i].toHexString(), '"'); + params = abi.encodePacked( + params, + '"', + args.txns[i].toHexString(), + '"' + ); if (i < args.txns.length - 1) { params = abi.encodePacked(params, ", "); } @@ -83,24 +109,48 @@ library Bundle { /// @notice Encodes a bundle into an RPC request to `eth_sendBundle`. /// @param args the bundle to encode. /// @return request the encoded HTTP request. - function encodeSendBundle(BundleObj memory args) internal pure returns (Suave.HttpRequest memory) { + function encodeSendBundle( + BundleObj memory args + ) internal pure returns (Suave.HttpRequest memory) { require(args.txns.length > 0, "Bundle: no txns"); // body - bytes memory body = abi.encodePacked('{"jsonrpc": "2.0", "method": "eth_sendBundle", "id": 1, "params": [{'); + bytes memory body = abi.encodePacked( + '{"jsonrpc": "2.0", "method": "eth_sendBundle", "id": 1, "params": [{' + ); // params - body = abi.encodePacked(body, '"blockNumber": "', args.blockNumber.toHexString(), '", '); + body = abi.encodePacked( + body, + '"blockNumber": "', + args.blockNumber.toMinimalHexString(), + '", ' + ); if (args.minTimestamp > 0) { - body = abi.encodePacked(body, '"minTimestamp": ', args.minTimestamp.toString(), ", "); + body = abi.encodePacked( + body, + '"minTimestamp": ', + args.minTimestamp.toString(), + ", " + ); } if (args.maxTimestamp > 0) { - body = abi.encodePacked(body, '"maxTimestamp": ', args.maxTimestamp.toString(), ", "); + body = abi.encodePacked( + body, + '"maxTimestamp": ', + args.maxTimestamp.toString(), + ", " + ); } if (args.revertingHashes.length > 0) { body = abi.encodePacked(body, '"revertingHashes": ['); for (uint256 i = 0; i < args.revertingHashes.length; i++) { - body = abi.encodePacked(body, '"', abi.encodePacked(args.revertingHashes[i]), '"'); + body = abi.encodePacked( + body, + '"', + abi.encodePacked(args.revertingHashes[i]), + '"' + ); if (i < args.revertingHashes.length - 1) { body = abi.encodePacked(body, ", "); } @@ -108,7 +158,12 @@ library Bundle { body = abi.encodePacked(body, "], "); } if (abi.encodePacked(args.replacementUuid).length > 0) { - body = abi.encodePacked(body, '"replacementUuid": "', args.replacementUuid, '", '); + body = abi.encodePacked( + body, + '"replacementUuid": "', + args.replacementUuid, + '", ' + ); } body = abi.encodePacked(body, '"txs": ['); for (uint256 i = 0; i < args.txns.length; i++) { @@ -129,7 +184,9 @@ library Bundle { return request; } - function _stripQuotesAndPrefix(string memory s) internal pure returns (string memory) { + 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++) { @@ -141,7 +198,9 @@ library Bundle { /// @notice decode a bundle from a JSON string. /// @param bundleJson the JSON string of the bundle. /// @return bundle the decoded bundle. - function decodeBundle(string memory bundleJson) public pure returns (Bundle.BundleObj memory) { + 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; @@ -151,20 +210,31 @@ library Bundle { for (uint256 i = 0; i < txnsLength; i++) { JSONParserLib.Item memory txnNode = txnsNode.at(i); - bytes memory txn = HexStrings.fromHexString(_stripQuotesAndPrefix(txnNode.value())); + 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()); + 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()); + bundle.minTimestamp = uint64( + root.at('"minTimestamp"').value().parseUint() + ); } if (root.at('"maxTimestamp"').isNumber()) { - bundle.maxTimestamp = uint64(root.at('"maxTimestamp"').value().parseUint()); + bundle.maxTimestamp = uint64( + root.at('"maxTimestamp"').value().parseUint() + ); } bundle.txns = txns;