From 245892b8d247398736ec96c573b216a4a3f9cc3d Mon Sep 17 00:00:00 2001 From: Elena Gesheva Date: Wed, 12 May 2021 16:46:11 +0300 Subject: [PATCH] Reduce gas costs of deposits (#667) * Remove messageNonce from BaseCrossDomainMessenger and use CTC queue lenght instead Remove Abs_BaseCrossDomainMessenger and restore dedicated nonce generation in OVM_L2CrossDomainMessenger Fix typo * Remove sentMessages mapping from L1CrossDomainMessenger storage and use the nonce to check for existence of replayed transaction * Refactor out common library function for getting cross domain calldata * Post rebase fixes * Use the queueIndex to check the transaction was enqueued * Fix tests for L1CrossDomainMessenger.replayMessage Also make that test work with an actual CanonicalTransactionChain implementation rather than a smock * Lint fixes * Optimise the resolve calls into the AddressManager lib * Rename the nonce parameter to be clear * Update test name Co-authored-by: ben-chain * Rename getXDomainCalldata to encodeXDomainCalldata to match the new Lib_CrossDomainUtils Co-authored-by: ben-chain --- .../Abs_BaseCrossDomainMessenger.sol | 139 ------------------ .../messaging/OVM_L1CrossDomainMessenger.sol | 106 ++++++++++--- .../messaging/OVM_L2CrossDomainMessenger.sol | 75 ++++++++-- .../predeploys/OVM_L2ToL1MessagePasser.sol | 1 - .../messaging/iOVM_L1CrossDomainMessenger.sol | 4 +- .../libraries/bridge/Lib_CrossDomainUtils.sol | 40 +++++ .../base/OVM_L1CrossDomainMessenger.spec.ts | 129 ++++++++++++---- .../base/OVM_L2CrossDomainMessenger.spec.ts | 8 +- .../contracts/test/helpers/codec/bridge.ts | 2 +- 9 files changed, 302 insertions(+), 202 deletions(-) delete mode 100644 packages/contracts/contracts/optimistic-ethereum/OVM/bridge/messaging/Abs_BaseCrossDomainMessenger.sol create mode 100644 packages/contracts/contracts/optimistic-ethereum/libraries/bridge/Lib_CrossDomainUtils.sol diff --git a/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/messaging/Abs_BaseCrossDomainMessenger.sol b/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/messaging/Abs_BaseCrossDomainMessenger.sol deleted file mode 100644 index b4b468b5d9d1..000000000000 --- a/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/messaging/Abs_BaseCrossDomainMessenger.sol +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >0.5.0 <0.8.0; -pragma experimental ABIEncoderV2; - -/* Interface Imports */ -import { iAbs_BaseCrossDomainMessenger } from "../../../iOVM/bridge/messaging/iAbs_BaseCrossDomainMessenger.sol"; - -/** - * @title Abs_BaseCrossDomainMessenger - * @dev The Base Cross Domain Messenger is an abstract contract providing the interface and common - * functionality used in the L1 and L2 Cross Domain Messengers. It can also serve as a template for - * developers wishing to implement a custom bridge contract to suit their needs. - * - * Compiler used: defined by child contract - * Runtime target: defined by child contract - */ -abstract contract Abs_BaseCrossDomainMessenger is iAbs_BaseCrossDomainMessenger { - - /************* - * Constants * - *************/ - - // The default x-domain message sender being set to a non-zero value makes - // deployment a bit more expensive, but in exchange the refund on every call to - // `relayMessage` by the L1 and L2 messengers will be higher. - address internal constant DEFAULT_XDOMAIN_SENDER = 0x000000000000000000000000000000000000dEaD; - - - /************* - * Variables * - *************/ - - mapping (bytes32 => bool) public relayedMessages; - mapping (bytes32 => bool) public successfulMessages; - mapping (bytes32 => bool) public sentMessages; - uint256 public messageNonce; - address internal xDomainMsgSender = DEFAULT_XDOMAIN_SENDER; - - - /*************** - * Constructor * - ***************/ - - constructor() {} - - - /******************** - * Public Functions * - ********************/ - - function xDomainMessageSender() - public - override - view - returns ( - address - ) - { - require(xDomainMsgSender != DEFAULT_XDOMAIN_SENDER, "xDomainMessageSender is not set"); - return xDomainMsgSender; - } - - /** - * Sends a cross domain message to the target messenger. - * @param _target Target contract address. - * @param _message Message to send to the target. - * @param _gasLimit Gas limit for the provided message. - */ - function sendMessage( - address _target, - bytes memory _message, - uint32 _gasLimit - ) - override - public - { - bytes memory xDomainCalldata = _getXDomainCalldata( - _target, - msg.sender, - _message, - messageNonce - ); - - messageNonce += 1; - sentMessages[keccak256(xDomainCalldata)] = true; - - _sendXDomainMessage(xDomainCalldata, _gasLimit); - emit SentMessage(xDomainCalldata); - } - - - /********************** - * Internal Functions * - **********************/ - - /** - * Generates the correct cross domain calldata for a message. - * @param _target Target contract address. - * @param _sender Message sender address. - * @param _message Message to send to the target. - * @param _messageNonce Nonce for the provided message. - * @return ABI encoded cross domain calldata. - */ - function _getXDomainCalldata( - address _target, - address _sender, - bytes memory _message, - uint256 _messageNonce - ) - internal - pure - returns ( - bytes memory - ) - { - return abi.encodeWithSignature( - "relayMessage(address,address,bytes,uint256)", - _target, - _sender, - _message, - _messageNonce - ); - } - - /** - * Sends a cross domain message. - * param // Message to send. - * param // Gas limit for the provided message. - */ - function _sendXDomainMessage( - bytes memory, // _message, - uint256 // _gasLimit - ) - virtual - internal - { - revert("Implement me in child contracts!"); - } -} diff --git a/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/messaging/OVM_L1CrossDomainMessenger.sol b/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/messaging/OVM_L1CrossDomainMessenger.sol index 445afd808509..0fa61807d07e 100644 --- a/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/messaging/OVM_L1CrossDomainMessenger.sol +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/messaging/OVM_L1CrossDomainMessenger.sol @@ -3,19 +3,17 @@ pragma solidity >0.5.0 <0.8.0; pragma experimental ABIEncoderV2; /* Library Imports */ -import { Lib_OVMCodec } from "../../../libraries/codec/Lib_OVMCodec.sol"; import { Lib_AddressResolver } from "../../../libraries/resolver/Lib_AddressResolver.sol"; +import { Lib_OVMCodec } from "../../../libraries/codec/Lib_OVMCodec.sol"; import { Lib_AddressManager } from "../../../libraries/resolver/Lib_AddressManager.sol"; import { Lib_SecureMerkleTrie } from "../../../libraries/trie/Lib_SecureMerkleTrie.sol"; +import { Lib_CrossDomainUtils } from "../../../libraries/bridge/Lib_CrossDomainUtils.sol"; /* Interface Imports */ import { iOVM_L1CrossDomainMessenger } from "../../../iOVM/bridge/messaging/iOVM_L1CrossDomainMessenger.sol"; import { iOVM_CanonicalTransactionChain } from "../../../iOVM/chain/iOVM_CanonicalTransactionChain.sol"; import { iOVM_StateCommitmentChain } from "../../../iOVM/chain/iOVM_StateCommitmentChain.sol"; -/* Contract Imports */ -import { Abs_BaseCrossDomainMessenger } from "./Abs_BaseCrossDomainMessenger.sol"; - /* External Imports */ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; @@ -32,7 +30,6 @@ import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/ */ contract OVM_L1CrossDomainMessenger is iOVM_L1CrossDomainMessenger, - Abs_BaseCrossDomainMessenger, Lib_AddressResolver, OwnableUpgradeable, PausableUpgradeable, @@ -51,11 +48,24 @@ contract OVM_L1CrossDomainMessenger is bytes32 indexed _xDomainCalldataHash ); + /************* + * Constants * + *************/ + + // The default x-domain message sender being set to a non-zero value makes + // deployment a bit more expensive, but in exchange the refund on every call to + // `relayMessage` by the L1 and L2 messengers will be higher. + address internal constant DEFAULT_XDOMAIN_SENDER = 0x000000000000000000000000000000000000dEaD; + /********************** * Contract Variables * **********************/ mapping (bytes32 => bool) public blockedMessages; + mapping (bytes32 => bool) public relayedMessages; + mapping (bytes32 => bool) public successfulMessages; + + address internal xDomainMsgSender = DEFAULT_XDOMAIN_SENDER; /*************** * Constructor * @@ -155,6 +165,48 @@ contract OVM_L1CrossDomainMessenger is emit MessageAllowed(_xDomainCalldataHash); } + function xDomainMessageSender() + public + override + view + returns ( + address + ) + { + require(xDomainMsgSender != DEFAULT_XDOMAIN_SENDER, "xDomainMessageSender is not set"); + return xDomainMsgSender; + } + + /** + * Sends a cross domain message to the target messenger. + * @param _target Target contract address. + * @param _message Message to send to the target. + * @param _gasLimit Gas limit for the provided message. + */ + function sendMessage( + address _target, + bytes memory _message, + uint32 _gasLimit + ) + override + public + { + address ovmCanonicalTransactionChain = resolve("OVM_CanonicalTransactionChain"); + // Use the CTC queue length as nonce + uint40 nonce = iOVM_CanonicalTransactionChain(ovmCanonicalTransactionChain).getQueueLength(); + + bytes memory xDomainCalldata = Lib_CrossDomainUtils.encodeXDomainCalldata( + _target, + msg.sender, + _message, + nonce + ); + + address l2CrossDomainMessenger = resolve("OVM_L2CrossDomainMessenger"); + _sendXDomainMessage(ovmCanonicalTransactionChain, l2CrossDomainMessenger, xDomainCalldata, _gasLimit); + emit SentMessage(xDomainCalldata); + } + /** * Relays a cross domain message to a contract. * @inheritdoc iOVM_L1CrossDomainMessenger @@ -172,7 +224,7 @@ contract OVM_L1CrossDomainMessenger is onlyRelayer whenNotPaused { - bytes memory xDomainCalldata = _getXDomainCalldata( + bytes memory xDomainCalldata = Lib_CrossDomainUtils.encodeXDomainCalldata( _target, _sender, _message, @@ -232,25 +284,40 @@ contract OVM_L1CrossDomainMessenger is address _target, address _sender, bytes memory _message, - uint256 _messageNonce, + uint256 _queueIndex, uint32 _gasLimit ) override public { - bytes memory xDomainCalldata = _getXDomainCalldata( - _target, - _sender, - _message, - _messageNonce + // Verify that the message is in the queue: + address canonicalTransactionChain = resolve("OVM_CanonicalTransactionChain"); + Lib_OVMCodec.QueueElement memory element = iOVM_CanonicalTransactionChain(canonicalTransactionChain).getQueueElement(_queueIndex); + + address l2CrossDomainMessenger = resolve("OVM_L2CrossDomainMessenger"); + // Compute the transactionHash + bytes32 transactionHash = keccak256( + abi.encode( + address(this), + l2CrossDomainMessenger, + _gasLimit, + _message + ) ); require( - sentMessages[keccak256(xDomainCalldata)] == true, - "Provided message has not already been sent." + transactionHash == element.transactionHash, + "Provided message has not been enqueued." + ); + + bytes memory xDomainCalldata = Lib_CrossDomainUtils.encodeXDomainCalldata( + _target, + _sender, + _message, + _queueIndex ); - _sendXDomainMessage(xDomainCalldata, _gasLimit); + _sendXDomainMessage(canonicalTransactionChain, l2CrossDomainMessenger, xDomainCalldata, _gasLimit); } @@ -364,18 +431,21 @@ contract OVM_L1CrossDomainMessenger is /** * Sends a cross domain message. + * @param _canonicalTransactionChain Address of the OVM_CanonicalTransactionChain instance. + * @param _l2CrossDomainMessenger Address of the OVM_L2CrossDomainMessenger instance. * @param _message Message to send. * @param _gasLimit OVM gas limit for the message. */ function _sendXDomainMessage( + address _canonicalTransactionChain, + address _l2CrossDomainMessenger, bytes memory _message, uint256 _gasLimit ) - override internal { - iOVM_CanonicalTransactionChain(resolve("OVM_CanonicalTransactionChain")).enqueue( - resolve("OVM_L2CrossDomainMessenger"), + iOVM_CanonicalTransactionChain(_canonicalTransactionChain).enqueue( + _l2CrossDomainMessenger, _gasLimit, _message ); diff --git a/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/messaging/OVM_L2CrossDomainMessenger.sol b/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/messaging/OVM_L2CrossDomainMessenger.sol index dfd3d56d8be6..1294794bd139 100644 --- a/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/messaging/OVM_L2CrossDomainMessenger.sol +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/messaging/OVM_L2CrossDomainMessenger.sol @@ -4,14 +4,15 @@ pragma experimental ABIEncoderV2; /* Library Imports */ import { Lib_AddressResolver } from "../../../libraries/resolver/Lib_AddressResolver.sol"; +import { Lib_CrossDomainUtils } from "../../../libraries/bridge/Lib_CrossDomainUtils.sol"; /* Interface Imports */ import { iOVM_L2CrossDomainMessenger } from "../../../iOVM/bridge/messaging/iOVM_L2CrossDomainMessenger.sol"; import { iOVM_L1MessageSender } from "../../../iOVM/predeploys/iOVM_L1MessageSender.sol"; import { iOVM_L2ToL1MessagePasser } from "../../../iOVM/predeploys/iOVM_L2ToL1MessagePasser.sol"; -/* Contract Imports */ -import { Abs_BaseCrossDomainMessenger } from "./Abs_BaseCrossDomainMessenger.sol"; +/* External Imports */ +import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; /* External Imports */ import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; @@ -26,11 +27,29 @@ import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.s */ contract OVM_L2CrossDomainMessenger is iOVM_L2CrossDomainMessenger, - Abs_BaseCrossDomainMessenger, Lib_AddressResolver, ReentrancyGuard { + /************* + * Constants * + *************/ + + // The default x-domain message sender being set to a non-zero value makes + // deployment a bit more expensive, but in exchange the refund on every call to + // `relayMessage` by the L1 and L2 messengers will be higher. + address internal constant DEFAULT_XDOMAIN_SENDER = 0x000000000000000000000000000000000000dEaD; + + /************* + * Variables * + *************/ + + mapping (bytes32 => bool) public relayedMessages; + mapping (bytes32 => bool) public successfulMessages; + mapping (bytes32 => bool) public sentMessages; + uint256 public messageNonce; + address internal xDomainMsgSender = DEFAULT_XDOMAIN_SENDER; + /*************** * Constructor * ***************/ @@ -38,18 +57,53 @@ contract OVM_L2CrossDomainMessenger is /** * @param _libAddressManager Address of the Address Manager. */ - constructor( - address _libAddressManager - ) - Lib_AddressResolver(_libAddressManager) - ReentrancyGuard() - {} + constructor(address _libAddressManager) Lib_AddressResolver(_libAddressManager) ReentrancyGuard() {} /******************** * Public Functions * ********************/ + function xDomainMessageSender() + public + override + view + returns ( + address + ) + { + require(xDomainMsgSender != DEFAULT_XDOMAIN_SENDER, "xDomainMessageSender is not set"); + return xDomainMsgSender; + } + + /** + * Sends a cross domain message to the target messenger. + * @param _target Target contract address. + * @param _message Message to send to the target. + * @param _gasLimit Gas limit for the provided message. + */ + function sendMessage( + address _target, + bytes memory _message, + uint32 _gasLimit + ) + override + public + { + bytes memory xDomainCalldata = Lib_CrossDomainUtils.encodeXDomainCalldata( + _target, + msg.sender, + _message, + messageNonce + ); + + messageNonce += 1; + sentMessages[keccak256(xDomainCalldata)] = true; + + _sendXDomainMessage(xDomainCalldata, _gasLimit); + emit SentMessage(xDomainCalldata); + } + /** * Relays a cross domain message to a contract. * @inheritdoc iOVM_L2CrossDomainMessenger @@ -69,7 +123,7 @@ contract OVM_L2CrossDomainMessenger is "Provided message could not be verified." ); - bytes memory xDomainCalldata = _getXDomainCalldata( + bytes memory xDomainCalldata = Lib_CrossDomainUtils.encodeXDomainCalldata( _target, _sender, _message, @@ -150,7 +204,6 @@ contract OVM_L2CrossDomainMessenger is bytes memory _message, uint256 // _gasLimit ) - override internal { iOVM_L2ToL1MessagePasser(resolve("OVM_L2ToL1MessagePasser")).passMessageToL1(_message); diff --git a/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_L2ToL1MessagePasser.sol b/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_L2ToL1MessagePasser.sol index cd9b2431bc5b..6e88c26e11c8 100644 --- a/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_L2ToL1MessagePasser.sol +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_L2ToL1MessagePasser.sol @@ -22,7 +22,6 @@ contract OVM_L2ToL1MessagePasser is iOVM_L2ToL1MessagePasser { mapping (bytes32 => bool) public sentMessages; - /******************** * Public Functions * ********************/ diff --git a/packages/contracts/contracts/optimistic-ethereum/iOVM/bridge/messaging/iOVM_L1CrossDomainMessenger.sol b/packages/contracts/contracts/optimistic-ethereum/iOVM/bridge/messaging/iOVM_L1CrossDomainMessenger.sol index 629e20e1d618..c560a623e115 100644 --- a/packages/contracts/contracts/optimistic-ethereum/iOVM/bridge/messaging/iOVM_L1CrossDomainMessenger.sol +++ b/packages/contracts/contracts/optimistic-ethereum/iOVM/bridge/messaging/iOVM_L1CrossDomainMessenger.sol @@ -51,14 +51,14 @@ interface iOVM_L1CrossDomainMessenger is iAbs_BaseCrossDomainMessenger { * @param _target Target contract address. * @param _sender Original sender address. * @param _message Message to send to the target. - * @param _messageNonce Nonce for the provided message. + * @param _queueIndex CTC Queue index for the message to replay. * @param _gasLimit Gas limit for the provided message. */ function replayMessage( address _target, address _sender, bytes memory _message, - uint256 _messageNonce, + uint256 _queueIndex, uint32 _gasLimit ) external; } diff --git a/packages/contracts/contracts/optimistic-ethereum/libraries/bridge/Lib_CrossDomainUtils.sol b/packages/contracts/contracts/optimistic-ethereum/libraries/bridge/Lib_CrossDomainUtils.sol new file mode 100644 index 000000000000..a4b8afff192e --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/libraries/bridge/Lib_CrossDomainUtils.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity >0.5.0 <0.8.0; +pragma experimental ABIEncoderV2; + +/* Library Imports */ +import { Lib_RLPReader } from "../rlp/Lib_RLPReader.sol"; + +/** + * @title Lib_CrossDomainUtils + */ +library Lib_CrossDomainUtils { + /** + * Generates the correct cross domain calldata for a message. + * @param _target Target contract address. + * @param _sender Message sender address. + * @param _message Message to send to the target. + * @param _messageNonce Nonce for the provided message. + * @return ABI encoded cross domain calldata. + */ + function encodeXDomainCalldata( + address _target, + address _sender, + bytes memory _message, + uint256 _messageNonce + ) + internal + pure + returns ( + bytes memory + ) + { + return abi.encodeWithSignature( + "relayMessage(address,address,bytes,uint256)", + _target, + _sender, + _message, + _messageNonce + ); + } +} diff --git a/packages/contracts/test/contracts/OVM/bridge/base/OVM_L1CrossDomainMessenger.spec.ts b/packages/contracts/test/contracts/OVM/bridge/base/OVM_L1CrossDomainMessenger.spec.ts index 7ed33d551d4e..75ceaaa9bd51 100644 --- a/packages/contracts/test/contracts/OVM/bridge/base/OVM_L1CrossDomainMessenger.spec.ts +++ b/packages/contracts/test/contracts/OVM/bridge/base/OVM_L1CrossDomainMessenger.spec.ts @@ -14,12 +14,16 @@ import { NON_ZERO_ADDRESS, DUMMY_BATCH_HEADERS, DUMMY_BATCH_PROOFS, + FORCE_INCLUSION_PERIOD_SECONDS, + FORCE_INCLUSION_PERIOD_BLOCKS, TrieTestGenerator, getNextBlockNumber, - getXDomainCalldata, + encodeXDomainCalldata, } from '../../../../helpers' import { keccak256 } from 'ethers/lib/utils' +const MAX_GAS_LIMIT = 8_000_000 + const deployProxyXDomainMessenger = async ( addressManager: Contract, l1XDomainMessenger: Contract @@ -48,8 +52,13 @@ describe('OVM_L1CrossDomainMessenger', () => { let Mock__TargetContract: MockContract let Mock__OVM_L2CrossDomainMessenger: MockContract - let Mock__OVM_CanonicalTransactionChain: MockContract let Mock__OVM_StateCommitmentChain: MockContract + + let Factory__OVM_CanonicalTransactionChain: ContractFactory + let Factory__OVM_ChainStorageContainer: ContractFactory + let Factory__OVM_L1CrossDomainMessenger: ContractFactory + + let OVM_CanonicalTransactionChain: Contract before(async () => { Mock__TargetContract = await smockit( await ethers.getContractFactory('Helper_SimpleProxy') @@ -57,9 +66,6 @@ describe('OVM_L1CrossDomainMessenger', () => { Mock__OVM_L2CrossDomainMessenger = await smockit( await ethers.getContractFactory('OVM_L2CrossDomainMessenger') ) - Mock__OVM_CanonicalTransactionChain = await smockit( - await ethers.getContractFactory('OVM_CanonicalTransactionChain') - ) Mock__OVM_StateCommitmentChain = await smockit( await ethers.getContractFactory('OVM_StateCommitmentChain') ) @@ -69,23 +75,53 @@ describe('OVM_L1CrossDomainMessenger', () => { Mock__OVM_L2CrossDomainMessenger.address ) - await setProxyTarget( - AddressManager, - 'OVM_CanonicalTransactionChain', - Mock__OVM_CanonicalTransactionChain - ) await setProxyTarget( AddressManager, 'OVM_StateCommitmentChain', Mock__OVM_StateCommitmentChain ) - }) - let Factory__OVM_L1CrossDomainMessenger: ContractFactory - before(async () => { + Factory__OVM_CanonicalTransactionChain = await ethers.getContractFactory( + 'OVM_CanonicalTransactionChain' + ) + + Factory__OVM_ChainStorageContainer = await ethers.getContractFactory( + 'OVM_ChainStorageContainer' + ) + Factory__OVM_L1CrossDomainMessenger = await ethers.getContractFactory( 'OVM_L1CrossDomainMessenger' ) + OVM_CanonicalTransactionChain = await Factory__OVM_CanonicalTransactionChain.deploy( + AddressManager.address, + FORCE_INCLUSION_PERIOD_SECONDS, + FORCE_INCLUSION_PERIOD_BLOCKS, + MAX_GAS_LIMIT + ) + + const batches = await Factory__OVM_ChainStorageContainer.deploy( + AddressManager.address, + 'OVM_CanonicalTransactionChain' + ) + const queue = await Factory__OVM_ChainStorageContainer.deploy( + AddressManager.address, + 'OVM_CanonicalTransactionChain' + ) + + await AddressManager.setAddress( + 'OVM_ChainStorageContainer:CTC:batches', + batches.address + ) + + await AddressManager.setAddress( + 'OVM_ChainStorageContainer:CTC:queue', + queue.address + ) + + await AddressManager.setAddress( + 'OVM_CanonicalTransactionChain', + OVM_CanonicalTransactionChain.address + ) }) let OVM_L1CrossDomainMessenger: Contract @@ -117,6 +153,26 @@ describe('OVM_L1CrossDomainMessenger', () => { }) }) + const getTransactionHash = ( + sender: string, + target: string, + gasLimit: number, + data: string + ): string => { + return keccak256(encodeQueueTransaction(sender, target, gasLimit, data)) + } + + const encodeQueueTransaction = ( + sender: string, + target: string, + gasLimit: number, + data: string + ): string => { + return ethers.utils.defaultAbiCoder.encode( + ['address', 'address', 'uint256', 'bytes'], + [sender, target, gasLimit, data] + ) + } describe('sendMessage', () => { const target = NON_ZERO_ADDRESS const message = NON_NULL_BYTES32 @@ -127,13 +183,24 @@ describe('OVM_L1CrossDomainMessenger', () => { OVM_L1CrossDomainMessenger.sendMessage(target, message, gasLimit) ).to.not.be.reverted - expect( - Mock__OVM_CanonicalTransactionChain.smocked.enqueue.calls[0] - ).to.deep.equal([ + const calldata = encodeXDomainCalldata( + target, + await signer.getAddress(), + message, + 0 + ) + const transactionHash = getTransactionHash( + OVM_L1CrossDomainMessenger.address, Mock__OVM_L2CrossDomainMessenger.address, - BigNumber.from(gasLimit), - getXDomainCalldata(target, await signer.getAddress(), message, 0), - ]) + gasLimit, + calldata + ) + + const queueLength = await OVM_CanonicalTransactionChain.getQueueLength() + const queueElement = await OVM_CanonicalTransactionChain.getQueueElement( + queueLength - 1 + ) + expect(queueElement[0]).to.equal(transactionHash) }) it('should be able to send the same message twice', async () => { @@ -150,27 +217,37 @@ describe('OVM_L1CrossDomainMessenger', () => { const message = NON_NULL_BYTES32 const gasLimit = 100_000 - it('should revert if the message does not exist', async () => { + it('should revert if given the wrong queue index', async () => { + await OVM_L1CrossDomainMessenger.sendMessage(target, message, 100_001) + + const queueLength = await OVM_CanonicalTransactionChain.getQueueLength() await expect( OVM_L1CrossDomainMessenger.replayMessage( target, await signer.getAddress(), message, - 0, + queueLength - 1, gasLimit ) - ).to.be.revertedWith('Provided message has not already been sent.') + ).to.be.revertedWith('Provided message has not been enqueued.') }) it('should succeed if the message exists', async () => { await OVM_L1CrossDomainMessenger.sendMessage(target, message, gasLimit) + const queueLength = await OVM_CanonicalTransactionChain.getQueueLength() + const calldata = encodeXDomainCalldata( + target, + await signer.getAddress(), + message, + queueLength - 1 + ) await expect( OVM_L1CrossDomainMessenger.replayMessage( - target, + Mock__OVM_L2CrossDomainMessenger.address, await signer.getAddress(), - message, - 0, + calldata, + queueLength - 1, gasLimit ) ).to.not.be.reverted @@ -190,7 +267,7 @@ describe('OVM_L1CrossDomainMessenger', () => { ]) sender = await signer.getAddress() - calldata = getXDomainCalldata(target, sender, message, 0) + calldata = encodeXDomainCalldata(target, sender, message, 0) const precompile = '0x4200000000000000000000000000000000000000' diff --git a/packages/contracts/test/contracts/OVM/bridge/base/OVM_L2CrossDomainMessenger.spec.ts b/packages/contracts/test/contracts/OVM/bridge/base/OVM_L2CrossDomainMessenger.spec.ts index fa0baf0248d6..ebea649e9fed 100644 --- a/packages/contracts/test/contracts/OVM/bridge/base/OVM_L2CrossDomainMessenger.spec.ts +++ b/packages/contracts/test/contracts/OVM/bridge/base/OVM_L2CrossDomainMessenger.spec.ts @@ -11,7 +11,7 @@ import { setProxyTarget, NON_NULL_BYTES32, NON_ZERO_ADDRESS, - getXDomainCalldata, + encodeXDomainCalldata, getNextBlockNumber, } from '../../../../helpers' import { solidityKeccak256 } from 'ethers/lib/utils' @@ -89,7 +89,7 @@ describe('OVM_L2CrossDomainMessenger', () => { expect( Mock__OVM_L2ToL1MessagePasser.smocked.passMessageToL1.calls[0] ).to.deep.equal([ - getXDomainCalldata(target, await signer.getAddress(), message, 0), + encodeXDomainCalldata(target, await signer.getAddress(), message, 0), ]) }) @@ -193,7 +193,7 @@ describe('OVM_L2CrossDomainMessenger', () => { await OVM_L2CrossDomainMessenger.successfulMessages( solidityKeccak256( ['bytes'], - [getXDomainCalldata(target, sender, message, 0)] + [encodeXDomainCalldata(target, sender, message, 0)] ) ) ).to.be.true @@ -211,7 +211,7 @@ describe('OVM_L2CrossDomainMessenger', () => { // Calculate xDomainCallData used for indexing // (within the first call to the L2 Messenger). - const xDomainCallData = getXDomainCalldata( + const xDomainCallData = encodeXDomainCalldata( OVM_L2CrossDomainMessenger.address, sender, reentrantMessage, diff --git a/packages/contracts/test/helpers/codec/bridge.ts b/packages/contracts/test/helpers/codec/bridge.ts index bf36d24ec924..a96e280544c7 100644 --- a/packages/contracts/test/helpers/codec/bridge.ts +++ b/packages/contracts/test/helpers/codec/bridge.ts @@ -1,6 +1,6 @@ import { getContractInterface } from '../../../src/contract-defs' -export const getXDomainCalldata = ( +export const encodeXDomainCalldata = ( target: string, sender: string, message: string,