diff --git a/contracts/src/arbitrumToEth/VeaInboxArbToEth.sol b/contracts/src/arbitrumToEth/VeaInboxArbToEth.sol index 38473644..00c6a046 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 calldata, abi.encodeWithSelector(fnSelector, param1, param2, ...) /// @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..52b3e88d 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: [] @@ -41,6 +41,7 @@ contract VeaOutboxArbToEth is IVeaOutboxOnL1 { mapping(uint256 epoch => bytes32) public claimHashes; // epoch => claim mapping(uint256 messageId => bytes32) internal relayed; // msgId/256 => packed replay bitmap, preferred over a simple boolean mapping to save 15k gas per message + mapping(address => mapping(address => bool)) public allowlist; // to => from => allowed, Enforces allowed sender addresses for a receiver contract. Address(0) allowing all senders. uint256 public sequencerDelayLimit; // This is MaxTimeVariation.delaySeconds from the arbitrum sequencer inbox, it is the maximum seconds the sequencer can backdate L2 txns relative to the L1 clock. SequencerDelayLimitDecreaseRequest public sequencerDelayLimitDecreaseRequest; // Decreasing the sequencerDelayLimit requires a delay to avoid griefing by sequencer, so we keep track of the request here. @@ -351,15 +352,32 @@ contract VeaOutboxArbToEth is IVeaOutboxOnL1 { emit Verified(_epoch); } + /// @dev Sets the allowlist for the sender gateway. + /// Note: Address(0) is used to allow all addresses. + /// @param _from The address to allow or disallow + /// @param _allowed Whether to allow or disallow the address. + function setAllowlist(address _from, bool _allowed) external { + allowlist[msg.sender][_from] = _allowed; + } + /// @dev Verifies and relays the message. UNTRUSTED. /// @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 as abi.encodeWithSelector(fnSelector, param1, param2, ...) + function sendMessage( + bytes32[] calldata _proof, + uint64 _msgId, + address _to, + address _from, + bytes calldata _message + ) external { require(_proof.length < 64, "Proof too long."); + bool isAllowed = allowlist[_to][_from] || allowlist[_to][address(0)]; + require(isAllowed, "Message sender not allowed to call receiver."); - 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 diff --git a/contracts/src/arbitrumToGnosis/VeaInboxArbToGnosis.sol b/contracts/src/arbitrumToGnosis/VeaInboxArbToGnosis.sol index edf4a194..5f545a43 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: [] @@ -70,26 +70,16 @@ contract VeaInboxArbToGnosis 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 calldata, abi.encodeWithSelector(fnSelector, param1, param2, ...) /// @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..6ef3a1cd 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: [] @@ -42,6 +42,7 @@ contract VeaOutboxArbToGnosis is IVeaOutboxOnL1, ISequencerDelayUpdatable { mapping(uint256 epoch => bytes32) public claimHashes; // epoch => claim mapping(uint256 messageId => bytes32) internal relayed; // msgId/256 => packed replay bitmap, preferred over a simple boolean mapping to save 15k gas per message + mapping(address => mapping(address => bool)) public allowlist; // to => from => allowed, Enforces allowed sender addresses for a receiver contract. Address(0) allowing all senders. uint256 public sequencerDelayLimit; // This is MaxTimeVariation.delaySeconds from the arbitrum sequencer inbox, it is the maximum seconds the sequencer can backdate L2 txns relative to the L1 clock. uint256 public timestampDelayUpdated; // The timestamp of the last sequencer delay update. @@ -295,15 +296,31 @@ contract VeaOutboxArbToGnosis is IVeaOutboxOnL1, ISequencerDelayUpdatable { emit Verified(_epoch); } + /// @dev Sets the allowlist for the sender gateway. + /// @param _from The address to allow or disallow + /// @param _allow Whether to allow or disallow the address. + function setAllowlist(address _from, bool _allow) external { + allowlist[msg.sender][_from] = _allow; + } + /// @dev Verifies and relays the message. UNTRUSTED. /// @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 as abi.encodeWithSelector(fnSelector, param1, param2, ...) + function sendMessage( + bytes32[] calldata _proof, + uint64 _msgId, + address _to, + address _from, + bytes calldata _message + ) external { require(_proof.length < 64, "Proof too long."); + bool isAllowed = allowlist[_to][_from] || allowlist[_to][address(0)]; + require(isAllowed, "Message sender not allowed to call receiver."); - 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 diff --git a/contracts/src/gnosisToArbitrum/VeaInboxGnosisToArb.sol b/contracts/src/gnosisToArbitrum/VeaInboxGnosisToArb.sol index 36abf3c1..74e92830 100644 --- a/contracts/src/gnosisToArbitrum/VeaInboxGnosisToArb.sol +++ b/contracts/src/gnosisToArbitrum/VeaInboxGnosisToArb.sol @@ -71,26 +71,16 @@ contract VeaInboxGnosisToArb 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 calldata, abi.encodeWithSelector(fnSelector, param1, param2, ...) /// @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..291449da 100644 --- a/contracts/src/gnosisToArbitrum/VeaOutboxGnosisToArb.sol +++ b/contracts/src/gnosisToArbitrum/VeaOutboxGnosisToArb.sol @@ -41,6 +41,7 @@ contract VeaOutboxGnosisToArb is IVeaOutboxOnL2 { mapping(uint256 epoch => Claim) public claims; // epoch => claim mapping(uint256 epoch => address) public challengers; // epoch => challenger mapping(uint256 messageId => bytes32) internal relayed; // msgId/256 => packed replay bitmap, preferred over a simple boolean mapping to save 15k gas per message + mapping(address => mapping(address => bool)) public allowlist; // to => from => allowed enum Party { None, @@ -281,15 +282,32 @@ contract VeaOutboxGnosisToArb is IVeaOutboxOnL2 { emit Verified(_epoch); } + /// @dev Sets the allowlist for the sender gateway. + /// Note: Address(0) is used to allow all addresses. + /// @param _from The address to allow or disallow + /// @param _allowed Whether to allow or disallow the address. + function setAllowlist(address _from, bool _allowed) external { + allowlist[msg.sender][_from] = _allowed; + } + /// @dev Verifies and relays the message. UNTRUSTED. /// @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 as abi.encodeWithSelector(fnSelector, param1, param2, ...) + function sendMessage( + bytes32[] memory _proof, + uint64 _msgId, + address _to, + address _from, + bytes memory _message + ) external { require(_proof.length < 64, "Proof too long."); + bool isAllowed = allowlist[_to][_from] || allowlist[_to][address(0)]; + require(isAllowed, "Message sender not allowed to call receiver."); - 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 diff --git a/contracts/src/interfaces/inboxes/IVeaInbox.sol b/contracts/src/interfaces/inboxes/IVeaInbox.sol index e5375b07..349154a2 100644 --- a/contracts/src/interfaces/inboxes/IVeaInbox.sol +++ b/contracts/src/interfaces/inboxes/IVeaInbox.sol @@ -12,10 +12,9 @@ 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 _data The message calldata, abi.encodeWithSelector(...) /// @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..ce9311ed 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. @@ -26,4 +33,10 @@ interface IVeaOutboxOnL1 { /// @param _stateRoot The true state root for the epoch. /// @param _claim The claim associated with the epoch. function resolveDisputedClaim(uint256 _epoch, bytes32 _stateRoot, Claim memory _claim) external; + + /// @dev Sets the allowlist for the sender gateway. + /// Note: Address(0) is used to allow all addresses. + /// @param _from The address to allow or disallow + /// @param _allow Whether to allow or disallow the address. + function setAllowlist(address _from, bool _allow) external; } diff --git a/contracts/src/interfaces/outboxes/IVeaOutboxOnL2.sol b/contracts/src/interfaces/outboxes/IVeaOutboxOnL2.sol index ed6a824c..d13b8f51 100644 --- a/contracts/src/interfaces/outboxes/IVeaOutboxOnL2.sol +++ b/contracts/src/interfaces/outboxes/IVeaOutboxOnL2.sol @@ -15,12 +15,25 @@ 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. /// @param _epoch The epoch to verify. /// @param _stateRoot The true state root for the epoch. function resolveDisputedClaim(uint256 _epoch, bytes32 _stateRoot) external; + + /// @dev Sets the allowlist for the sender gateway. + /// Note: Address(0) is used to allow all addresses. + /// @param _from The address to allow or disallow + /// @param _allow Whether to allow or disallow the address. + function setAllowlist(address _from, bool _allow) external; } diff --git a/contracts/src/test/gateways/IReceiverGatewayMock.sol b/contracts/src/test/gateways/IReceiverGatewayMock.sol index 27cb449d..231a9c6b 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 receiveMessage(uint256 _data) external; + + /// Receive the message array from the sender gateway. + function receiveMessageArray(uint256[] calldata _data) external; } diff --git a/contracts/src/test/gateways/ReceiverGatewayMock.sol b/contracts/src/test/gateways/ReceiverGatewayMock.sol index 23fd4d91..6dd2653b 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,31 +19,36 @@ 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() { require(veaOutbox == msg.sender, "Vea Bridge only."); - require(messageSender == senderGateway, "Only the sender gateway is allowed."); _; } - /// Receive the message from the sender gateway. - function receiveMessage(address messageSender) external onlyFromAuthenticatedVeaSender(messageSender) { - _receiveMessage(); + function allowlistSender(bool _allowed) external { + IVeaOutboxOnL1(veaOutbox).setAllowlist(senderGateway, _allowed); + } + + function allowlistAllSender(bool _allowed) external { + IVeaOutboxOnL1(veaOutbox).setAllowlist(address(0), _allowed); } /// Receive the message from the sender gateway. - function receiveMessage( - address messageSender, - uint256 _data - ) external onlyFromAuthenticatedVeaSender(messageSender) { + function receiveMessage(uint256 _data) external onlyFromVeaBridge { _receiveMessage(_data); } + function receiveMessageArray(uint256[] calldata _data) external onlyFromVeaBridge { + _receiveMessage(); + dataArray = _data; + } + function _receiveMessage() internal { messageCount++; } diff --git a/contracts/src/test/gateways/SenderGatewayMock.sol b/contracts/src/test/gateways/SenderGatewayMock.sol index 36e7961a..64beb6ce 100644 --- a/contracts/src/test/gateways/SenderGatewayMock.sol +++ b/contracts/src/test/gateways/SenderGatewayMock.sol @@ -24,7 +24,13 @@ 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); + bytes memory data = abi.encodeWithSelector(methodSelector, _data); + veaInbox.sendMessage(receiverGateway, data); + } + + function sendMessageArray(uint256[] calldata _data) external { + bytes4 methodSelector = IReceiverGatewayMock.receiveMessageArray.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..39d99706 100644 --- a/contracts/test/integration/ArbToEth.ts +++ b/contracts/test/integration/ArbToEth.ts @@ -53,6 +53,7 @@ describe("Integration tests", async () => { senderGateway = (await ethers.getContract("SenderGateway")) as SenderGatewayMock; bridge = (await ethers.getContract("BridgeMock")) as BridgeMock; arbsysMock = (await ethers.getContract("ArbSysMock")) as ArbSysMock; + await receiverGateway.allowlistSender(true); }); it("should initialize contracts correctly", async () => { @@ -225,7 +226,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 +243,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 +258,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,61 +273,202 @@ 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 but not relay message", async () => { + // Disabling the allowlist for the sender enable in beforeEach hook (default is false) + await receiverGateway.allowlistSender(false); + // sending sample data through the fast bridge + const data = 1121; + const sendMessagetx = await senderGateway.sendMessage(data); - const blockStartValidation = await ethers.provider.getBlock(startValidationTxn.blockNumber!); - if (!blockStartValidation) return; + 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 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."); const proof = mt.getHexProof(nodes[0]); - const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, nonce, to, msgData); + await expect(veaOutbox.connect(relayer).sendMessage(proof, nonce, to, from, msgData)).to.be.revertedWith( + "Message sender not allowed to call receiver." + ); + }); + + 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); + + 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 { nonce, to, from, msgData } = decodeMessage(msg); + + 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, from, msgData)).to.be.revertedWith( + "Invalid proof." + ); + const proof = mt.getHexProof(nodes[0]); + + 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" ); }); + it("should be able to verify and relay with global allowance", async () => { + // Disabling the allowlist for the sender enable in beforeEach hook (default is false) + await receiverGateway.allowlistSender(false); + await receiverGateway.allowlistAllSender(true); + // sending sample data through the fast bridge + const data = 1121; + const sendMessagetx = await senderGateway.sendMessage(data); + + 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, to, from, msgData } = decodeMessage(msg); + + 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, from, msgData)).to.be.revertedWith( + "Invalid proof." + ); + const proof = mt.getHexProof(nodes[0]); + + const verifyAndRelayTx = await veaOutbox.connect(relayer).sendMessage(proof, nonce, to, from, msgData); + await expect(verifyAndRelayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0); + }); + it("should allow bridger to claim deposit", async () => { // sending sample data through the fast bridge const data = 1121; @@ -339,17 +478,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 +528,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 +538,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 +561,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 +612,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 +622,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 +643,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 +682,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 +699,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 +740,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 +830,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 +843,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 +858,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 +879,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 +932,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 +959,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 +972,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 +1007,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 +1060,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 +1087,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 +1113,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 +1158,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 +1201,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 +1212,7 @@ describe("Integration tests", async () => { }); // Resolve dispute - const sendSafeFallbackTx = await veaInbox.connect(bridger).sendSnapshot( + await veaInbox.connect(bridger).sendSnapshot( epoch, { stateRoot: fakeHash, @@ -1117,3 +1236,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..b32a63fa 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 @@ -248,20 +247,82 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { }); it("should relay message after verification", async () => { + // Setup + 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, to, from, msgData } = await decodeMessage(msg); + + let nodes: string[] = []; + nodes.push(MerkleTree.makeLeafNode(nonce, to, from, msgData)); + const mt = new MerkleTree(nodes); + const proof = mt.getHexProof(nodes[0]); + + const BatchOutgoing = veaInbox.filters.SnapshotSaved(); + const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); + const epoch = Math.floor((await batchOutGoingEvent[0].getBlock()).timestamp / EPOCH_PERIOD); + const batchMerkleRoot = await veaInbox.snapshots(epoch); + + // Advance time, make claim, and start verification + await network.provider.send("evm_increaseTime", [EPOCH_PERIOD]); + await network.provider.send("evm_mine"); + + await weth.connect(bridger).approve(veaOutbox.target, TEN_ETH); + const claimTx = await veaOutbox.connect(bridger).claim(epoch, batchMerkleRoot); + const block = await ethers.provider.getBlock(claimTx.blockNumber!); + if (!block) return; + + const sequencerDelayLimit = await veaOutbox.sequencerDelayLimit(); + const maxL2StateSyncDelay = sequencerDelayLimit + BigInt(EPOCH_PERIOD); + + await network.provider.send("evm_increaseTime", [Number(maxL2StateSyncDelay)]); + await network.provider.send("evm_mine"); + + const startVerificationTx = await veaOutbox.startVerification( + epoch, + createClaim(batchMerkleRoot, bridger.address, block.timestamp) + ); + const verificationBlock = await ethers.provider.getBlock(startVerificationTx.blockNumber!); + if (!verificationBlock) return; + + await network.provider.send("evm_increaseTime", [CHALLENGE_PERIOD]); + await mine(Math.ceil(CHALLENGE_PERIOD / 12)); + + await veaOutbox.connect(bridger).verifySnapshot(epoch, { + ...createClaim(batchMerkleRoot, bridger.address, block.timestamp), + timestampVerification: verificationBlock.timestamp, + blocknumberVerification: startVerificationTx.blockNumber!, + }); + + await receiverGateway.allowlistSender(true); + + // Relay message + 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, from, msgData)).to.be.revertedWith( + "Message already relayed" + ); + }); + + it("should not be able to relay after verification", async () => { // Setup const data = 1121; - const sendMessageTx = await senderGateway.connect(sender).sendMessage(data); + await senderGateway.connect(sender).sendMessage(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 +363,71 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { }); // Relay message - const relayTx = await veaOutbox.connect(receiver).sendMessage(proof, nonce, to, msgData); + await expect(veaOutbox.connect(receiver).sendMessage(proof, nonce, to, from, msgData)).to.be.revertedWith( + "Message sender not allowed to call receiver." + ); + }); + + it("should be able to relay with global allowance after verification", async () => { + // Setup + 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, to, from, msgData } = await decodeMessage(msg); + + let nodes: string[] = []; + nodes.push(MerkleTree.makeLeafNode(nonce, to, from, msgData)); + const mt = new MerkleTree(nodes); + const proof = mt.getHexProof(nodes[0]); + + const BatchOutgoing = veaInbox.filters.SnapshotSaved(); + const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); + const epoch = Math.floor((await batchOutGoingEvent[0].getBlock()).timestamp / EPOCH_PERIOD); + const batchMerkleRoot = await veaInbox.snapshots(epoch); + + // Advance time, make claim, and start verification + await network.provider.send("evm_increaseTime", [EPOCH_PERIOD]); + await network.provider.send("evm_mine"); + + await weth.connect(bridger).approve(veaOutbox.target, TEN_ETH); + const claimTx = await veaOutbox.connect(bridger).claim(epoch, batchMerkleRoot); + const block = await ethers.provider.getBlock(claimTx.blockNumber!); + if (!block) return; + + const sequencerDelayLimit = await veaOutbox.sequencerDelayLimit(); + const maxL2StateSyncDelay = sequencerDelayLimit + BigInt(EPOCH_PERIOD); + + await network.provider.send("evm_increaseTime", [Number(maxL2StateSyncDelay)]); + await network.provider.send("evm_mine"); + + const startVerificationTx = await veaOutbox.startVerification( + epoch, + createClaim(batchMerkleRoot, bridger.address, block.timestamp) + ); + const verificationBlock = await ethers.provider.getBlock(startVerificationTx.blockNumber!); + if (!verificationBlock) return; + + await network.provider.send("evm_increaseTime", [CHALLENGE_PERIOD]); + await mine(Math.ceil(CHALLENGE_PERIOD / 12)); + + await veaOutbox.connect(bridger).verifySnapshot(epoch, { + ...createClaim(batchMerkleRoot, bridger.address, block.timestamp), + timestampVerification: verificationBlock.timestamp, + blocknumberVerification: startVerificationTx.blockNumber!, + }); + + await receiverGateway.allowlistAllSender(true); + + // Relay message + 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 +516,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 +758,15 @@ 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); + await receiverGateway.allowlistSender(true); + const relayTx = await veaOutbox.connect(receiver).sendMessage(proof, 0, receiverGateway.target, from, msgData); await expect(relayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0); }); }); @@ -681,7 +801,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 +935,15 @@ 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); + await receiverGateway.allowlistSender(true); + const relayTx = await veaOutbox.connect(receiver).sendMessage(proof, 0, receiverGateway.target, from, msgData); await expect(relayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0); }); @@ -964,3 +1083,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 }; +} diff --git a/relayer-cli/src/utils/proof.ts b/relayer-cli/src/utils/proof.ts index 524bd7b0..771329f1 100644 --- a/relayer-cli/src/utils/proof.ts +++ b/relayer-cli/src/utils/proof.ts @@ -5,6 +5,9 @@ interface MessageSentData { to: { id: string; }; + msgSender: { + id: string; + }; data: string; } @@ -34,12 +37,15 @@ const getMessageDataToRelay = async ( to { id } + msgSender { + id + } data } }` )) as MessageSentsDataResponse; - return [result[`messageSents`][0].to.id, result[`messageSents`][0].data]; + return [result[`messageSents`][0].to.id, result[`messageSents`][0].msgSender.id, result[`messageSents`][0].data]; } catch (e) { console.log(e); return undefined; diff --git a/relayer-cli/src/utils/relay.test.ts b/relayer-cli/src/utils/relay.test.ts index ad9ff754..4633c366 100644 --- a/relayer-cli/src/utils/relay.test.ts +++ b/relayer-cli/src/utils/relay.test.ts @@ -61,7 +61,7 @@ describe("relay", () => { fetchVeaOutbox = jest.fn().mockReturnValue(veaOutboxMock); fetchProofAtCount = jest.fn().mockResolvedValue([]); - fetchMessageDataToRelay = jest.fn().mockResolvedValue(["to", "data"]); + fetchMessageDataToRelay = jest.fn().mockResolvedValue(["to", "from", "data"]); mockWait = jest.fn().mockResolvedValue("receipt"); mockBatchSend = jest.fn().mockResolvedValue({ wait: mockWait }); diff --git a/relayer-cli/src/utils/relay.ts b/relayer-cli/src/utils/relay.ts index 767bcd0b..fb7f82af 100644 --- a/relayer-cli/src/utils/relay.ts +++ b/relayer-cli/src/utils/relay.ts @@ -59,8 +59,8 @@ const relay = async (chainId: number, nonce: number, network: Network) => { getMessageDataToRelay(chainId, veaInboxAddress, nonce), ]); if (!messageData) throw new DataError("relay message data"); - const [to, data] = messageData; - const txn = await veaOutbox.sendMessage(proof, nonce, to, data); + const [to, from, data] = messageData; + const txn = await veaOutbox.sendMessage(proof, nonce, to, from, data); const receipt = await txn.wait(); return receipt; }; @@ -127,10 +127,10 @@ const relayBatch = async ({ fetchProofAtCount(chainId, nonce, count, veaInboxAddress), fetchMessageDataToRelay(chainId, veaInboxAddress, nonce), ]); - const [to, data] = messageData; + const [to, from, data] = messageData; try { - await veaOutbox.sendMessage.staticCall(proof, nonce, to, data); - const callData = veaOutbox.interface.encodeFunctionData("sendMessage", [proof, nonce, to, data]); + await veaOutbox.sendMessage.staticCall(proof, nonce, to, from, data); + const callData = veaOutbox.interface.encodeFunctionData("sendMessage", [proof, nonce, to, from, data]); datas.push(callData); targets.push(veaOutboxAddress); values.push(0); @@ -142,6 +142,7 @@ const relayBatch = async ({ } } if (batchMessages > 0) { + console.log(targets, datas); const gasLimit = await batcher.batchSend.estimateGas(targets, values, datas); const tx = await batcher.batchSend(targets, values, datas, { gasLimit }); const receipt = await tx.wait(); @@ -193,9 +194,9 @@ const relayAllFrom = async ( getProofAtCount(chainId, x, count, veaInboxAddress), getMessageDataToRelay(chainId, veaInboxAddress, x), ]); - const [to, data] = messageData; + const [to, from, data] = messageData; - const callData = veaOutbox.interface.encodeFunctionData("sendMessage", [proof, x, to, data]); + const callData = veaOutbox.interface.encodeFunctionData("sendMessage", [proof, x, to, from, data]); datas.push(callData); targets.push(veaContracts[network].veaOutbox.address); values.push(0); diff --git a/relayer-subgraph-inbox/src/vea-inbox.ts b/relayer-subgraph-inbox/src/vea-inbox.ts index 189a902f..b3a536aa 100644 --- a/relayer-subgraph-inbox/src/vea-inbox.ts +++ b/relayer-subgraph-inbox/src/vea-inbox.ts @@ -30,12 +30,12 @@ export function handleMessageSent(event: MessageSentEvent): void { let _to = new ByteArray(20); for (let i = 0; i < 20; i++) _to[i] = msgData[i + 8]; - let dataLength = msgData.length - 28; - let _data = new ByteArray(dataLength); - for (let i = 0; i < dataLength; i++) _data[i] = msgData[i + 28]; - let _msgSender = new ByteArray(20); - for (let i = 0; i < 20; i++) _msgSender[i] = _data[i + 16]; + for (let i = 0; i < 20; i++) _msgSender[i] = msgData[i + 28]; + + let dataLength = msgData.length - 48; + let _data = new ByteArray(dataLength); + for (let i = 0; i < dataLength; i++) _data[i] = msgData[i + 48]; entity.inbox = event.address; entity.nonce = BigInt.fromByteArray(_nonce.reverse() as ByteArray);