diff --git a/packages/contracts-da-bridge/contracts/DABridgeMessage.sol b/packages/contracts-da-bridge/contracts/DABridgeMessage.sol index bc28b722..1bcfeddd 100644 --- a/packages/contracts-da-bridge/contracts/DABridgeMessage.sol +++ b/packages/contracts-da-bridge/contracts/DABridgeMessage.sol @@ -24,15 +24,23 @@ library DABridgeMessage { enum Types { Invalid, // 0 - DataRoot // 1 + DataRootBatch // 1 } // ============ Constants ============ uint256 private constant IDENTIFIER_LEN = 1; + uint256 private constant COUNT_LEN = 2; uint256 private constant BLOCK_NUMBER_LEN = 4; uint256 private constant DATA_ROOT_LEN = 32; + // ============ Structs ============ + + struct DataRootBatchItem { + bytes32 dataRoot; + uint32 blockNumber; + } + // ============ Internal Functions ============ /** @@ -49,9 +57,19 @@ library DABridgeMessage { * @param _view The bytes string * @return TRUE if message is valid */ - function isValidDataRootLength(bytes29 _view) internal pure returns (bool) { + function isValidDataRootBatchLength(bytes29 _view) + internal + pure + returns (bool) + { uint256 _len = _view.len(); - return _len == IDENTIFIER_LEN + BLOCK_NUMBER_LEN + DATA_ROOT_LEN; + uint16 dataCount = count(_view); + require(dataCount > 0, "!valid message"); + uint256 expectedLen = IDENTIFIER_LEN + + COUNT_LEN + + dataCount * + (BLOCK_NUMBER_LEN + DATA_ROOT_LEN); + return _len == expectedLen; } /** @@ -74,53 +92,75 @@ library DABridgeMessage { } /** - * @notice Checks that the message is of type DataRoot + * @notice Checks that the message is of type DataRootBatch * @param _view The message - * @return True if the message is of type DataRoot + * @return True if the message is of type DataRootBatch */ - function isDataRoot(bytes29 _view) internal pure returns (bool) { - return isType(_view, Types.DataRoot); + function isDataRootBatch(bytes29 _view) internal pure returns (bool) { + return isType(_view, Types.DataRootBatch); } /** * @notice Creates a serialized data root from components - * @param _blockNumber The block number - * @param _root The root - * @return The formatted data root + * @param data block number + root array + * @return result The formatted data root */ - function formatDataRoot(uint32 _blockNumber, bytes32 _root) + function formatDataRootBatch(DataRootBatchItem[] memory data) internal pure - returns (bytes memory) + returns (bytes memory result) { - return abi.encodePacked(uint8(Types.DataRoot), _blockNumber, _root); + result = abi.encodePacked( + uint8(Types.DataRootBatch), + uint16(data.length) + ); + for (uint256 i = 0; i < data.length; i++) { + result = abi.encodePacked( + result, + data[i].blockNumber, + data[i].dataRoot + ); + } } /** - * @notice Retrieves the block number from a message - * @param _message The message - * @return The block number + * @notice Retrieves the number of data roots from a message + * @param _view The message + * @return The amount of data roots */ - function blockNumber(bytes29 _message) internal pure returns (uint32) { - return - uint32(_message.indexUint(IDENTIFIER_LEN, uint8(BLOCK_NUMBER_LEN))); + function count(bytes29 _view) internal pure returns (uint16) { + return uint16(_view.indexUint(IDENTIFIER_LEN, uint8(COUNT_LEN))); } - /** - * @notice Retrieves the data root from a message - * @param _message The message - * @return The data root - */ - function dataRoot(bytes29 _message) internal pure returns (bytes32) { - return - _message.index( - BLOCK_NUMBER_LEN + IDENTIFIER_LEN, - uint8(DATA_ROOT_LEN) - ); + function dataRootBatch(bytes29 _view) + internal + pure + returns (DataRootBatchItem[] memory) + { + uint16 _count = count(_view); + DataRootBatchItem[] memory _dataRoots = new DataRootBatchItem[](_count); + uint256 _offset = IDENTIFIER_LEN + COUNT_LEN; + for (uint256 i = 0; i < _count; i++) { + _dataRoots[i] = DataRootBatchItem({ + blockNumber: uint32( + _view.indexUint(_offset, uint8(BLOCK_NUMBER_LEN)) + ), + dataRoot: bytes32( + _view.indexUint( + _offset + BLOCK_NUMBER_LEN, + uint8(DATA_ROOT_LEN) + ) + ) + }); + _offset += BLOCK_NUMBER_LEN + DATA_ROOT_LEN; + } + return _dataRoots; } - function isValidDataRoot(bytes29 _view) internal pure returns (bool) { - return isType(_view, Types.DataRoot) && isValidDataRootLength(_view); + function isValidDataRootBatch(bytes29 _view) internal pure returns (bool) { + return + isType(_view, Types.DataRootBatch) && + isValidDataRootBatchLength(_view); } function getTypedView(bytes29 _view) internal pure returns (bytes29) { diff --git a/packages/contracts-da-bridge/contracts/DABridgeRouter.sol b/packages/contracts-da-bridge/contracts/DABridgeRouter.sol index e66ed0b0..eb55762c 100644 --- a/packages/contracts-da-bridge/contracts/DABridgeRouter.sol +++ b/packages/contracts-da-bridge/contracts/DABridgeRouter.sol @@ -74,7 +74,7 @@ contract DABridgeRouter is Version0, Router { ) external override onlyReplica onlyRemoteRouter(_origin, _sender) { require(_origin == _availDomain, "!valid domain"); bytes29 _view = _message.ref(0).getTypedView(); - if (_view.isValidDataRoot()) { + if (_view.isValidDataRootBatch()) { _handleDataRoot(_origin, _nonce, _view); } else { revert("!valid message"); @@ -94,27 +94,20 @@ contract DABridgeRouter is Version0, Router { uint32 _nonce, bytes29 _message ) internal { - (uint32 blockNumber, bytes32 dataRoot) = _parse(_message); - assert(roots[blockNumber] == 0); - roots[blockNumber] = dataRoot; - emit DataRootReceived( - _originAndNonce(_origin, _nonce), - blockNumber, - dataRoot - ); - } - - /** - * @notice parse blockNumber and root from message and emit event - * @param _message The message in the form of raw bytes - */ - function _parse(bytes29 _message) - internal - pure - returns (uint32 blockNumber, bytes32 dataRoot) - { - blockNumber = _message.blockNumber(); - dataRoot = _message.dataRoot(); + DABridgeMessage.DataRootBatchItem[] memory batch = _message + .dataRootBatch(); + for (uint256 i = 0; i < batch.length; i++) { + bytes32 root = batch[i].dataRoot; + uint32 blockNumber = batch[i].blockNumber; + assert(roots[blockNumber] == 0); + + roots[blockNumber] = root; + emit DataRootReceived( + (uint64(_origin) << 32) | uint64(_nonce), + blockNumber, + root + ); + } } // ============ Internal: Utils ============ diff --git a/packages/contracts-da-bridge/contracts/test/DABridgeMessage.t.sol b/packages/contracts-da-bridge/contracts/test/DABridgeMessage.t.sol index 8fede089..5e4909e1 100644 --- a/packages/contracts-da-bridge/contracts/test/DABridgeMessage.t.sol +++ b/packages/contracts-da-bridge/contracts/test/DABridgeMessage.t.sol @@ -22,6 +22,7 @@ contract DABridgeMessageTest is Test { using DABridgeMessage for bytes29; uint256 private constant IDENTIFIER_LEN = 1; + uint256 private constant COUNT_LEN = 2; uint256 private constant BLOCK_NUMBER_LEN = 4; uint256 private constant DATA_ROOT_LEN = 32; @@ -33,21 +34,33 @@ contract DABridgeMessageTest is Test { /// @notice Verify that the enum for the memview types remains unchaged function test_typeOrderUnchanged() public { assertEq(uint256(DABridgeMessage.Types.Invalid), 0); - assertEq(uint256(DABridgeMessage.Types.DataRoot), 1); + assertEq(uint256(DABridgeMessage.Types.DataRootBatch), 1); } /// @notice A DABridgeMessage must be IDENTIFIER_LEN + BLOCK_NUMBER_LEN + DATA_ROOT_LEN /// so that it can contain all the required information needed by the Bridge. - function test_isValidMessageLength() public { - bytes memory longMessage = new bytes(38); - bytes memory correctMessage = new bytes(37); - bytes memory shortMessage = new bytes(36); + function test_isValidMessageLength(uint16 count) public { + vm.assume(count > 0); + bytes memory identifierAndLength = abi.encodePacked(uint8(0), count); + uint256 correctLength = count * (BLOCK_NUMBER_LEN + DATA_ROOT_LEN); + bytes memory longMessage = abi.encodePacked( + identifierAndLength, + new bytes(correctLength + 1) + ); + bytes memory correctMessage = abi.encodePacked( + identifierAndLength, + new bytes(correctLength) + ); + bytes memory shortMessage = abi.encodePacked( + identifierAndLength, + new bytes(correctLength - 1) + ); bytes29 longView = longMessage.ref(0); bytes29 correctView = correctMessage.ref(0); bytes29 shortView = shortMessage.ref(0); - assertFalse(shortView.isValidDataRootLength()); - assertFalse(longView.isValidDataRootLength()); - assertTrue(correctView.isValidDataRootLength()); + assertFalse(shortView.isValidDataRootBatchLength()); + assertFalse(longView.isValidDataRootBatchLength()); + assertTrue(correctView.isValidDataRootBatchLength()); } function test_messageTypeReturnsCorrectType() public { @@ -60,29 +73,31 @@ contract DABridgeMessageTest is Test { uint256(DABridgeMessage.Types.Invalid) ); viewUnderTest = emptyView.castTo( - uint40(DABridgeMessage.Types.DataRoot) + uint40(DABridgeMessage.Types.DataRootBatch) ); assertEq( uint256(viewUnderTest.messageType()), - uint256(DABridgeMessage.Types.DataRoot) + uint256(DABridgeMessage.Types.DataRootBatch) ); } function test_detectsCorrectType() public { bytes memory message = abi.encodePacked( - uint8(DABridgeMessage.Types.DataRoot), + uint8(DABridgeMessage.Types.DataRootBatch), + uint32(1), _blockNumber, _dataRoot ); bytes29 _view = message.ref(0); - assertFalse(_view.isDataRoot()); + assertFalse(_view.isDataRootBatch()); _view = _view.getTypedView(); - assertTrue(_view.isDataRoot()); + assertTrue(_view.isDataRootBatch()); } function test_assertsExistingType() public { bytes memory message = abi.encodePacked( uint8(255), + uint32(1), _blockNumber, _dataRoot ); @@ -94,34 +109,62 @@ contract DABridgeMessageTest is Test { function test_formatDataRootSucceeds() public { bytes29 manualDataRoot = abi .encodePacked( - DABridgeMessage.Types.DataRoot, + DABridgeMessage.Types.DataRootBatch, + uint16(1), _blockNumber, _dataRoot ) .ref(0); - bytes29 dataRoot = DABridgeMessage - .formatDataRoot(_blockNumber, _dataRoot) - .ref(0); + + DABridgeMessage.DataRootBatchItem[] + memory _dataRoots = new DABridgeMessage.DataRootBatchItem[](1); + + _dataRoots[0] = DABridgeMessage.DataRootBatchItem({ + dataRoot: _dataRoot, + blockNumber: _blockNumber + }); + + bytes29 dataRoot = DABridgeMessage.formatDataRootBatch(_dataRoots).ref( + 0 + ); assertEq(dataRoot.keccak(), manualDataRoot.keccak()); } function test_getBlockNumber() public { - bytes29 dataRoot = DABridgeMessage - .formatDataRoot(_blockNumber, _dataRoot) - .ref(0); + DABridgeMessage.DataRootBatchItem[] + memory _dataRoots = new DABridgeMessage.DataRootBatchItem[](1); + + _dataRoots[0] = DABridgeMessage.DataRootBatchItem({ + dataRoot: _dataRoot, + blockNumber: _blockNumber + }); + + bytes29 dataRoot = DABridgeMessage.formatDataRootBatch(_dataRoots).ref( + 0 + ); + assertEq( uint256(_blockNumber), - uint256(DABridgeMessage.blockNumber(dataRoot)) + uint256(DABridgeMessage.dataRootBatch(dataRoot)[0].blockNumber) ); } function test_getDataRoot() public { - bytes29 dataRoot = DABridgeMessage - .formatDataRoot(_blockNumber, _dataRoot) - .ref(0); + DABridgeMessage.DataRootBatchItem[] + memory _dataRoots = new DABridgeMessage.DataRootBatchItem[](1); + + _dataRoots[0] = DABridgeMessage.DataRootBatchItem({ + dataRoot: _dataRoot, + blockNumber: _blockNumber + }); + + bytes29 dataRoot = DABridgeMessage.formatDataRootBatch(_dataRoots).ref( + 0 + ); + assertEq( uint256(_dataRoot), - uint256(DABridgeMessage.dataRoot(dataRoot)) + uint256(DABridgeMessage.dataRootBatch(dataRoot)[0].dataRoot) ); } } diff --git a/packages/contracts-da-bridge/contracts/test/DABridgeRouter.t.sol b/packages/contracts-da-bridge/contracts/test/DABridgeRouter.t.sol index 4c98172c..528e4000 100644 --- a/packages/contracts-da-bridge/contracts/test/DABridgeRouter.t.sol +++ b/packages/contracts-da-bridge/contracts/test/DABridgeRouter.t.sol @@ -47,19 +47,29 @@ contract DABridgeRouterTest is Test { } function test_failsInvalidDomain() public { - bytes memory message = DABridgeMessage.formatDataRoot( - _blockNumber, - _dataRoot - ); + DABridgeMessage.DataRootBatchItem[] + memory _dataRoots = new DABridgeMessage.DataRootBatchItem[](1); + + _dataRoots[0] = DABridgeMessage.DataRootBatchItem({ + dataRoot: _dataRoot, + blockNumber: _blockNumber + }); + + bytes memory message = DABridgeMessage.formatDataRootBatch(_dataRoots); vm.expectRevert("!valid domain"); router.handle(invalidDomain, uint32(0), remoteRouter, message); } function test_handleSuccess() public { - bytes memory message = DABridgeMessage.formatDataRoot( - _blockNumber, - _dataRoot - ); + DABridgeMessage.DataRootBatchItem[] + memory _dataRoots = new DABridgeMessage.DataRootBatchItem[](1); + + _dataRoots[0] = DABridgeMessage.DataRootBatchItem({ + dataRoot: _dataRoot, + blockNumber: _blockNumber + }); + + bytes memory message = DABridgeMessage.formatDataRootBatch(_dataRoots); uint64 originAndNonce = (uint64(domain) << 32) | 0; vm.expectEmit(true, true, false, true); emit DataRootReceived(originAndNonce, _blockNumber, _dataRoot);