Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Batching for Nomad Avail bridge #485

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 72 additions & 32 deletions packages/contracts-da-bridge/contracts/DABridgeMessage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 ============

/**
Expand All @@ -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;
}

/**
Expand All @@ -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) {
Expand Down
37 changes: 15 additions & 22 deletions packages/contracts-da-bridge/contracts/DABridgeRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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 ============
Expand Down
93 changes: 68 additions & 25 deletions packages/contracts-da-bridge/contracts/test/DABridgeMessage.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 {
Expand All @@ -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
);
Expand All @@ -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)
);
}
}
Loading