diff --git a/src/periphery/CompressedSubmitter.sol b/src/periphery/CompressedSubmitter.sol index fe963f9c..da8e81da 100644 --- a/src/periphery/CompressedSubmitter.sol +++ b/src/periphery/CompressedSubmitter.sol @@ -5,6 +5,8 @@ import "openzeppelin/access/Ownable2Step.sol"; import "../interfaces/IBaseManager.sol"; import "../interfaces/IDataReceiver.sol"; +import "../interfaces/IBaseLyraFeed.sol"; + import "forge-std/console2.sol"; contract CompressedSubmitter is IDataReceiver, Ownable2Step { @@ -22,7 +24,7 @@ contract CompressedSubmitter is IDataReceiver, Ownable2Step { * @dev submit compressed data directly (not through the manager) */ function submitCompressedData(bytes calldata data) external { - IBaseManager.ManagerData[] memory feedDatas = _parseFeedDataArray(data); + IBaseManager.ManagerData[] memory feedDatas = _parseCompressedToFeedDatas(data); for (uint i; i < feedDatas.length; i++) { IDataReceiver(feedDatas[i].receiver).acceptData(feedDatas[i].data); @@ -34,7 +36,7 @@ contract CompressedSubmitter is IDataReceiver, Ownable2Step { * Data is compressed into bytes */ function acceptData(bytes calldata data) external { - IBaseManager.ManagerData[] memory feedDatas = _parseFeedDataArray(data); + IBaseManager.ManagerData[] memory feedDatas = _parseCompressedToFeedDatas(data); for (uint i; i < feedDatas.length; i++) { IDataReceiver(feedDatas[i].receiver).acceptData(feedDatas[i].data); @@ -56,10 +58,7 @@ contract CompressedSubmitter is IDataReceiver, Ownable2Step { emit FeedIdRegistered(id, signer); } - function _parseFeedDataArray(bytes calldata data) internal returns (IBaseManager.ManagerData[] memory) { - bytes memory data2 = data[0:1]; - console2.logBytes(data2); - + function _parseCompressedToFeedDatas(bytes calldata data) internal returns (IBaseManager.ManagerData[] memory) { // first byte of each byte array is number of feeds uint8 numFeeds = sliceUint8(data, 1); @@ -89,9 +88,61 @@ contract CompressedSubmitter is IDataReceiver, Ownable2Step { /** * The raw feed data doesn't have signer addresses encoded, so here we attach them, and build FeedData struct + * Parse the following bytes format + * 4 bytes: length of data (uint32) --> l + * [l] bytes: data; + * 8 bytes: deadline (uint64) + * 8 bytes: timestamp (uint64) + * 1 byte: number of signers (uint8) --> k + * [20 x k] bytes address[] signers; + * [65 x k] bytes[] signatures; */ function buildFeedDataFromRaw(bytes calldata data) internal view returns (bytes memory) { - return data; + IBaseLyraFeed.FeedData memory feedData; + + uint offset = 0; + + // 4 bytes of data length + uint length = bytesToUint(data[offset:offset + 4]); + offset += 4; + + // [length] bytes of data + feedData.data = data[offset:offset + length]; + offset += length; + + // 8 bytes of deadline + feedData.deadline = uint64(bytesToUint(data[offset:offset + 8])); + offset += 8; + + // 8 bytes of timestamp + feedData.timestamp = uint64(bytesToUint(data[offset:offset + 8])); + offset += 8; + + { + // 1 byte of number of signers + uint8 numSigners = uint8(bytesToUint(data[offset:offset + 1])); + offset += 1; + + // [20 x k] bytes address[] signers; + address[] memory _signers = new address[](numSigners); + for (uint i; i < numSigners; i++) { + bytes calldata signerIdData = data[offset:offset + 20]; + _signers[i] = address(uint160(bytesToUint(signerIdData))); + offset += 20; + } + feedData.signers = _signers; + + // [65 x k] bytes[] signatures; + bytes[] memory signatures = new bytes[](numSigners); + for (uint i; i < numSigners; i++) { + bytes calldata signatureData = data[offset:offset + 65]; + signatures[i] = signatureData; + offset += 65; + } + + feedData.signatures = signatures; + } + return abi.encode(feedData); } /// read a single byte and return it as a uint8 @@ -101,13 +152,6 @@ contract CompressedSubmitter is IDataReceiver, Ownable2Step { } } - // read 4 bytes and return as uint32 - function sliceUint32(bytes memory bs, uint location) internal pure returns (uint32 x) { - assembly { - x := mload(add(bs, location)) - } - } - function bytesToUint(bytes memory b) internal pure returns (uint num) { for (uint i = 0; i < b.length; i++) { num = num + uint(uint8(b[i])) * (2 ** (8 * (b.length - (i + 1)))); diff --git a/test/feed/integration-tests/CompressedSubmitter.t.sol b/test/feed/integration-tests/CompressedSubmitter.t.sol index a5499411..419de0e5 100644 --- a/test/feed/integration-tests/CompressedSubmitter.t.sol +++ b/test/feed/integration-tests/CompressedSubmitter.t.sol @@ -34,10 +34,11 @@ contract CompressedSubmitterTest is LyraFeedTestUtils { function testSubmitBatchData() public { // prepare raw data IBaseLyraFeed.FeedData memory spotData1 = _getDefaultSpotData(); - bytes memory data1 = _signFeedData(spotFeed1, pk, spotData1); + + bytes memory data1 = _transformToCompressedFeedData(_signFeedData(spotFeed1, pk, spotData1)); IBaseLyraFeed.FeedData memory spotData2 = _getDefaultSpotData(); - bytes memory data2 = _signFeedData(spotFeed2, pk, spotData2); + bytes memory data2 = _transformToCompressedFeedData(_signFeedData(spotFeed2, pk, spotData2)); uint32 data1Length = uint32(data1.length); uint32 data2Length = uint32(data2.length); @@ -47,7 +48,7 @@ contract CompressedSubmitterTest is LyraFeedTestUtils { // bytes[] memory managerDatas = new bytes[](2); bytes memory compressedData = abi.encodePacked(numOfFeeds, feedId1, data1Length, data1, feedId2, data2Length, data2); - console2.log("length", compressedData.length); + console2.log("final compressed data length", compressedData.length); submitter.submitCompressedData(compressedData); @@ -73,4 +74,52 @@ contract CompressedSubmitterTest is LyraFeedTestUtils { signatures: new bytes[](1) }); } + + /** + * Convert abi encoded bytes to the following format: + * + * 4 bytes: length of data (uint32) --> l + * [l] bytes: data; + * 8 bytes: deadline (uint64) + * 8 bytes: timestamp (uint64) + * 1 byte: number of signers (uint8) --> k + * [20 x k] bytes address[] signers; + * [65 x k] bytes[] signatures; + */ + function _transformToCompressedFeedData(bytes memory data) internal view returns (bytes memory) { + IBaseLyraFeed.FeedData memory feedData = abi.decode(data, (IBaseLyraFeed.FeedData)); + uint32 length = uint32(feedData.data.length); + uint8 numOfSigners = uint8(feedData.signers.length); + + // put all signers into a single bytes array (20 bytes each) + bytes memory signers = new bytes(numOfSigners * 20); + for (uint i; i < numOfSigners; i++) { + bytes memory signer = abi.encodePacked(feedData.signers[i]); + for (uint j; j < signer.length; j++) { + signers[i * 20 + j] = signer[j]; + } + } + + // pad signatures to 65 bytes and form packed bytes + bytes memory signatures = new bytes(numOfSigners * 65); + + for (uint i; i < numOfSigners; i++) { + bytes memory signature = feedData.signatures[i]; + for (uint j; j < signature.length; j++) { + signatures[i * 65 + j] = signature[j]; + } + } + + bytes memory compressedData = abi.encodePacked( + length, + feedData.data, + uint64(feedData.deadline), + uint64(feedData.timestamp), + uint8(feedData.signers.length), + signers, + signatures + ); + + return compressedData; + } }