diff --git a/contracts/src/arbitrumToEth/VeaInboxArbToEth.sol b/contracts/src/arbitrumToEth/VeaInboxArbToEth.sol index 38473644..1779f504 100644 --- a/contracts/src/arbitrumToEth/VeaInboxArbToEth.sol +++ b/contracts/src/arbitrumToEth/VeaInboxArbToEth.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -/// @custom:authors: [@jaybuidl, @shotaronowhere] +/// @custom:authors: [@jaybuidl, @mani99brar, @shotaronowhere] /// @custom:reviewers: [] /// @custom:auditors: [] /// @custom:bounties: [] @@ -70,26 +70,16 @@ contract VeaInboxArbToEth is IVeaInbox { /// Amortized cost is constant. /// Note: See docs for details how inbox manages merkle tree state. /// @param _to The address of the contract on the receiving chain which receives the calldata. - /// @param _fnSelector The function selector of the receiving contract. - /// @param _data The message calldata, abi.encode(param1, param2, ...) + /// @param _data The message data for the receiving gateway. /// @return msgId The zero based index of the message in the inbox. - function sendMessage(address _to, bytes4 _fnSelector, bytes memory _data) external override returns (uint64) { + function sendMessage(address _to, bytes calldata _data) external override returns (uint64) { uint64 oldCount = count; // Given arbitrum's speed limit of 7 million gas / second, it would take atleast 8 million years of full blocks to overflow. // It *should* be impossible to overflow, but we check to be safe when appending to the tree. require(oldCount < type(uint64).max, "Inbox is full."); - bytes memory nodeData = abi.encodePacked( - oldCount, - _to, - // _data is abi.encode(param1, param2, ...), we need to encode it again to get the correct leaf data - abi.encodePacked( // equivalent to abi.encodeWithSelector(fnSelector, msg.sender, param1, param2, ...) - _fnSelector, - bytes32(uint256(uint160(msg.sender))), // big endian padded encoding of msg.sender, simulating abi.encodeWithSelector - _data - ) - ); + bytes memory nodeData = abi.encodePacked(oldCount, _to, msg.sender, _data); // single hashed leaf bytes32 newInboxNode = keccak256(nodeData); diff --git a/contracts/src/arbitrumToEth/VeaOutboxArbToEth.sol b/contracts/src/arbitrumToEth/VeaOutboxArbToEth.sol index 7bba912a..f182b6d2 100644 --- a/contracts/src/arbitrumToEth/VeaOutboxArbToEth.sol +++ b/contracts/src/arbitrumToEth/VeaOutboxArbToEth.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -/// @custom:authors: [@jaybuidl, @shotaronowhere] +/// @custom:authors: [@jaybuidl, @mani99brar, @shotaronowhere] /// @custom:reviewers: [] /// @custom:auditors: [] /// @custom:bounties: [] @@ -12,6 +12,7 @@ import "../canonical/arbitrum/ISequencerInbox.sol"; import "../canonical/arbitrum/IBridge.sol"; import "../canonical/arbitrum/IOutbox.sol"; import "../interfaces/outboxes/IVeaOutboxOnL1.sol"; +import "../interfaces/gateways/IReceiverGateway.sol"; /// @dev Vea Outbox From Arbitrum to Ethereum. /// Note: This contract is deployed on Ethereum. @@ -355,11 +356,18 @@ contract VeaOutboxArbToEth is IVeaOutboxOnL1 { /// @param _proof The merkle proof to prove the message inclusion in the inbox state root. /// @param _msgId The zero based index of the message in the inbox. /// @param _to The address of the contract on Ethereum to call. - /// @param _message The message encoded in the vea inbox as abi.encodeWithSelector(fnSelector, msg.sender, param1, param2, ...) - function sendMessage(bytes32[] calldata _proof, uint64 _msgId, address _to, bytes calldata _message) external { + /// @param _from The address of the contract on Arbitrum that sent the message. + /// @param _message The message in the vea inbox. + function sendMessage( + bytes32[] calldata _proof, + uint64 _msgId, + address _to, + address _from, + bytes calldata _message + ) external { require(_proof.length < 64, "Proof too long."); - bytes32 nodeHash = keccak256(abi.encodePacked(_msgId, _to, _message)); + bytes32 nodeHash = keccak256(abi.encodePacked(_msgId, _to, _from, _message)); // double hashed leaf // avoids second order preimage attacks @@ -407,8 +415,7 @@ contract VeaOutboxArbToEth is IVeaOutboxOnL1 { relayed[relayIndex] = replay | bytes32(1 << offset); // UNTRUSTED. - (bool success, ) = _to.call(_message); - require(success, "Failed to call contract"); + IReceiverGateway(_to).receiveMessage(_from, _message); emit MessageRelayed(_msgId); } @@ -477,7 +484,7 @@ contract VeaOutboxArbToEth is IVeaOutboxOnL1 { } else { address challenger = _claim.challenger; _claim.challenger = address(0); - claimHashes[_epoch] == hashClaim(_claim); + claimHashes[_epoch] = hashClaim(_claim); payable(challenger).send(deposit); // User is responsible for accepting ETH. } } diff --git a/contracts/src/arbitrumToGnosis/VeaInboxArbToGnosis.sol b/contracts/src/arbitrumToGnosis/VeaInboxArbToGnosis.sol index edf4a194..dc24cb83 100644 --- a/contracts/src/arbitrumToGnosis/VeaInboxArbToGnosis.sol +++ b/contracts/src/arbitrumToGnosis/VeaInboxArbToGnosis.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -/// @custom:authors: [@jaybuidl, @shotaronowhere] +/// @custom:authors: [@jaybuidl, @mani99brar, @shotaronowhere] /// @custom:reviewers: [] /// @custom:auditors: [] /// @custom:bounties: [] @@ -69,27 +69,17 @@ contract VeaInboxArbToGnosis is IVeaInbox { /// `O(log(count))` where count is the number of messages already sent. /// Amortized cost is constant. /// Note: See docs for details how inbox manages merkle tree state. - /// @param _to The address of the contract on the receiving chain which receives the calldata. - /// @param _fnSelector The function selector of the receiving contract. - /// @param _data The message calldata, abi.encode(param1, param2, ...) + /// @param _to The address of the contract on the receiving chain which receives the message. + /// @param _data The message data for the receiving gateway. /// @return msgId The zero based index of the message in the inbox. - function sendMessage(address _to, bytes4 _fnSelector, bytes memory _data) external override returns (uint64) { + function sendMessage(address _to, bytes calldata _data) external override returns (uint64) { uint64 oldCount = count; // Given arbitrum's speed limit of 7 million gas / second, it would take atleast 8 million years of full blocks to overflow. // It *should* be impossible to overflow, but we check to be safe when appending to the tree. require(oldCount < type(uint64).max, "Inbox is full."); - bytes memory nodeData = abi.encodePacked( - oldCount, - _to, - // _data is abi.encode(param1, param2, ...), we need to encode it again to get the correct leaf data - abi.encodePacked( // equivalent to abi.encodeWithSelector(fnSelector, msg.sender, param1, param2, ...) - _fnSelector, - bytes32(uint256(uint160(msg.sender))), // big endian padded encoding of msg.sender, simulating abi.encodeWithSelector - _data - ) - ); + bytes memory nodeData = abi.encodePacked(oldCount, _to, msg.sender, _data); // single hashed leaf bytes32 newInboxNode = keccak256(nodeData); diff --git a/contracts/src/arbitrumToGnosis/VeaOutboxArbToGnosis.sol b/contracts/src/arbitrumToGnosis/VeaOutboxArbToGnosis.sol index e328b281..a980997b 100644 --- a/contracts/src/arbitrumToGnosis/VeaOutboxArbToGnosis.sol +++ b/contracts/src/arbitrumToGnosis/VeaOutboxArbToGnosis.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -/// @custom:authors: [@jaybuidl, @shotaronowhere] +/// @custom:authors: [@jaybuidl, @mani99brar, @shotaronowhere] /// @custom:reviewers: [] /// @custom:auditors: [] /// @custom:bounties: [] @@ -12,6 +12,7 @@ import "../canonical/gnosis-chain/IAMB.sol"; import "../interfaces/outboxes/IVeaOutboxOnL1.sol"; import "../interfaces/updaters/ISequencerDelayUpdatable.sol"; import "../interfaces/tokens/gnosis/IWETH.sol"; +import "../interfaces/gateways/IReceiverGateway.sol"; /// @dev Vea Outbox From Arbitrum to Gnosis. /// Note: This contract is deployed on Gnosis. @@ -299,11 +300,18 @@ contract VeaOutboxArbToGnosis is IVeaOutboxOnL1, ISequencerDelayUpdatable { /// @param _proof The merkle proof to prove the message inclusion in the inbox state root. /// @param _msgId The zero based index of the message in the inbox. /// @param _to The address of the contract on Gnosis to call. - /// @param _message The message encoded in the vea inbox as abi.encodeWithSelector(fnSelector, msg.sender, param1, param2, ...) - function sendMessage(bytes32[] calldata _proof, uint64 _msgId, address _to, bytes calldata _message) external { + /// @param _from The address of the contract on Arbitrum that sent the message. + /// @param _message The message in the vea inbox + function sendMessage( + bytes32[] calldata _proof, + uint64 _msgId, + address _to, + address _from, + bytes calldata _message + ) external { require(_proof.length < 64, "Proof too long."); - bytes32 nodeHash = keccak256(abi.encodePacked(_msgId, _to, _message)); + bytes32 nodeHash = keccak256(abi.encodePacked(_msgId, _to, _from, _message)); // double hashed leaf // avoids second order preimage attacks @@ -351,8 +359,7 @@ contract VeaOutboxArbToGnosis is IVeaOutboxOnL1, ISequencerDelayUpdatable { relayed[relayIndex] = replay | bytes32(1 << offset); // UNTRUSTED. - (bool success, ) = _to.call(_message); - require(success, "Failed to call contract"); + IReceiverGateway(_to).receiveMessage(_from, _message); emit MessageRelayed(_msgId); } @@ -421,7 +428,7 @@ contract VeaOutboxArbToGnosis is IVeaOutboxOnL1, ISequencerDelayUpdatable { } else { address challenger = _claim.challenger; _claim.challenger = address(0); - claimHashes[_epoch] == hashClaim(_claim); + claimHashes[_epoch] = hashClaim(_claim); require(weth.transfer(challenger, deposit), "Failed WETH transfer."); // should revert on errors, but we check return value anyways } } diff --git a/contracts/src/gnosisToArbitrum/VeaInboxGnosisToArb.sol b/contracts/src/gnosisToArbitrum/VeaInboxGnosisToArb.sol index 36abf3c1..625c1bf4 100644 --- a/contracts/src/gnosisToArbitrum/VeaInboxGnosisToArb.sol +++ b/contracts/src/gnosisToArbitrum/VeaInboxGnosisToArb.sol @@ -70,27 +70,17 @@ contract VeaInboxGnosisToArb is IVeaInbox { /// `O(log(count))` where count is the number of messages already sent. /// Amortized cost is constant. /// Note: See docs for details how inbox manages merkle tree state. - /// @param _to The address of the contract on the receiving chain which receives the calldata. - /// @param _fnSelector The function selector of the receiving contract. - /// @param _data The message calldata, abi.encode(param1, param2, ...) + /// @param _to The address of the contract on the receiving chain which receives the message. + /// @param _data The message data for the receiving gateway. /// @return msgId The zero based index of the message in the inbox. - function sendMessage(address _to, bytes4 _fnSelector, bytes memory _data) external override returns (uint64) { + function sendMessage(address _to, bytes calldata _data) external override returns (uint64) { uint64 oldCount = count; // Given arbitrum's speed limit of 7 million gas / second, it would take atleast 8 million years of full blocks to overflow. // It *should* be impossible to overflow, but we check to be safe when appending to the tree. require(oldCount < type(uint64).max, "Inbox is full."); - bytes memory nodeData = abi.encodePacked( - oldCount, - _to, - // _data is abi.encode(param1, param2, ...), we need to encode it again to get the correct leaf data - abi.encodePacked( // equivalent to abi.encodeWithSelector(fnSelector, msg.sender, param1, param2, ...) - _fnSelector, - bytes32(uint256(uint160(msg.sender))), // big endian padded encoding of msg.sender, simulating abi.encodeWithSelector - _data - ) - ); + bytes memory nodeData = abi.encodePacked(oldCount, _to, msg.sender, _data); // single hashed leaf bytes32 newInboxNode = keccak256(nodeData); diff --git a/contracts/src/gnosisToArbitrum/VeaOutboxGnosisToArb.sol b/contracts/src/gnosisToArbitrum/VeaOutboxGnosisToArb.sol index 957ff9f9..62136c16 100644 --- a/contracts/src/gnosisToArbitrum/VeaOutboxGnosisToArb.sol +++ b/contracts/src/gnosisToArbitrum/VeaOutboxGnosisToArb.sol @@ -10,6 +10,7 @@ pragma solidity ^0.8.24; import "../interfaces/outboxes/IVeaOutboxOnL2.sol"; import "../canonical/arbitrum/AddressAliasHelper.sol"; +import "../interfaces/gateways/IReceiverGateway.sol"; /// @dev Vea Outbox From Gnosis to Arbitrum. /// Note: This contract is deployed on Arbitrum. @@ -285,11 +286,18 @@ contract VeaOutboxGnosisToArb is IVeaOutboxOnL2 { /// @param _proof The merkle proof to prove the message inclusion in the inbox state root. /// @param _msgId The zero based index of the message in the inbox. /// @param _to The address of the contract on Arbitrum to call. - /// @param _message The message encoded in the vea inbox as abi.encodeWithSelector(fnSelector, msg.sender, param1, param2, ...) - function sendMessage(bytes32[] memory _proof, uint64 _msgId, address _to, bytes memory _message) external { + /// @param _from The address of the message sender + /// @param _message The message in the vea inbox + function sendMessage( + bytes32[] memory _proof, + uint64 _msgId, + address _to, + address _from, + bytes memory _message + ) external { require(_proof.length < 64, "Proof too long."); - bytes32 nodeHash = keccak256(abi.encodePacked(_msgId, _to, _message)); + bytes32 nodeHash = keccak256(abi.encodePacked(_msgId, _to, _from, _message)); // double hashed leaf // avoids second order preimage attacks @@ -337,8 +345,7 @@ contract VeaOutboxGnosisToArb is IVeaOutboxOnL2 { relayed[relayIndex] = replay | bytes32(1 << offset); // UNTRUSTED. - (bool success, ) = _to.call(_message); - require(success, "Failed to call contract"); + IReceiverGateway(_to).receiveMessage(_from, _message); emit MessageRelayed(_msgId); } diff --git a/contracts/src/interfaces/gateways/IReceiverGateway.sol b/contracts/src/interfaces/gateways/IReceiverGateway.sol index e49d8e30..a2777a7b 100644 --- a/contracts/src/interfaces/gateways/IReceiverGateway.sol +++ b/contracts/src/interfaces/gateways/IReceiverGateway.sol @@ -12,4 +12,6 @@ interface IReceiverGateway { function veaOutbox() external view returns (address); function senderGateway() external view returns (address); + + function receiveMessage(address msgSender, bytes calldata msgData) external; } diff --git a/contracts/src/interfaces/inboxes/IVeaInbox.sol b/contracts/src/interfaces/inboxes/IVeaInbox.sol index e5375b07..55bf81a5 100644 --- a/contracts/src/interfaces/inboxes/IVeaInbox.sol +++ b/contracts/src/interfaces/inboxes/IVeaInbox.sol @@ -11,11 +11,10 @@ pragma solidity ^0.8.24; interface IVeaInbox { /// @dev Sends an arbitrary message to receiving chain. /// Note: Calls authenticated by receiving gateway checking the sender argument. - /// @param _to The cross-domain contract address which receives the calldata. - /// @param _fnSelection The function selector of the receiving contract. - /// @param _data The message calldata, abi.encode(...) + /// @param _to The cross-domain contract address which receives the message. + /// @param _data The message data. /// @return msgId The index of the message in the inbox, as a message Id, needed to relay the message. - function sendMessage(address _to, bytes4 _fnSelection, bytes memory _data) external returns (uint64 msgId); + function sendMessage(address _to, bytes calldata _data) external returns (uint64 msgId); /// @dev Snapshots can be saved a maximum of once per epoch. /// Saves snapshot of state root. diff --git a/contracts/src/interfaces/outboxes/IVeaOutboxOnL1.sol b/contracts/src/interfaces/outboxes/IVeaOutboxOnL1.sol index 94fbf25f..46afa101 100644 --- a/contracts/src/interfaces/outboxes/IVeaOutboxOnL1.sol +++ b/contracts/src/interfaces/outboxes/IVeaOutboxOnL1.sol @@ -11,14 +11,21 @@ pragma solidity ^0.8.24; import "../types/VeaClaim.sol"; /// @dev Interface of the Vea Outbox on L1 chains like Ethereum, Gnosis, Polygon POS where storage is expensive. + interface IVeaOutboxOnL1 { /// @dev Verifies and relays the message. - /// Note: Gateways expect first argument of message call to be the arbitrum message sender, used for authentication. /// @param _proof The merkle proof to prove the message. /// @param _msgId The zero based index of the message in the inbox. /// @param _to The address to send the message to. + /// @param _from The address of the contract on L2 that sent the message, used for authentication. /// @param _message The message to relay. - function sendMessage(bytes32[] calldata _proof, uint64 _msgId, address _to, bytes calldata _message) external; + function sendMessage( + bytes32[] calldata _proof, + uint64 _msgId, + address _to, + address _from, + bytes calldata _message + ) external; /// @dev Resolves any challenge of the optimistic claim for 'epoch' using the canonical bridge. /// Note: Access restricted to canonical bridge. diff --git a/contracts/src/interfaces/outboxes/IVeaOutboxOnL2.sol b/contracts/src/interfaces/outboxes/IVeaOutboxOnL2.sol index ed6a824c..fa8d7b40 100644 --- a/contracts/src/interfaces/outboxes/IVeaOutboxOnL2.sol +++ b/contracts/src/interfaces/outboxes/IVeaOutboxOnL2.sol @@ -15,8 +15,15 @@ interface IVeaOutboxOnL2 { /// @param _proof The merkle proof to prove the message. /// @param _msgId The zero based index of the message in the inbox. /// @param _to The address to send the message to. + /// @param _from The address of the message sender. /// @param _message The message to relay. - function sendMessage(bytes32[] calldata _proof, uint64 _msgId, address _to, bytes calldata _message) external; + function sendMessage( + bytes32[] calldata _proof, + uint64 _msgId, + address _to, + address _from, + bytes calldata _message + ) external; /// @dev Resolves any challenge of the optimistic claim for 'epoch' using the canonical bridge. /// Note: Access restricted to canonical bridge. diff --git a/contracts/src/test/gateways/IReceiverGatewayMock.sol b/contracts/src/test/gateways/IReceiverGatewayMock.sol index 27cb449d..47407139 100644 --- a/contracts/src/test/gateways/IReceiverGatewayMock.sol +++ b/contracts/src/test/gateways/IReceiverGatewayMock.sol @@ -12,5 +12,8 @@ import "../../interfaces/gateways/IReceiverGateway.sol"; interface IReceiverGatewayMock is IReceiverGateway { /// Receive the message from the sender gateway. - function receiveMessage(address msgSender, uint256 _data) external; + function digestMessage(uint256 _data) external; + + /// Receive the message array from the sender gateway. + function digestMessageArray(uint256[] calldata _data) external; } diff --git a/contracts/src/test/gateways/ReceiverGatewayMock.sol b/contracts/src/test/gateways/ReceiverGatewayMock.sol index 23fd4d91..341fc6f0 100644 --- a/contracts/src/test/gateways/ReceiverGatewayMock.sol +++ b/contracts/src/test/gateways/ReceiverGatewayMock.sol @@ -9,6 +9,7 @@ pragma solidity ^0.8.24; import "./IReceiverGatewayMock.sol"; +import "../../interfaces/outboxes/IVeaOutboxOnL1.sol"; /// Receiver Gateway Mock /// Counterpart of `SenderGatewayMock` @@ -18,37 +19,40 @@ contract ReceiverGatewayMock is IReceiverGatewayMock { uint256 public messageCount; uint256 public data; + uint256[] public dataArray; constructor(address _veaOutbox, address _senderGateway) { veaOutbox = _veaOutbox; senderGateway = _senderGateway; } - modifier onlyFromAuthenticatedVeaSender(address messageSender) { + modifier onlyFromVeaBridge(address msgSender) { require(veaOutbox == msg.sender, "Vea Bridge only."); - require(messageSender == senderGateway, "Only the sender gateway is allowed."); + require(senderGateway == msgSender, "Sender gateway mismatch."); _; } - /// Receive the message from the sender gateway. - function receiveMessage(address messageSender) external onlyFromAuthenticatedVeaSender(messageSender) { - _receiveMessage(); + modifier internalCall() { + require(msg.sender == address(this), "Internal call only."); + _; } /// Receive the message from the sender gateway. - function receiveMessage( - address messageSender, - uint256 _data - ) external onlyFromAuthenticatedVeaSender(messageSender) { - _receiveMessage(_data); + function receiveMessage(address msgSender, bytes calldata data) external override onlyFromVeaBridge(msgSender) { + // Internal call to this contract with the provided data + (bool success, ) = address(this).call(data); + require(success, "Internal call failed"); } - function _receiveMessage() internal { + /// @dev Only callable via internal call from receiveMessage + function digestMessage(uint256 _data) external override internalCall { messageCount++; + data = _data; } - function _receiveMessage(uint256 _data) internal { + /// @dev Only callable via internal call from receiveMessage + function digestMessageArray(uint256[] calldata _data) external override internalCall { messageCount++; - data = _data; + dataArray = _data; } } diff --git a/contracts/src/test/gateways/SenderGatewayMock.sol b/contracts/src/test/gateways/SenderGatewayMock.sol index 36e7961a..d140d797 100644 --- a/contracts/src/test/gateways/SenderGatewayMock.sol +++ b/contracts/src/test/gateways/SenderGatewayMock.sol @@ -8,7 +8,7 @@ pragma solidity ^0.8.24; -import "./IReceiverGatewayMock.sol"; +import "./ReceiverGatewayMock.sol"; import "../../interfaces/gateways/ISenderGateway.sol"; /// Sender Gateway @@ -23,8 +23,14 @@ contract SenderGatewayMock is ISenderGateway { } function sendMessage(uint256 _data) external { - bytes4 methodSelector = IReceiverGatewayMock.receiveMessage.selector; - bytes memory data = abi.encode(_data); - veaInbox.sendMessage(receiverGateway, methodSelector, data); + bytes4 methodSelector = ReceiverGatewayMock.digestMessage.selector; + bytes memory data = abi.encodeWithSelector(methodSelector, _data); + veaInbox.sendMessage(receiverGateway, data); + } + + function sendMessageArray(uint256[] calldata _data) external { + bytes4 methodSelector = ReceiverGatewayMock.digestMessageArray.selector; + bytes memory data = abi.encodeWithSelector(methodSelector, _data); + veaInbox.sendMessage(receiverGateway, data); } } diff --git a/contracts/src/utils/veaInboxTouch.sol b/contracts/src/utils/veaInboxTouch.sol index a073bcdd..71332284 100644 --- a/contracts/src/utils/veaInboxTouch.sol +++ b/contracts/src/utils/veaInboxTouch.sol @@ -20,7 +20,7 @@ contract VeaInboxTouch { } function touch(uint256 random) external payable { - veaInbox.sendMessage(0x0000000000000000000000000000000000000000, 0x00000000, abi.encode(random)); + veaInbox.sendMessage(0x0000000000000000000000000000000000000000, abi.encode(random)); veaInbox.saveSnapshot(); } } diff --git a/contracts/test/integration/ArbToEth.ts b/contracts/test/integration/ArbToEth.ts index b308a0bc..33d0c1a5 100644 --- a/contracts/test/integration/ArbToEth.ts +++ b/contracts/test/integration/ArbToEth.ts @@ -225,7 +225,7 @@ describe("Integration tests", async () => { const blocksToMine = Math.ceil(minChallengePeriod / 12); await mine(blocksToMine); - const verifySnapshotTxn = await veaOutbox.connect(bridger).verifySnapshot(epoch, { + await veaOutbox.connect(bridger).verifySnapshot(epoch, { stateRoot: batchMerkleRoot, claimer: bridger.address, timestampClaimed: blockClaim.timestamp, @@ -242,18 +242,14 @@ describe("Integration tests", async () => { // sending sample data through the fast bridge const data = 1121; const sendMessagetx = await senderGateway.sendMessage(data); - //const inboxsnapshot = await veaInbox.inbox(0); - const sendMessagetx2 = await senderGateway.sendMessage(data); - //const inboxsnapshot2 = await veaInbox.inbox(0); + await senderGateway.sendMessage(data); await expect(sendMessagetx).to.emit(veaInbox, "MessageSent"); const MessageSent = veaInbox.filters.MessageSent(); const MessageSentEvent = await veaInbox.queryFilter(MessageSent); const msg = MessageSentEvent[0].args._nodeData; - const nonce = "0x" + msg.slice(2, 18); - const to = "0x" + msg.slice(18, 58); //18+40 - const msgData = "0x" + msg.slice(58); + const { nonce, to, from, msgData } = decodeMessage(msg); const msg2 = MessageSentEvent[1].args._nodeData; @@ -261,12 +257,13 @@ describe("Integration tests", async () => { const nonce2 = "0x" + msg2.slice(2, 18); const to2 = "0x" + msg2.slice(18, 58); //18+40 - const msgData2 = "0x" + msg2.slice(58); + const from2 = "0x" + msg2.slice(58, 98); //58+40 + const msgData2 = "0x" + msg2.slice(98); - nodes.push(MerkleTree.makeLeafNode(nonce, to, msgData)); - nodes.push(MerkleTree.makeLeafNode(nonce2, to2, msgData2)); + nodes.push(MerkleTree.makeLeafNode(nonce, to, from, msgData)); + nodes.push(MerkleTree.makeLeafNode(nonce2, to2, from2, msgData2)); - const sendBatchTx = await veaInbox.connect(bridger).saveSnapshot(); + await veaInbox.connect(bridger).saveSnapshot(); const BatchOutgoing = veaInbox.filters.SnapshotSaved(); const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); @@ -275,57 +272,86 @@ describe("Integration tests", async () => { ); const batchMerkleRoot = await veaInbox.snapshots(epoch); // Honest Bridger - const epochPeriod = Number(await veaInbox.epochPeriod()); + await claimAndVerify({ + veaInbox, + veaOutbox, + bridger, + epoch, + batchMerkleRoot, + ethers, + network, + mine, + }); + const mt = new MerkleTree(nodes); + await expect(veaOutbox.connect(relayer).sendMessage([], nonce, to, from, msgData)).to.be.revertedWith( + "Invalid proof." + ); + const proof = mt.getHexProof(nodes[0]); - await network.provider.send("evm_increaseTime", [epochPeriod]); - await network.provider.send("evm_mine"); + const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, nonce, to, from, msgData); + await expect(verifyAndRelayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0); - // Honest Bridger - const bridgerClaimTx = await veaOutbox.connect(bridger).claim(epoch, batchMerkleRoot, { value: TEN_ETH }); - const blockClaim = await ethers.provider.getBlock(bridgerClaimTx.blockNumber!); - if (!blockClaim) return; - const maxL2StateSyncDelay = Number(await veaOutbox.sequencerDelayLimit()) + epochPeriod / 2; - await network.provider.send("evm_increaseTime", [epochPeriod + maxL2StateSyncDelay]); - await network.provider.send("evm_mine"); + await expect(veaOutbox.connect(relayer).sendMessage(proof, nonce, to, from, msgData)).to.be.revertedWith( + "Message already relayed" + ); + }); - const startValidationTxn = await veaOutbox.startVerification(epoch, { - stateRoot: batchMerkleRoot, - claimer: bridger.address, - timestampClaimed: blockClaim.timestamp, - timestampVerification: 0, - blocknumberVerification: 0, - honest: 0, - challenger: ethers.ZeroAddress, - }); - await expect(startValidationTxn).to.emit(veaOutbox, "VerificationStarted").withArgs(epoch); + it("should be able to verify and relay message with dynamic array", async () => { + // sending sample data through the fast bridge + const data = [1121, 1122, 1123, 1124, 1125]; + const sendMessagetx = await senderGateway.sendMessageArray(data); + //const inboxsnapshot = await veaInbox.inbox(0); - const blockStartValidation = await ethers.provider.getBlock(startValidationTxn.blockNumber!); - if (!blockStartValidation) return; + await senderGateway.sendMessageArray(data); + //const inboxsnapshot2 = await veaInbox.inbox(0); + await expect(sendMessagetx).to.emit(veaInbox, "MessageSent"); + const MessageSent = veaInbox.filters.MessageSent(); + const MessageSentEvent = await veaInbox.queryFilter(MessageSent); + const msg = MessageSentEvent[0].args._nodeData; - const minChallengePeriod = Number(await veaOutbox.minChallengePeriod()); - await network.provider.send("evm_increaseTime", [minChallengePeriod]); - await network.provider.send("evm_mine"); - const blocksToMine = Math.ceil(minChallengePeriod / 12); - await mine(blocksToMine); + const { nonce, to, from, msgData } = decodeMessage(msg); - const verifySnapshotTxn = await veaOutbox.connect(bridger).verifySnapshot(epoch, { - stateRoot: batchMerkleRoot, - claimer: bridger.address, - timestampClaimed: blockClaim.timestamp, - timestampVerification: blockStartValidation.timestamp!, - blocknumberVerification: startValidationTxn.blockNumber!, - honest: 0, - challenger: ethers.ZeroAddress, - }); + const msg2 = MessageSentEvent[1].args._nodeData; + + let nodes: string[] = []; + + const nonce2 = "0x" + msg2.slice(2, 18); + const to2 = "0x" + msg2.slice(18, 58); //18+40 + const from2 = "0x" + msg2.slice(58, 98); //58+40 + const msgData2 = "0x" + msg2.slice(98); + + nodes.push(MerkleTree.makeLeafNode(nonce, to, from, msgData)); + nodes.push(MerkleTree.makeLeafNode(nonce2, to2, from2, msgData2)); + await veaInbox.connect(bridger).saveSnapshot(); + + const BatchOutgoing = veaInbox.filters.SnapshotSaved(); + const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); + const epoch = Math.floor( + (await batchOutGoingEvent[0].getBlock()).timestamp / Number(await veaInbox.epochPeriod()) + ); + const batchMerkleRoot = await veaInbox.snapshots(epoch); + // Honest Bridger + await claimAndVerify({ + veaInbox, + veaOutbox, + bridger, + epoch, + batchMerkleRoot, + ethers, + network, + mine, + }); const mt = new MerkleTree(nodes); - await expect(veaOutbox.connect(relayer).sendMessage([], nonce, to, msgData)).to.be.revertedWith("Invalid proof."); + await expect(veaOutbox.connect(relayer).sendMessage([], nonce, to, from, msgData)).to.be.revertedWith( + "Invalid proof." + ); const proof = mt.getHexProof(nodes[0]); - const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, nonce, to, msgData); + const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, nonce, to, from, msgData); await expect(verifyAndRelayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0); - await expect(veaOutbox.connect(relayer).sendMessage(proof, nonce, to, msgData)).to.be.revertedWith( + await expect(veaOutbox.connect(relayer).sendMessage(proof, nonce, to, from, msgData)).to.be.revertedWith( "Message already relayed" ); }); @@ -339,17 +365,15 @@ describe("Integration tests", async () => { const MessageSent = veaInbox.filters.MessageSent(); const MessageSentEvent = await veaInbox.queryFilter(MessageSent); const msg = MessageSentEvent[0].args._nodeData; - const nonce = "0x" + msg.slice(2, 18); - const to = "0x" + msg.slice(18, 58); //18+40 - const msgData = "0x" + msg.slice(58); + const { nonce, to, from, msgData } = decodeMessage(msg); let nodes: string[] = []; - nodes.push(MerkleTree.makeLeafNode(nonce, to, msgData)); + nodes.push(MerkleTree.makeLeafNode(nonce, to, from, msgData)); const mt = new MerkleTree(nodes); const proof = mt.getHexProof(nodes[nodes.length - 1]); - const sendBatchTx = await veaInbox.connect(bridger).saveSnapshot(); + await veaInbox.connect(bridger).saveSnapshot(); const BatchOutgoing = veaInbox.filters.SnapshotSaved(); const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); @@ -391,7 +415,7 @@ describe("Integration tests", async () => { const blocksToMine = Math.ceil(minChallengePeriod / 12); await mine(blocksToMine); - const verifySnapshotTxn = await veaOutbox.connect(bridger).verifySnapshot(epoch, { + await veaOutbox.connect(bridger).verifySnapshot(epoch, { stateRoot: batchMerkleRoot, claimer: bridger.address, timestampClaimed: blockClaim.timestamp, @@ -401,10 +425,10 @@ describe("Integration tests", async () => { challenger: ethers.ZeroAddress, }); - const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, 0, to, msgData); + const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, 0, to, from, msgData); await expect(verifyAndRelayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0); - const withdrawClaimDepositTx = await veaOutbox.connect(bridger).withdrawClaimDeposit(epoch, { + await veaOutbox.connect(bridger).withdrawClaimDeposit(epoch, { stateRoot: batchMerkleRoot, claimer: bridger.address, timestampClaimed: blockClaim.timestamp, @@ -424,17 +448,15 @@ describe("Integration tests", async () => { const MessageSent = veaInbox.filters.MessageSent(); const MessageSentEvent = await veaInbox.queryFilter(MessageSent); const msg = MessageSentEvent[0].args._nodeData; - const nonce = "0x" + msg.slice(2, 18); - const to = "0x" + msg.slice(18, 58); //18+40 - const msgData = "0x" + msg.slice(58); + const { nonce, to, from, msgData } = decodeMessage(msg); let nodes: string[] = []; - nodes.push(MerkleTree.makeLeafNode(nonce, to, msgData)); + nodes.push(MerkleTree.makeLeafNode(nonce, to, from, msgData)); const mt = new MerkleTree(nodes); const proof = mt.getHexProof(nodes[nodes.length - 1]); - const sendBatchTx = await veaInbox.connect(bridger).saveSnapshot(); + await veaInbox.connect(bridger).saveSnapshot(); const BatchOutgoing = veaInbox.filters.SnapshotSaved(); const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); @@ -477,7 +499,7 @@ describe("Integration tests", async () => { const blocksToMine = Math.ceil(minChallengePeriod / 12); await mine(blocksToMine); - const verifySnapshotTxn = await veaOutbox.connect(bridger).verifySnapshot(epoch, { + await veaOutbox.connect(bridger).verifySnapshot(epoch, { stateRoot: batchMerkleRoot, claimer: bridger.address, timestampClaimed: blockClaim.timestamp, @@ -487,10 +509,10 @@ describe("Integration tests", async () => { challenger: ethers.ZeroAddress, }); - const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, 0, to, msgData); + const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, 0, to, from, msgData); await expect(verifyAndRelayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0); - const withdrawClaimDepositTx = await veaOutbox.withdrawClaimDeposit(epoch, { + await veaOutbox.withdrawClaimDeposit(epoch, { stateRoot: batchMerkleRoot, claimer: bridger.address, timestampClaimed: blockClaim.timestamp, @@ -508,8 +530,8 @@ describe("Integration tests", async () => { it("should be able to challenge", async () => { const data = 1121; - const sendMessagetx = await senderGateway.sendMessage(data); - const sendBatchTx = await veaInbox.connect(bridger).saveSnapshot(); + await senderGateway.sendMessage(data); + await veaInbox.connect(bridger).saveSnapshot(); const BatchOutgoing = veaInbox.filters.SnapshotSaved(); const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); @@ -547,8 +569,8 @@ describe("Integration tests", async () => { it("should be able to fallback to send safe", async () => { const data = 1121; - const sendMessagetx = await senderGateway.sendMessage(data); - const sendBatchTx = await veaInbox.connect(bridger).saveSnapshot(); + await senderGateway.sendMessage(data); + await veaInbox.connect(bridger).saveSnapshot(); const BatchOutgoing = veaInbox.filters.SnapshotSaved(); const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); @@ -564,21 +586,19 @@ describe("Integration tests", async () => { const block = await ethers.provider.getBlock(bridgerClaimTx.blockNumber!); if (!block) return; - const challengeTx = await veaOutbox - .connect(challenger) - ["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( - epoch, - { - stateRoot: batchMerkleRoot, - claimer: bridger.address, - timestampClaimed: block.timestamp, - timestampVerification: 0, - blocknumberVerification: 0, - honest: 0, - challenger: ethers.ZeroAddress, - }, - { value: TEN_ETH } - ); + await veaOutbox.connect(challenger)["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( + epoch, + { + stateRoot: batchMerkleRoot, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: 0, + blocknumberVerification: 0, + honest: 0, + challenger: ethers.ZeroAddress, + }, + { value: TEN_ETH } + ); const sendSafeFallbackTx = await veaInbox.connect(bridger).sendSnapshot( epoch, @@ -607,17 +627,15 @@ describe("Integration tests", async () => { const MessageSent = veaInbox.filters.MessageSent(); const MessageSentEvent = await veaInbox.queryFilter(MessageSent); const msg = MessageSentEvent[0].args._nodeData; - const nonce = "0x" + msg.slice(2, 18); - const to = "0x" + msg.slice(18, 58); //18+40 - const msgData = "0x" + msg.slice(58); + const { nonce, to, from, msgData } = decodeMessage(msg); let nodes: string[] = []; - nodes.push(MerkleTree.makeLeafNode(nonce, to, msgData)); + nodes.push(MerkleTree.makeLeafNode(nonce, to, from, msgData)); const mt = new MerkleTree(nodes); const proof = mt.getHexProof(nodes[nodes.length - 1]); - const sendBatchTx = await veaInbox.connect(bridger).saveSnapshot(); + await veaInbox.connect(bridger).saveSnapshot(); const BatchOutgoing = veaInbox.filters.SnapshotSaved(); const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); @@ -699,8 +717,7 @@ describe("Integration tests", async () => { }) ).revertedWith("Claim is challenged."); - //expect(await (await veaOutbox.challenges(epoch)).honest).to.equal(false); - const sendSafeFallbackTx = await veaInbox.connect(bridger).sendSnapshot( + await veaInbox.connect(bridger).sendSnapshot( epoch, { stateRoot: batchMerkleRoot, @@ -713,9 +730,8 @@ describe("Integration tests", async () => { }, { gasLimit: 1000000 } ); - //expect(await (await veaOutbox.challenges(epoch)).honest).to.equal(false); - //expect(await (await veaOutbox.claims(epoch)).honest).to.equal(true); - const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, 0, to, msgData); + + const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, 0, to, from, msgData); await expect(verifyAndRelayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0); await expect( veaOutbox.withdrawChallengeDeposit(epoch, { @@ -729,7 +745,7 @@ describe("Integration tests", async () => { }) ).to.be.revertedWith("Challenge failed."); - const withdrawClaimDepositTx = await veaOutbox.withdrawClaimDeposit(epoch, { + await veaOutbox.withdrawClaimDeposit(epoch, { stateRoot: batchMerkleRoot, claimer: bridger.address, timestampClaimed: block.timestamp, @@ -750,17 +766,15 @@ describe("Integration tests", async () => { const MessageSent = veaInbox.filters.MessageSent(); const MessageSentEvent = await veaInbox.queryFilter(MessageSent); const msg = MessageSentEvent[0].args._nodeData; - const nonce = "0x" + msg.slice(2, 18); - const to = "0x" + msg.slice(18, 58); //18+40 - const msgData = "0x" + msg.slice(58); + const { nonce, to, from, msgData } = decodeMessage(msg); let nodes: string[] = []; - nodes.push(MerkleTree.makeLeafNode(nonce, to, msgData)); + nodes.push(MerkleTree.makeLeafNode(nonce, to, from, msgData)); const mt = new MerkleTree(nodes); const proof = mt.getHexProof(nodes[nodes.length - 1]); - const sendBatchTx = await veaInbox.connect(bridger).saveSnapshot(); + await veaInbox.connect(bridger).saveSnapshot(); const BatchOutgoing = veaInbox.filters.SnapshotSaved(); const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); @@ -805,21 +819,19 @@ describe("Integration tests", async () => { await mine(blocksToMine); // Challenger tx starts - const challengeTx = await veaOutbox - .connect(challenger) - ["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( - epoch, - { - stateRoot: fakeHash, - claimer: bridger.address, - timestampClaimed: block.timestamp, - timestampVerification: blockStartValidation.timestamp!, - blocknumberVerification: startValidationTxn.blockNumber!, - honest: 0, - challenger: ethers.ZeroAddress, - }, - { value: TEN_ETH } - ); + await veaOutbox.connect(challenger)["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( + epoch, + { + stateRoot: fakeHash, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: blockStartValidation.timestamp!, + blocknumberVerification: startValidationTxn.blockNumber!, + honest: 0, + challenger: ethers.ZeroAddress, + }, + { value: TEN_ETH } + ); await expect( veaOutbox.connect(relayer).verifySnapshot(epoch, { @@ -834,7 +846,7 @@ describe("Integration tests", async () => { ).to.revertedWith("Claim is challenged."); // sendSafeFallback internally calls the verifySafeBatch - const sendSafeFallbackTx = await veaInbox.connect(bridger).sendSnapshot( + await veaInbox.connect(bridger).sendSnapshot( epoch, { stateRoot: fakeHash, @@ -847,7 +859,7 @@ describe("Integration tests", async () => { }, { gasLimit: 1000000 } ); - const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, 0, to, msgData); + const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, 0, to, from, msgData); await expect(verifyAndRelayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0); expect( veaOutbox.connect(relayer).withdrawClaimDeposit(epoch, { @@ -882,17 +894,15 @@ describe("Integration tests", async () => { const MessageSent = veaInbox.filters.MessageSent(); const MessageSentEvent = await veaInbox.queryFilter(MessageSent); const msg = MessageSentEvent[0].args._nodeData; - const nonce = "0x" + msg.slice(2, 18); - const to = "0x" + msg.slice(18, 58); //18+40 - const msgData = "0x" + msg.slice(58); + const { nonce, to, from, msgData } = decodeMessage(msg); let nodes: string[] = []; - nodes.push(MerkleTree.makeLeafNode(nonce, to, msgData)); + nodes.push(MerkleTree.makeLeafNode(nonce, to, from, msgData)); const mt = new MerkleTree(nodes); const proof = mt.getHexProof(nodes[nodes.length - 1]); - const sendBatchTx = await veaInbox.connect(bridger).saveSnapshot(); + await veaInbox.connect(bridger).saveSnapshot(); const BatchOutgoing = veaInbox.filters.SnapshotSaved(); const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); @@ -937,21 +947,19 @@ describe("Integration tests", async () => { await mine(blocksToMine); // Challenger tx starts - const challengeTx = await veaOutbox - .connect(challenger) - ["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( - epoch, - { - stateRoot: fakeHash, - claimer: bridger.address, - timestampClaimed: block.timestamp, - timestampVerification: blockStartValidation.timestamp!, - blocknumberVerification: startValidationTxn.blockNumber!, - honest: 0, - challenger: ethers.ZeroAddress, - }, - { value: TEN_ETH } - ); + await veaOutbox.connect(challenger)["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( + epoch, + { + stateRoot: fakeHash, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: blockStartValidation.timestamp!, + blocknumberVerification: startValidationTxn.blockNumber!, + honest: 0, + challenger: ethers.ZeroAddress, + }, + { value: TEN_ETH } + ); await expect( veaOutbox.connect(relayer).verifySnapshot(epoch, { @@ -966,7 +974,7 @@ describe("Integration tests", async () => { ).to.revertedWith("Claim is challenged."); // sendSafeFallback internally calls the verifySafeBatch - const sendSafeFallbackTx = await veaInbox.connect(bridger).sendSnapshot( + await veaInbox.connect(bridger).sendSnapshot( epoch, { stateRoot: fakeHash, @@ -992,7 +1000,7 @@ describe("Integration tests", async () => { const sendMessagetx = await senderGateway.sendMessage(data); await expect(sendMessagetx).to.emit(veaInbox, "MessageSent"); - const sendBatchTx = await veaInbox.connect(bridger).saveSnapshot(); + await veaInbox.connect(bridger).saveSnapshot(); const BatchOutgoing = veaInbox.filters.SnapshotSaved(); const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); @@ -1037,21 +1045,19 @@ describe("Integration tests", async () => { await mine(blocksToMine); // Challenger tx starts - const challengeTx = await veaOutbox - .connect(challenger) - ["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( - epoch, - { - stateRoot: fakeHash, - claimer: bridger.address, - timestampClaimed: block.timestamp, - timestampVerification: blockStartValidation.timestamp!, - blocknumberVerification: startValidationTxn.blockNumber!, - honest: 0, - challenger: ethers.ZeroAddress, - }, - { value: TEN_ETH } - ); + await veaOutbox.connect(challenger)["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( + epoch, + { + stateRoot: fakeHash, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: blockStartValidation.timestamp!, + blocknumberVerification: startValidationTxn.blockNumber!, + honest: 0, + challenger: ethers.ZeroAddress, + }, + { value: TEN_ETH } + ); // 2nd message at new epoch const epoch2 = await veaOutbox.epochNow(); @@ -1082,7 +1088,7 @@ describe("Integration tests", async () => { await network.provider.send("evm_mine"); await mine(blocksToMine); - const verifySnapshotTxn = await veaOutbox.connect(bridger).verifySnapshot(epoch2, { + await veaOutbox.connect(bridger).verifySnapshot(epoch2, { stateRoot: stateRoot2, claimer: bridger.address, timestampClaimed: claimTxn2Block.timestamp, @@ -1093,7 +1099,7 @@ describe("Integration tests", async () => { }); // Resolve dispute - const sendSafeFallbackTx = await veaInbox.connect(bridger).sendSnapshot( + await veaInbox.connect(bridger).sendSnapshot( epoch, { stateRoot: fakeHash, @@ -1117,3 +1123,75 @@ describe("Integration tests", async () => { }); }); }); + +// Utility function for claiming and verifying a batch +async function claimAndVerify({ + veaInbox, + veaOutbox, + bridger, + epoch, + batchMerkleRoot, + ethers, + network, + mine, +}: { + veaInbox: any; + veaOutbox: any; + bridger: any; + epoch: number; + batchMerkleRoot: string; + ethers: any; + network: any; + mine: (blocks: number) => Promise; +}) { + const epochPeriod = Number(await veaInbox.epochPeriod()); + + await network.provider.send("evm_increaseTime", [epochPeriod]); + await network.provider.send("evm_mine"); + + // Honest Bridger + const bridgerClaimTx = await veaOutbox.connect(bridger).claim(epoch, batchMerkleRoot, { value: TEN_ETH }); + const blockClaim = await ethers.provider.getBlock(bridgerClaimTx.blockNumber!); + if (!blockClaim) return; + const maxL2StateSyncDelay = Number(await veaOutbox.sequencerDelayLimit()) + epochPeriod / 2; + await network.provider.send("evm_increaseTime", [epochPeriod + maxL2StateSyncDelay]); + await network.provider.send("evm_mine"); + + const startValidationTxn = await veaOutbox.startVerification(epoch, { + stateRoot: batchMerkleRoot, + claimer: bridger.address, + timestampClaimed: blockClaim.timestamp, + timestampVerification: 0, + blocknumberVerification: 0, + honest: 0, + challenger: ethers.ZeroAddress, + }); + await expect(startValidationTxn).to.emit(veaOutbox, "VerificationStarted").withArgs(epoch); + + const blockStartValidation = await ethers.provider.getBlock(startValidationTxn.blockNumber!); + if (!blockStartValidation) return; + + const minChallengePeriod = Number(await veaOutbox.minChallengePeriod()); + await network.provider.send("evm_increaseTime", [minChallengePeriod]); + await network.provider.send("evm_mine"); + const blocksToMine = Math.ceil(minChallengePeriod / 12); + await mine(blocksToMine); + + const verifySnapshotTxn = await veaOutbox.connect(bridger).verifySnapshot(epoch, { + stateRoot: batchMerkleRoot, + claimer: bridger.address, + timestampClaimed: blockClaim.timestamp, + timestampVerification: blockStartValidation.timestamp!, + blocknumberVerification: startValidationTxn.blockNumber!, + honest: 0, + challenger: ethers.ZeroAddress, + }); +} + +function decodeMessage(msg: any) { + const nonce = "0x" + msg.slice(2, 18); + const to = "0x" + msg.slice(18, 58); //18+40 + const from = "0x" + msg.slice(58, 98); //58+40 + const msgData = "0x" + msg.slice(98); + return { nonce, to, from, msgData }; +} diff --git a/contracts/test/integration/ArbToGnosis.ts b/contracts/test/integration/ArbToGnosis.ts index fb0b320d..a650e348 100644 --- a/contracts/test/integration/ArbToGnosis.ts +++ b/contracts/test/integration/ArbToGnosis.ts @@ -15,7 +15,6 @@ import { BridgeMock, VeaInboxArbToGnosisMock, } from "../../typechain-types"; -import { bigint } from "hardhat/internal/core/params/argumentTypes"; import { Block } from "ethers"; // Constants @@ -249,19 +248,17 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { it("should relay message after verification", async () => { // Setup - const data = 1121; - const sendMessageTx = await senderGateway.connect(sender).sendMessage(data); + const data = [1121, 1122, 1123, 1124, 1125]; + await senderGateway.connect(sender).sendMessageArray(data); await veaInbox.connect(bridger).saveSnapshot(); const MessageSent = veaInbox.filters.MessageSent(); const MessageSentEvent = await veaInbox.queryFilter(MessageSent); const msg = MessageSentEvent[0].args._nodeData; - const nonce = "0x" + msg.slice(2, 18); - const to = "0x" + msg.slice(18, 58); - const msgData = "0x" + msg.slice(58); + const { nonce, to, from, msgData } = await decodeMessage(msg); let nodes: string[] = []; - nodes.push(MerkleTree.makeLeafNode(nonce, to, msgData)); + nodes.push(MerkleTree.makeLeafNode(nonce, to, from, msgData)); const mt = new MerkleTree(nodes); const proof = mt.getHexProof(nodes[0]); @@ -302,11 +299,11 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { }); // Relay message - const relayTx = await veaOutbox.connect(receiver).sendMessage(proof, nonce, to, msgData); + const relayTx = await veaOutbox.connect(receiver).sendMessage(proof, nonce, to, from, msgData); await expect(relayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0); // Ensure message can't be relayed twice - await expect(veaOutbox.connect(receiver).sendMessage(proof, nonce, to, msgData)).to.be.revertedWith( + await expect(veaOutbox.connect(receiver).sendMessage(proof, nonce, to, from, msgData)).to.be.revertedWith( "Message already relayed" ); }); @@ -395,7 +392,7 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { }); it("should allow challenger to submit a challenge", async () => { - const { claimBlock, challengeTx } = await setupClaimAndChallenge(epoch, batchMerkleRoot, 0); + const { challengeTx } = await setupClaimAndChallenge(epoch, batchMerkleRoot, 0); await expect(challengeTx).to.emit(veaOutbox, "Challenged").withArgs(epoch, challenger.address); }); @@ -637,16 +634,13 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { const MessageSent = veaInbox.filters.MessageSent(); const MessageSentEvent = await veaInbox.queryFilter(MessageSent); const msg = MessageSentEvent[0].args._nodeData; - const nonce = "0x" + msg.slice(2, 18); - const to = "0x" + msg.slice(18, 58); - const msgData = "0x" + msg.slice(58); + const { nonce, to, from, msgData } = await decodeMessage(msg); let nodes: string[] = []; - nodes.push(MerkleTree.makeLeafNode(nonce, to, msgData)); + nodes.push(MerkleTree.makeLeafNode(nonce, to, from, msgData)); const mt = new MerkleTree(nodes); const proof = mt.getHexProof(nodes[0]); - - const relayTx = await veaOutbox.connect(receiver).sendMessage(proof, 0, receiverGateway.target, msgData); + const relayTx = await veaOutbox.connect(receiver).sendMessage(proof, 0, receiverGateway.target, from, msgData); await expect(relayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0); }); }); @@ -681,7 +675,7 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { }); it("should allow challenger to submit a challenge to a dishonest claim", async () => { - const { claimBlock, challengeTx } = await setupClaimAndChallenge(epoch, dishonestMerkleRoot, 0); + const { challengeTx } = await setupClaimAndChallenge(epoch, dishonestMerkleRoot, 0); await expect(challengeTx).to.emit(veaOutbox, "Challenged").withArgs(epoch, challenger.address); }); @@ -815,16 +809,13 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { const MessageSent = veaInbox.filters.MessageSent(); const MessageSentEvent = await veaInbox.queryFilter(MessageSent); const msg = MessageSentEvent[0].args._nodeData; - const nonce = "0x" + msg.slice(2, 18); - const to = "0x" + msg.slice(18, 58); - const msgData = "0x" + msg.slice(58); + const { nonce, to, from, msgData } = await decodeMessage(msg); let nodes: string[] = []; - nodes.push(MerkleTree.makeLeafNode(nonce, to, msgData)); + nodes.push(MerkleTree.makeLeafNode(nonce, to, from, msgData)); const mt = new MerkleTree(nodes); const proof = mt.getHexProof(nodes[0]); - - const relayTx = await veaOutbox.connect(receiver).sendMessage(proof, 0, receiverGateway.target, msgData); + const relayTx = await veaOutbox.connect(receiver).sendMessage(proof, 0, receiverGateway.target, from, msgData); await expect(relayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0); }); @@ -964,3 +955,11 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { }); }); }); + +async function decodeMessage(msg: any) { + const nonce = "0x" + msg.slice(2, 18); + const to = "0x" + msg.slice(18, 58); //18+40 + const from = "0x" + msg.slice(58, 98); //58+40 + const msgData = "0x" + msg.slice(98); + return { nonce, to, from, msgData }; +}