diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a4d2359..77da559 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,7 +57,7 @@ jobs: yarn yarn typechain - - name: Run relayCalls Arbitrum fork tests + - name: Run dispatchMessageBatch Arbitrum fork tests continue-on-error: true env: MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} @@ -65,9 +65,9 @@ jobs: FORK_ENABLED: true HDWALLET_MNEMONIC: ${{ secrets.HDWALLET_MNEMONIC }} run: | - yarn fork:startRelayCallsArbitrumMainnet + yarn fork:startDispatchMessageBatchArbitrumMainnet - - name: Run executeCalls Arbitrum fork tests + - name: Run executeMessageBatch Arbitrum fork tests continue-on-error: true env: MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} @@ -75,4 +75,4 @@ jobs: FORK_ENABLED: true HDWALLET_MNEMONIC: ${{ secrets.HDWALLET_MNEMONIC }} run: | - yarn fork:startExecuteCallsArbitrumMainnet + yarn fork:startExecuteMessageBatchArbitrumMainnet diff --git a/README.md b/README.md index c44d2c3..20a24a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ERC-5164 -EIP-5164 specifies how smart contracts on one chain can call contracts on another. Transport layers, such as bridges, will have their own EIP-5164 implementations. This repository includes implementations for: Ethereum to Polygon, Ethereum to Optimism, and Ethereum to Arbitrum. All three use the 'native' bridge solutions. +EIP-5164 specifies how smart contracts on one chain can message contracts on another. Transport layers, such as bridges, will have their own EIP-5164 implementations. This repository includes implementations for: Ethereum to Polygon, Ethereum to Optimism, and Ethereum to Arbitrum. All three use the 'native' bridge solutions. The EIP is currently in the Review stage: https://eips.ethereum.org/EIPS/eip-5164 @@ -10,77 +10,97 @@ Feedback and PR are welcome! To use ERC-5164 to send messages your contract code will need to: -- On the sending chain, send a batch of calls to the CrossChainRelayer `relayCalls` function -- Listen for calls from the corresponding CrossChainExecutor(s) on the receiving chain. +- On the sending chain, send a message to the MessageDispatcher `dispatchMessage` or `dispatchMessageBatch` function +- Listen for messages from the corresponding MessageExecutor(s) on the receiving chain. _The listener will need to be able to unpack the original sender address (it's appended to calldata). We recommend inheriting from the included [`ExecutorAware.sol`](./src/abstract/ExecutorAware.sol) contract._ **Note** -For most bridges, you only have to call `relayCalls` to have messages executed by the CrossChainExecutor. However, Arbitrum requires an EOA to process the relay. We will review this below. +For most bridges, you only have to call `dispatchMessage` or `dispatchMessageBatch` to have messages executed by the MessageExecutor. However, Arbitrum requires an EOA to process the dispatch. We will review this below. ## How it works -1. A smart contract on the sending chain calls `relayCalls` on the CrossChainRelayer; it is passed an array of Call structs. -2. The corresponding CrossChainExecutor(s) on the receiving chain will execute the batch of Call structs. The address of the original caller on the sending chain is appended to the call data. -3. Any smart contract can receive calls from a CrossChainExecutor, but they should use the original caller address for authentication. +1. A smart contract on the sending chain calls `dispatchMessage` or `dispatchMessageBatch` on the MessageDispatcher.. +2. The corresponding MessageExecutor(s) on the receiving chain will execute the message or batch of Message structs. The address of the original dispatcher on the sending chain is appended to the message data. +3. Any smart contract can receive messages from a MessageExecutor, but they should use the original dispatcher address for authentication. **Note: this specification does not require messages to be executed in order** -## Relaying +## Dispatching -### Relay calls +### Dispatch a message -To relay a message from Ethereum to the L2 of your choice, you have to interact with the [CrossChainRelayer](./src/interfaces/ICrossChainRelayer.sol) contract and call the `relayCalls` function: +To dispatch a message from Ethereum to the L2 of your choice, you have to interact with the [ISingleMessageDispatcher](./src/interfaces/ISingleMessageDispatcher.sol) contract and call the following function. ```solidity /** - * @notice Relay the calls to the receiving chain. - * @dev Must increment a `nonce` so that the batch of calls can be uniquely identified. - * @dev Must emit the `RelayedCalls` event when successfully called. - * @dev May require payment. Some bridges may require payment in the native currency, so the function is payable. - * @param calls Array of calls being relayed - * @param gasLimit Maximum amount of gas required for the `calls` to be executed - * @return uint256 Nonce to uniquely idenfity the batch of calls + * @notice Dispatch a message to the receiving chain. + * @dev Must compute and return an ID uniquely identifying the message. + * @dev Must emit the `MessageDispatched` event when successfully dispatched. + * @param toChainId ID of the receiving chain + * @param to Address on the receiving chain that will receive `data` + * @param data Data dispatched to the receiving chain + * @return bytes32 ID uniquely identifying the message */ -function relayCalls(CallLib.Call[] calldata calls, uint256 gasLimit) +function dispatchMessage( + uint256 toChainId, + address to, + bytes calldata data +) external returns (bytes32); + +``` + +- `toChainId`: id of the chain to which you want to dispatch the message +- `to`: address of the contract that will receive the message +- `data`: message that you want to be executed on L2 + +### Dispatch a batch messages + +To dispatch a batch of messages from Ethereum to the L2 of your choice, you have to interact with the [IBatchedMessageDispatcher](./src/interfaces/IBatchedMessageDispatcher.sol) contract and call the following function. + +```solidity +/** + * @notice Dispatch `messages` to the receiving chain. + * @dev Must compute and return an ID uniquely identifying the `messages`. + * @dev Must emit the `MessageBatchDispatched` event when successfully dispatched. + * @param toChainId ID of the receiving chain + * @param messages Array of Message dispatched + * @return bytes32 ID uniquely identifying the `messages` + */ +function dispatchMessageBatch(uint256 toChainId, MessageLib.Message[] calldata messages) external - payable - returns (uint256); + returns (bytes32); ``` -`calls` is an array of calls that you want to be executed on L2: +- `toChainId`: id of the chain to which you want to dispatch the message +- `messages`: array of Message that you want to be executed on L2 ```solidity /** - * @notice Call data structure - * @param target Address that will be called on the receiving chain - * @param data Data that will be sent to the `target` address + * @notice Message data structure + * @param to Address that will be dispatched on the receiving chain + * @param data Data that will be sent to the `to` address */ -struct Call { - address target; +struct Message { + address to; bytes data; } ``` -`gasLimit` is the maximum amount of gas that will be needed to execute these calls. - #### Example ```solidity -CrossChainRelayerOptimism _crossChainRelayer = 0xB577c479D6D7eC677dB6c349e6E23B7bfE303295; -address _greeter = 0xd55052D3617f8ebd5DeEb7F0AC2D6f20d185Bc9d; - -CallLib.Call[] memory _calls = new CallLib.Call[](1); +MessageDispatcherOptimism _messageDispatcher = 0x3F3623aB84a86410096f53051b82aA41773A4480; +address _greeter = 0x19c8f7B8BA7a151d6825924446A596b6084a36ae; -_calls[0] = CallLib.Call({ - target: _greeter, - data: abi.encodeWithSignature("setGreeting(string)", "Hello from L1") -}); - -_crossChainRelayer.relayCalls(_calls, 1000000); +_messageDispatcher.dispatchMessage( + 420, + _greeter, + abi.encodeCall(Greeter.setGreeting, ("Hello from L1")) +); ``` Code: @@ -88,31 +108,37 @@ Code: - [script/bridge/BridgeToOptimismGoerli.s.sol](script/bridge/BridgeToOptimismGoerli.s.sol) - [script/bridge/BridgeToMumbai.s.sol](script/bridge/BridgeToMumbai.s.sol) -### Arbitrum Relay +### Arbitrum Dispatch -Arbitrum requires an EOA to submit a bridge transaction. The Ethereum to Arbitrum ERC-5164 CrossChainRelayer `relayCalls` implementation is therefore split into two actions: +Arbitrum requires an EOA to submit a bridge transaction. The Ethereum to Arbitrum ERC-5164 MessageDispatcher `dispatchMessage` implementation is therefore split into two actions: -1. Calls to CrossChainRelayer `relayCalls` are fingerprinted and stored along with their nonce. -2. Anyone may call CrossChainRelayer `processCalls` to send a previously fingerprinted relayed call. +1. Message to MessageDispatcher `dispatchMessage` is fingerprinted and stored along with their `messageId`. +2. Anyone may call MessageDispatcher `processMessage` to send a previously fingerprinted dispatched message. -The `processCalls` function requires the same transaction parameters as the Arbitrum bridge. The [Arbitrum SDK](https://github.com/offchainlabs/arbitrum-sdk) is needed to properly estimate the gas required to execute the message on L2. +The `processMessage` function requires the same transaction parameters as the Arbitrum bridge. The [Arbitrum SDK](https://github.com/offchainlabs/arbitrum-sdk) is needed to properly estimate the gas required to execute the message on L2. ```solidity /** - * @notice Process calls that have been relayed. - * @dev The transaction hash must match the one stored in the `relayed` mapping. - * @param nonce Nonce of the batch of calls to process - * @param calls Array of calls being processed - * @param sender Address who relayed the `calls` - * @param gasLimit Maximum amount of gas required for the `calls` to be executed - * @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee - * @param gasPriceBid Gas price bid for L2 execution + * @notice Process message that has been dispatched. + * @dev The transaction hash must match the one stored in the `dispatched` mapping. + * @dev `_from` is passed as `callValueRefundAddress` cause this address can cancel the retryably ticket. + * @dev We store `_message` in memory to avoid a stack too deep error. + * @param _messageId ID of the message to process + * @param _from Address who dispatched the `_data` + * @param _to Address that will receive the message + * @param _data Data that was dispatched + * @param _refundAddress Address that will receive the `excessFeeRefund` amount if any + * @param _gasLimit Maximum amount of gas required for the `_messages` to be executed + * @param _maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee + * @param _gasPriceBid Gas price bid for L2 execution * @return uint256 Id of the retryable ticket that was created */ -function processCalls( - uint256 nonce, - CallLib.Call[] calldata calls, - address sender, +function processMessage( + bytes32 messageId, + address from, + address to, + bytes calldata data, + address refundAddress, uint256 gasLimit, uint256 maxSubmissionCost, uint256 gasPriceBid @@ -120,63 +146,87 @@ function processCalls( ``` -#### Arbitrum Relay Example +#### Arbitrum Dispatch Example ```typescript -const greeting = 'Hello from L1'; -const callData = new Interface(['function setGreeting(string)']).encodeFunctionData( - 'setGreeting', - [greeting], -); - -const calls: ICrossChainRelayer.CallStruct[] = [ - { - target: greeterAddress, - data: callData, - }, -]; + const greeterAddress = await getContractAddress('Greeter', ARBITRUM_GOERLI_CHAIN_ID, 'Forge'); + + const greeting = 'Hello from L1'; + const messageData = new Interface(['function setGreeting(string)']).encodeFunctionData( + 'setGreeting', + [greeting], + ); + + const nextNonce = (await messageDispatcherArbitrum.nonce()).add(1); + + const encodedMessageId = keccak256( + defaultAbiCoder.encode( + ['uint256', 'address', 'address', 'bytes'], + [nextNonce, deployer, greeterAddress, messageData], + ), + ); + + const executeMessageData = new Interface([ + 'function executeMessage(address,bytes,bytes32,uint256,address)', + ]).encodeFunctionData('executeMessage', [ + greeterAddress, + messageData, + encodedMessageId, + GOERLI_CHAIN_ID, + deployer, + ]); ... -const maxGas = await l1ToL2MessageGasEstimate.estimateRetryableTicketGasLimit({ - from: crossChainRelayerArbitrumAddress, - to: crossChainExecutorAddress, - l2CallValue: BigNumber.from(0), - excessFeeRefundAddress: deployer, - callValueRefundAddress: deployer, - data: executeCallsData, -}); - -await crossChainRelayerArbitrum.relayCalls(calls, maxGas); + const { deposit, gasLimit, maxSubmissionCost } = await l1ToL2MessageGasEstimate.estimateAll( + { + from: messageDispatcherArbitrumAddress, + to: messageExecutorAddress, + l2CallValue: BigNumber.from(0), + excessFeeRefundAddress: deployer, + callValueRefundAddress: deployer, + data: executeMessageData, + }, + baseFee, + l1Provider, + ); + + await messageDispatcherArbitrum.dispatchMessage( + ARBITRUM_GOERLI_CHAIN_ID, + greeterAddress, + messageData, + ); ... -await crossChainRelayerArbitrum.processCalls( - relayCallsNonce, - calls, - deployer, - maxGas, - maxSubmissionCost, - gasPriceBid, - { - value: callValue, - }, -); +await messageDispatcherArbitrum.processMessage( + messageId, + deployer, + greeterAddress, + messageData, + deployer, + gasLimit, + maxSubmissionCost, + gasPriceBid, + { + value: deposit, + }, + ); ``` Code: [script/bridge/BridgeToArbitrumGoerli.ts](script/bridge/BridgeToArbitrumGoerli.ts) ## Execution -#### Execute calls +#### Execute message -Once the message has been bridged it will be executed by the [CrossChainExecutor](./src/interfaces/ICrossChainExecutor.sol) contract. +Once the message has been bridged it will be executed by the [MessageExecutor](./src/interfaces/IMessageExecutor.sol) contract. -#### Authenticate calls +#### Authenticate messages -To ensure that the calls originate from the CrossChainExecutor contract, your contracts can inherit from the [ExecutorAware](./src/abstract/ExecutorAware.sol) abstract contract. +To ensure that the messages originate from the MessageExecutor contract, your contracts can inherit from the [ExecutorAware](./src/abstract/ExecutorAware.sol) abstract contract. -It makes use of [EIP-2771](https://eips.ethereum.org/EIPS/eip-2771) to authenticate the call forwarder (i.e. the CrossChainExecutor) and has helper functions to extract from the calldata the original sender and the nonce of the relayed call. +It makes use of [EIP-2771](https://eips.ethereum.org/EIPS/eip-2771) to authenticate the message forwarder (i.e. the MessageExecutor) and has helper functions to extract from the calldata the original sender and the `messageId` of the dispatched message. ```solidity /** @@ -186,44 +236,61 @@ It makes use of [EIP-2771](https://eips.ethereum.org/EIPS/eip-2771) to authentic function isTrustedExecutor(address _executor) public view returns (bool); /** - * @notice Retrieve signer address from call data. - * @return _signer Address of the signer - */ -function _msgSender() internal view returns (address payable _signer); + * @notice Retrieve messageId from message data. + * @return _msgDataMessageId ID uniquely identifying the message that was executed + */ +function _messageId() internal pure returns (bytes32 _msgDataMessageId) + +/** + * @notice Retrieve fromChainId from message data. + * @return _msgDataFromChainId ID of the chain that dispatched the messages + */ +function _fromChainId() internal pure returns (uint256 _msgDataFromChainId); /** - * @notice Retrieve nonce from call data. - * @return _callDataNonce Nonce uniquely identifying the message that was executed + * @notice Retrieve signer address from message data. + * @return _signer Address of the signer */ -function _nonce() internal pure returns (uint256 _callDataNonce); +function _msgSender() internal view returns (address payable _signer); ``` ## Deployed Contracts -### Ethereum Goerli -> Arbitrum Goerli +### Mainnet + +#### Ethereum -> Optimism + +| Network | Contract | Address | +| -------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| Ethereum | [EthereumToOptimismDispatcher.sol](./src/ethereum-arbitrum/EthereumToOptimismDispatcher.sol) | [0xa8f85bAB964D7e6bE938B54Bf4b29A247A88CD9d](https://etherscan.io/address/0xa8f85bAB964D7e6bE938B54Bf4b29A247A88CD9d) | +| Optimism | [EthereumToOptimismExecutor](./src/ethereum-arbitrum/EthereumToOptimismExecutor.sol) | [0x890a87E71E731342a6d10e7628bd1F0733ce3296](https://optimistic.etherscan.io/address/0x890a87E71E731342a6d10e7628bd1F0733ce3296) | + +### Testnet + +#### Ethereum Goerli -> Arbitrum Goerli -| Network | Contract | Address | -| --------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| Ethereum Goerli | [EthereumToArbitrumRelayer.sol](./src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol) | [0x961f05163dBB383EF39323D04e04aE5b7cc5A7b2](https://goerli.etherscan.io/address/0x961f05163dBB383EF39323D04e04aE5b7cc5A7b2) | -| Arbitrum Goerli | [EthereumToArbitrumExecutor](./src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol) | [0x9c53fb1D0AE3b7EDd6da970Fa3dC70e8d2092723](https://goerli.arbiscan.io/address/0x9c53fb1D0AE3b7EDd6da970Fa3dC70e8d2092723) | -| Arbitrum Goerli | [Greeter](./test/contracts/Greeter.sol) | [0xcCD175Fe1f7389A06C40765eaf33180295216460](https://goerli.arbiscan.io/address/0xcCD175Fe1f7389A06C40765eaf33180295216460) | +| Network | Contract | Address | +| --------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| Ethereum Goerli | [EthereumToArbitrumDispatcher.sol](./src/ethereum-arbitrum/EthereumToArbitrumDispatcher.sol) | [0xBc244773f71a2f897fAB5D5953AA052B8ff68670](https://goerli.etherscan.io/address/0xBc244773f71a2f897fAB5D5953AA052B8ff68670) | +| Arbitrum Goerli | [EthereumToArbitrumExecutor](./src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol) | [0xe7Ab52219631882f778120c1f19D6086ED390bE1](https://goerli.arbiscan.io/address/0xe7Ab52219631882f778120c1f19D6086ED390bE1) | +| Arbitrum Goerli | [Greeter](./test/contracts/Greeter.sol) | [0xA181dE5454daa63115e4A2f626E9268Cc812FcC1](https://goerli.arbiscan.io/address/0xA181dE5454daa63115e4A2f626E9268Cc812FcC1) | -### Ethereum Goerli -> Optimism Goerli +#### Ethereum Goerli -> Optimism Goerli -| Network | Contract | Address | -| --------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| Ethereum Goerli | [EthereumToOptimismRelayer.sol](./src/ethereum-optimism/EthereumToOptimismRelayer.sol) | [0xB577c479D6D7eC677dB6c349e6E23B7bfE303295](https://goerli.etherscan.io/address/0xB577c479D6D7eC677dB6c349e6E23B7bfE303295) | -| Optimism Goerli | [EthereumToOptimismExecutor](./src/ethereum-optimism/EthereumToOptimismExecutor.sol) | [0x7A4c111CEBfA573f785BFa4ED144f70b1ab519a0](https://goerli-optimism.etherscan.io/address/0x7A4c111CEBfA573f785BFa4ED144f70b1ab519a0) | -| Optimism Goerli | [Greeter](./test/contracts/Greeter.sol) | [0xd55052D3617f8ebd5DeEb7F0AC2D6f20d185Bc9d](https://goerli-optimism.etherscan.io/address/0xd55052D3617f8ebd5DeEb7F0AC2D6f20d185Bc9d) | +| Network | Contract | Address | +| --------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| Ethereum Goerli | [EthereumToOptimismDispatcher.sol](./src/ethereum-optimism/EthereumToOptimismDispatcher.sol) | [0x81F4056FFFa1C1fA870de40BC45c752260E3aD13](https://goerli.etherscan.io/address/0x81F4056FFFa1C1fA870de40BC45c752260E3aD13) | +| Optimism Goerli | [EthereumToOptimismExecutor](./src/ethereum-optimism/EthereumToOptimismExecutor.sol) | [0xc5165406dB791549f0D2423D1483c1EA10A3A206](https://goerli-optimism.etherscan.io/address/0xc5165406dB791549f0D2423D1483c1EA10A3A206) | +| Optimism Goerli | [Greeter](./test/contracts/Greeter.sol) | [0x50281C11B6a18d0613F507fD2E7a1ADd712De7D8](https://goerli-optimism.etherscan.io/address/0x50281C11B6a18d0613F507fD2E7a1ADd712De7D8) | -### Ethereum Goerli -> Polygon Mumbai +#### Ethereum Goerli -> Polygon Mumbai -| Network | Contract | Address | -| --------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| Ethereum Goerli | [EthereumToPolygonRelayer](./src/ethereum-polygon/EthereumToPolygonRelayer.sol) | [0xB867e4E65eb093dC86D9E4Fd6622dDA58583B7F1](https://goerli.etherscan.io/address/0xB867e4E65eb093dC86D9E4Fd6622dDA58583B7F1) | -| Polygon Mumbai | [EthereumToPolygonExecutor](./src/ethereum-polygon/EthereumToPolygonExecutor.sol) | [0x5A1Ca26f637dad188ea95A92C2b262226E2a2646](https://mumbai.polygonscan.com/address/0x5A1Ca26f637dad188ea95A92C2b262226E2a2646) | -| Polygon Mumbai | [Greeter](./test/contracts/Greeter.sol) | [0xe0B149a4fb0a40eC13531596f824cFa523445280](https://mumbai.polygonscan.com/address/0xe0B149a4fb0a40eC13531596f824cFa523445280) | +| Network | Contract | Address | +| --------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| Ethereum Goerli | [EthereumToPolygonDispatcher](./src/ethereum-polygon/EthereumToPolygonDispatcher.sol) | [0xBA8d8a0554dFd7F7CCf3cEB47a88d711e6a65F5b](https://goerli.etherscan.io/address/0xBA8d8a0554dFd7F7CCf3cEB47a88d711e6a65F5b) | +| Polygon Mumbai | [EthereumToPolygonExecutor](./src/ethereum-polygon/EthereumToPolygonExecutor.sol) | [0x784fFd1E27FA32804bD0a170dc7A277399AbD361](https://mumbai.polygonscan.com/address/0x784fFd1E27FA32804bD0a170dc7A277399AbD361) | +| Polygon Mumbai | [Greeter](./test/contracts/Greeter.sol) | [0x3b73dCeC4447DDB1303F9b766BbBeB87aFAf22a3](https://mumbai.polygonscan.com/address/0x3b73dCeC4447DDB1303F9b766BbBeB87aFAf22a3) | ## Development @@ -276,16 +343,16 @@ yarn test To run Arbitrum fork tests, use the following commands: -- Fork tests to relay calls from Ethereum to Arbitrum: +- Fork tests to dispatch messages from Ethereum to Arbitrum: ``` - yarn fork:startRelayCallsArbitrumMainnet + yarn fork:startDispatchMessageBatchArbitrumMainnet ``` -- Fork tests to execute calls on Arbitrum: +- Fork tests to execute messages on Arbitrum: ``` - yarn fork:startExecuteCallsArbitrumMainnet + yarn fork:startExecuteMessageBatchArbitrumMainnet ``` ### Coverage @@ -340,11 +407,11 @@ It takes about 15 minutes for the message to be bridged to Arbitrum Goerli. ##### Example transaction -| Network | Call | Transaction hash | -| --------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Ethereum Goerli | relayCalls | [0x34c62a91cf035c19393b90027b26bd4883e68e079f0814854a396b183ae03c28](https://goerli.etherscan.io/tx/0x34c62a91cf035c19393b90027b26bd4883e68e079f0814854a396b183ae03c28) | -| Ethereum Goerli | processCalls | [0xf028e7415e2bd88c63419b9ba519913cd063651d4f6a5c5f3b4a2d3d7c6ccb04](https://goerli.etherscan.io/tx/0xf028e7415e2bd88c63419b9ba519913cd063651d4f6a5c5f3b4a2d3d7c6ccb04) | -| Arbitrum Goerli | executeCalls | [0xcb418fb130f95035ee1cb17aa0759557b12e083bb00d9893663497fd7be8c197](https://goerli.arbiscan.io/tx/0xcb418fb130f95035ee1cb17aa0759557b12e083bb00d9893663497fd7be8c197) | +| Network | Message | Transaction hash | +| --------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Ethereum Goerli | dispatchMessage | [0xfdb983cad74d5d95c2ffdbb38cde50fefbe78280416bbe44de35485c213909d5](https://goerli.etherscan.io/tx/0xfdb983cad74d5d95c2ffdbb38cde50fefbe78280416bbe44de35485c213909d5) | +| Ethereum Goerli | processMessage | [0x4effcda5e729a2943a86bd1317a784644123388bb4fd7ea207e70ec3a360ab60](https://goerli.etherscan.io/tx/0x4effcda5e729a2943a86bd1317a784644123388bb4fd7ea207e70ec3a360ab60) | +| Arbitrum Goerli | executeMessage | [0x0883252887d34a4a545a20e252e55c712807d1707438cf6e8503a99a32357024](https://goerli.arbiscan.io/tx/0x0883252887d34a4a545a20e252e55c712807d1707438cf6e8503a99a32357024) | #### Ethereum Goerli to Optimism Goerli @@ -356,10 +423,10 @@ It takes about 5 minutes for the message to be bridged to Optimism Goerli. ##### Example transaction -| Network | Call | Transaction hash | -| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Ethereum Goerli | relayCalls | [0xe3864c4fa1f77fc0ca9ff5c5185582833049a4d8cc3cf4e30a6c53f49eaad53d](https://goerli.etherscan.io/tx/0xe3864c4fa1f77fc0ca9ff5c5185582833049a4d8cc3cf4e30a6c53f49eaad53d) | -| Optimism Goerli | executeCalls | [0x5f73e44b9fd601b0e0031ac87ad18092a8fc621963ec8a4447062baf799d982a](https://goerli-optimism.etherscan.io/tx/0x5f73e44b9fd601b0e0031ac87ad18092a8fc621963ec8a4447062baf799d982a) | +| Network | Message | Transaction hash | +| --------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Ethereum Goerli | dispatchMessage | [0xdaf3b8210294dc2414beefa14e56f47f638510031c4487443c58fd6a92c8f386](https://goerli.etherscan.io/tx/0xdaf3b8210294dc2414beefa14e56f47f638510031c4487443c58fd6a92c8f386) | +| Optimism Goerli | executeMessage | [https://goerli-optimism.etherscan.io/tx/0xa83813646e7978cea4f27b57688ce30e3622b135ca6c18489d0c8fa3ee297c5b](https://goerli-optimism.etherscan.io/tx/https://goerli-optimism.etherscan.io/tx/0xa83813646e7978cea4f27b57688ce30e3622b135ca6c18489d0c8fa3ee297c5b) | #### Ethereum Goerli to Polygon Mumbai @@ -371,10 +438,10 @@ It takes about 30 minutes for the message to be bridged to Mumbai. ##### Example transaction -| Network | Call | Transaction hash | -| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Ethereum Goerli | relayCalls | [0x069e20553358d4faba80c9b699e3be6d2331608979733260469f6c5375140058](https://goerli.etherscan.io/tx/0x069e20553358d4faba80c9b699e3be6d2331608979733260469f6c5375140058) | -| Polygon Mumbai | executeCalls | [0x590babab7b396ee3cae566a894f34c32daee3832d9a206ccc53576b88de49f4a](https://mumbai.polygonscan.com/tx/0x590babab7b396ee3cae566a894f34c32daee3832d9a206ccc53576b88de49f4a) | +| Network | Message | Transaction hash | +| --------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Ethereum Goerli | dispatchMessage | [0x856355f3df4f94bae2075abbce57163af95637ae9c65bbe231f170d9cdf251c9](https://goerli.etherscan.io/tx/0x856355f3df4f94bae2075abbce57163af95637ae9c65bbe231f170d9cdf251c9) | +| Polygon Mumbai | executeMessage | [0x78aff3ff10b43169ce468bf88da79560724ea292290c336cd84a43fdd8441c52](https://mumbai.polygonscan.com/tx/0x78aff3ff10b43169ce468bf88da79560724ea292290c336cd84a43fdd8441c52) | ### Code quality diff --git a/lcov.info b/lcov.info index 20e6bac..68d81ca 100644 --- a/lcov.info +++ b/lcov.info @@ -1,184 +1,327 @@ TN: SF:src/abstract/ExecutorAware.sol FN:37,ExecutorAware.isTrustedExecutor -FN:47,ExecutorAware._msgSender -FN:61,ExecutorAware._nonce -FNDA:4,ExecutorAware._nonce -FNDA:4,ExecutorAware._msgSender +FN:47,ExecutorAware._messageId +FN:61,ExecutorAware._fromChainId +FN:75,ExecutorAware._msgSender +FNDA:9,ExecutorAware._messageId FNDA:0,ExecutorAware.isTrustedExecutor -FNF:3 -FNH:2 -DA:38,10 -DA:48,4 -DA:50,4 -DA:52,4 -DA:64,4 -DA:66,4 -LF:6 -LH:6 +FNDA:9,ExecutorAware._fromChainId +FNDA:9,ExecutorAware._msgSender +FNF:4 +FNH:3 +DA:38,20 +DA:50,9 +DA:52,9 +DA:64,9 +DA:66,9 +DA:76,9 +DA:78,9 +DA:80,9 +LF:8 +LH:8 +end_of_record +TN: +SF:src/ethereum-arbitrum/EthereumToArbitrumDispatcher.sol +FN:86,MessageDispatcherArbitrum.dispatchMessage +FN:104,MessageDispatcherArbitrum.dispatchMessageBatch +FN:135,MessageDispatcherArbitrum.processMessage +FN:184,MessageDispatcherArbitrum.processMessageBatch +FN:228,MessageDispatcherArbitrum.setExecutor +FN:242,MessageDispatcherArbitrum.getMessageTxHash +FN:259,MessageDispatcherArbitrum.getMessageBatchTxHash +FN:268,MessageDispatcherArbitrum.getMessageExecutorAddress +FN:284,MessageDispatcherArbitrum._getMessageTxHash +FN:301,MessageDispatcherArbitrum._getMessageBatchTxHash +FN:314,MessageDispatcherArbitrum._checkToChainId +FN:325,MessageDispatcherArbitrum._checkProcessParams +FN:334,MessageDispatcherArbitrum._incrementNonce +FN:355,MessageDispatcherArbitrum._createRetryableTicket +FNDA:2,MessageDispatcherArbitrum._createRetryableTicket +FNDA:3,MessageDispatcherArbitrum.getMessageTxHash +FNDA:4,MessageDispatcherArbitrum.dispatchMessageBatch +FNDA:6,MessageDispatcherArbitrum._incrementNonce +FNDA:3,MessageDispatcherArbitrum.dispatchMessage +FNDA:10,MessageDispatcherArbitrum._checkToChainId +FNDA:3,MessageDispatcherArbitrum.getMessageExecutorAddress +FNDA:13,MessageDispatcherArbitrum.setExecutor +FNDA:11,MessageDispatcherArbitrum._getMessageBatchTxHash +FNDA:7,MessageDispatcherArbitrum._getMessageTxHash +FNDA:4,MessageDispatcherArbitrum._checkProcessParams +FNDA:2,MessageDispatcherArbitrum.processMessage +FNDA:4,MessageDispatcherArbitrum.processMessageBatch +FNDA:3,MessageDispatcherArbitrum.getMessageBatchTxHash +FNF:14 +FNH:14 +DA:91,3 +DA:93,2 +DA:94,2 +DA:96,2 +DA:98,2 +DA:100,2 +DA:108,4 +DA:110,4 +DA:111,4 +DA:113,4 +DA:115,4 +DA:117,4 +DA:145,2 +DA:150,1 +DA:151,1 +DA:153,1 +DA:155,1 +DA:165,1 +DA:167,1 +DA:193,4 +DA:198,3 +DA:199,3 +DA:201,1 +DA:208,1 +DA:218,1 +DA:220,1 +DA:229,13 +DA:230,12 +DA:248,3 +DA:264,3 +DA:269,3 +DA:270,2 +DA:290,7 +DA:306,11 +DA:315,10 +DA:326,4 +DA:327,3 +DA:336,6 +DA:339,6 +DA:364,2 +LF:40 +LH:40 end_of_record TN: SF:src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol -FN:31,CrossChainExecutorArbitrum.executeCalls -FN:52,CrossChainExecutorArbitrum.setRelayer -FN:64,CrossChainExecutorArbitrum._isAuthorized -FNDA:4,CrossChainExecutorArbitrum._isAuthorized -FNDA:4,CrossChainExecutorArbitrum.executeCalls -FNDA:6,CrossChainExecutorArbitrum.setRelayer -FNF:3 -FNH:3 -DA:36,4 -DA:37,4 -DA:39,3 -DA:40,3 -DA:42,3 -DA:44,2 -DA:53,6 +FN:41,MessageExecutorArbitrum.executeMessage +FN:60,MessageExecutorArbitrum.executeMessageBatch +FN:82,MessageExecutorArbitrum.setDispatcher +FN:94,MessageExecutorArbitrum._isAuthorized +FNDA:6,MessageExecutorArbitrum.executeMessageBatch +FNDA:6,MessageExecutorArbitrum.executeMessage +FNDA:12,MessageExecutorArbitrum._isAuthorized +FNDA:13,MessageExecutorArbitrum.setDispatcher +FNF:4 +FNH:4 +DA:48,6 +DA:49,6 +DA:51,5 +DA:52,5 DA:54,5 -DA:65,4 -LF:9 -LH:9 +DA:56,2 +DA:66,6 +DA:67,6 +DA:69,5 +DA:70,5 +DA:72,5 +DA:74,2 +DA:83,13 +DA:84,12 +DA:95,12 +LF:15 +LH:15 end_of_record TN: -SF:src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol -FN:67,CrossChainRelayerArbitrum.relayCalls -FN:101,CrossChainRelayerArbitrum.processCalls -FN:139,CrossChainRelayerArbitrum.setExecutor -FN:153,CrossChainRelayerArbitrum.getTxHash -FN:173,CrossChainRelayerArbitrum._getTxHash -FNDA:8,CrossChainRelayerArbitrum._getTxHash -FNDA:7,CrossChainRelayerArbitrum.setExecutor -FNDA:3,CrossChainRelayerArbitrum.relayCalls -FNDA:4,CrossChainRelayerArbitrum.getTxHash -FNDA:2,CrossChainRelayerArbitrum.processCalls -FNF:5 -FNH:5 -DA:72,3 -DA:74,3 -DA:75,1 -DA:78,2 -DA:80,2 -DA:82,2 -DA:84,2 -DA:86,2 -DA:109,2 -DA:111,1 -DA:118,1 -DA:129,1 -DA:131,1 -DA:140,7 -DA:141,6 -DA:159,4 -DA:179,8 -LF:17 -LH:17 +SF:src/ethereum-optimism/EthereumToOptimismDispatcher.sol +FN:54,MessageDispatcherOptimism.dispatchMessage +FN:82,MessageDispatcherOptimism.dispatchMessageBatch +FN:108,MessageDispatcherOptimism.getMessageExecutorAddress +FN:117,MessageDispatcherOptimism.setExecutor +FN:129,MessageDispatcherOptimism._checkToChainId +FN:138,MessageDispatcherOptimism._checkExecutor +FN:148,MessageDispatcherOptimism._getMessageExecutorAddress +FN:157,MessageDispatcherOptimism._incrementNonce +FN:170,MessageDispatcherOptimism._sendMessage +FNDA:4,MessageDispatcherOptimism._checkExecutor +FNDA:3,MessageDispatcherOptimism.dispatchMessage +FNDA:7,MessageDispatcherOptimism._checkToChainId +FNDA:3,MessageDispatcherOptimism.dispatchMessageBatch +FNDA:2,MessageDispatcherOptimism._sendMessage +FNDA:7,MessageDispatcherOptimism._getMessageExecutorAddress +FNDA:1,MessageDispatcherOptimism.getMessageExecutorAddress +FNDA:2,MessageDispatcherOptimism._incrementNonce +FNDA:8,MessageDispatcherOptimism.setExecutor +FNF:9 +FNH:9 +DA:59,3 +DA:60,2 +DA:62,1 +DA:63,1 +DA:65,1 +DA:76,1 +DA:78,1 +DA:86,3 +DA:87,2 +DA:89,1 +DA:90,1 +DA:92,1 +DA:102,1 +DA:104,1 +DA:109,1 +DA:118,8 +DA:119,8 +DA:130,7 +DA:139,4 +DA:149,7 +DA:150,5 +DA:159,2 +DA:162,2 +DA:171,2 +LF:24 +LH:24 end_of_record TN: SF:src/ethereum-optimism/EthereumToOptimismExecutor.sol -FN:45,CrossChainExecutorOptimism.executeCalls -FN:66,CrossChainExecutorOptimism.setRelayer -FN:77,CrossChainExecutorOptimism._isAuthorized -FNDA:2,CrossChainExecutorOptimism.executeCalls -FNDA:2,CrossChainExecutorOptimism._isAuthorized -FNDA:6,CrossChainExecutorOptimism.setRelayer -FNF:3 -FNH:3 -DA:50,2 -DA:51,2 -DA:53,1 -DA:54,1 +FN:45,MessageExecutorOptimism.executeMessage +FN:64,MessageExecutorOptimism.executeMessageBatch +FN:86,MessageExecutorOptimism.setDispatcher +FN:97,MessageExecutorOptimism._isAuthorized +FNDA:2,MessageExecutorOptimism.executeMessage +FNDA:8,MessageExecutorOptimism.setDispatcher +FNDA:4,MessageExecutorOptimism._isAuthorized +FNDA:2,MessageExecutorOptimism.executeMessageBatch +FNF:4 +FNH:4 +DA:52,2 +DA:53,2 +DA:55,1 DA:56,1 DA:58,1 -DA:67,6 -DA:68,6 -DA:78,2 -DA:80,2 -LF:10 -LH:10 +DA:60,1 +DA:70,2 +DA:71,2 +DA:73,1 +DA:74,1 +DA:76,1 +DA:78,1 +DA:87,8 +DA:88,8 +DA:98,4 +DA:100,4 +LF:16 +LH:16 end_of_record TN: -SF:src/ethereum-optimism/EthereumToOptimismRelayer.sol -FN:49,CrossChainRelayerOptimism.relayCalls -FN:85,CrossChainRelayerOptimism.setExecutor -FNDA:2,CrossChainRelayerOptimism.relayCalls -FNDA:6,CrossChainRelayerOptimism.setExecutor -FNF:2 -FNH:2 -DA:54,2 -DA:56,2 -DA:57,1 -DA:60,1 +SF:src/ethereum-polygon/EthereumToPolygonDispatcher.sol +FN:51,MessageDispatcherPolygon.dispatchMessage +FN:72,MessageDispatcherPolygon.dispatchMessageBatch +FN:91,MessageDispatcherPolygon.getMessageExecutorAddress +FN:103,MessageDispatcherPolygon._checkToChainId +FN:113,MessageDispatcherPolygon._checkDispatchParams +FN:122,MessageDispatcherPolygon._incrementNonce +FN:135,MessageDispatcherPolygon._processMessageFromChild +FNDA:0,MessageDispatcherPolygon._processMessageFromChild +FNDA:6,MessageDispatcherPolygon._checkToChainId +FNDA:2,MessageDispatcherPolygon._incrementNonce +FNDA:2,MessageDispatcherPolygon.getMessageExecutorAddress +FNDA:6,MessageDispatcherPolygon._checkDispatchParams +FNDA:3,MessageDispatcherPolygon.dispatchMessage +FNDA:3,MessageDispatcherPolygon.dispatchMessageBatch +FNF:7 +FNH:6 +DA:56,3 +DA:58,1 +DA:59,1 +DA:61,1 DA:62,1 DA:64,1 -DA:75,1 -DA:77,1 -DA:86,6 -DA:87,6 -LF:10 -LH:10 +DA:66,1 +DA:68,1 +DA:76,3 +DA:78,1 +DA:79,1 +DA:81,1 +DA:83,1 +DA:85,1 +DA:87,1 +DA:92,2 +DA:93,1 +DA:104,6 +DA:114,6 +DA:115,4 +DA:124,2 +DA:127,2 +LF:22 +LH:22 end_of_record TN: SF:src/ethereum-polygon/EthereumToPolygonExecutor.sol -FN:44,CrossChainExecutorPolygon._processMessageFromRoot -FNDA:2,CrossChainExecutorPolygon._processMessageFromRoot +FN:61,MessageExecutorPolygon._processMessageFromRoot +FNDA:6,MessageExecutorPolygon._processMessageFromRoot FNF:1 FNH:1 -DA:49,2 -DA:54,2 -DA:55,2 -DA:57,2 -DA:59,1 -LF:5 -LH:5 -end_of_record -TN: -SF:src/ethereum-polygon/EthereumToPolygonRelayer.sol -FN:45,CrossChainRelayerPolygon.relayCalls -FN:74,CrossChainRelayerPolygon._processMessageFromChild -FNDA:2,CrossChainRelayerPolygon.relayCalls -FNDA:0,CrossChainRelayerPolygon._processMessageFromChild -FNF:2 -FNH:1 -DA:50,2 -DA:52,2 -DA:53,1 -DA:56,1 -DA:58,1 -DA:60,1 -DA:62,1 -DA:64,1 -LF:8 -LH:8 +DA:66,6 +DA:71,6 +DA:73,6 +DA:74,6 +DA:76,6 +DA:77,3 +DA:78,3 +DA:80,1 +DA:82,3 +DA:84,1 +LF:10 +LH:10 end_of_record TN: -SF:src/libraries/CallLib.sol -FN:49,CallLib.executeCalls -FNDA:6,CallLib.executeCalls -FNF:1 -FNH:1 -DA:55,6 -DA:56,1 -DA:59,5 -DA:61,5 -DA:62,5 -DA:64,5 -DA:68,5 -DA:69,1 -LF:8 -LH:8 +SF:src/libraries/MessageLib.sol +FN:59,MessageLib.computeMessageId +FN:75,MessageLib.computeMessageBatchId +FN:91,MessageLib.encodeMessage +FN:116,MessageLib.encodeMessageBatch +FN:142,MessageLib.executeMessage +FN:174,MessageLib.executeMessageBatch +FN:209,MessageLib._requireContract +FNDA:9,MessageLib.executeMessage +FNDA:4,MessageLib.computeMessageId +FNDA:6,MessageLib.computeMessageBatchId +FNDA:17,MessageLib._requireContract +FNDA:2,MessageLib.encodeMessageBatch +FNDA:2,MessageLib.encodeMessage +FNDA:9,MessageLib.executeMessageBatch +FNF:7 +FNH:7 +DA:65,4 +DA:80,6 +DA:98,2 +DA:122,2 +DA:150,9 +DA:151,1 +DA:154,8 +DA:156,6 +DA:160,6 +DA:161,2 +DA:181,9 +DA:182,1 +DA:185,8 +DA:187,8 +DA:188,9 +DA:189,9 +DA:191,7 +DA:195,7 +DA:196,2 +DA:200,5 +DA:210,17 +LF:21 +LH:21 end_of_record TN: SF:test/contracts/Greeter.sol -FN:23,Greeter.greet -FN:27,Greeter.setGreeting -FNDA:6,Greeter.setGreeting -FNDA:4,Greeter.greet +FN:22,Greeter.greet +FN:26,Greeter.setGreeting +FNDA:10,Greeter.greet +FNDA:11,Greeter.setGreeting FNF:2 FNH:2 -DA:24,4 -DA:28,6 -DA:30,4 -DA:31,4 +DA:23,10 +DA:27,11 +DA:29,9 +DA:30,9 LF:4 LH:4 end_of_record diff --git a/package.json b/package.json index 55a837a..76b77af 100644 --- a/package.json +++ b/package.json @@ -1,62 +1,70 @@ { "name": "@pooltogether/erc5164", - "description": "Implementation of the ERC5164 standard to communicate cross chain", - "version": "1.0.0", + "description": "EIP-5164 implementation allowing contracts to send cross-chain messages", + "version": "0.1.0", "author": { "name": "PoolTogether Inc.", - "url": "https://github.com/pooltogether" + "url": "https://github.com/pooltogether/ERC5164" }, "scripts": { + "clean": "forge clean", "compile": "forge build", - "postinstall": "husky install", + "coverage": "forge coverage --report lcov && lcov --remove lcov.info -o lcov.info 'test/contracts/mock/*' 'test/fork' 'test/unit' 'script/*' && genhtml lcov.info -o coverage", "format": "prettier --config .prettierrc --write \"**/*.{ts,js,json,md,sol,yml}\"", "format:file": "prettier --config .prettierrc --write", - "lint-staged": "lint-staged", "hint": "solhint --config \"./.solhint.json\" \"{src,test}/**/*.sol\"", - "deploy:relayerOptimismGoerli": "forge script script/deploy/DeployToOptimismGoerli.s.sol:DeployCrossChainRelayerToGoerli --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY -vv", - "deploy:executorOptimismGoerli": "forge script script/deploy/DeployToOptimismGoerli.s.sol:DeployCrossChainExecutorToOptimismGoerli --rpc-url $OPTIMISM_GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY -vv", - "deploy:greeterOptimismGoerli": "forge script script/deploy/DeployToOptimismGoerli.s.sol:DeployGreeterToOptimismGoerli --ffi --rpc-url $OPTIMISM_GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY -vv", - "set:executorOptimismGoerli": "forge script script/deploy/DeployToOptimismGoerli.s.sol:SetCrossChainExecutor --ffi --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", - "set:relayerOptimismGoerli": "forge script script/deploy/DeployToOptimismGoerli.s.sol:SetCrossChainRelayer --ffi --rpc-url $OPTIMISM_GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", - "deploy:optimismGoerli": "yarn deploy:relayerOptimismGoerli && yarn deploy:executorOptimismGoerli && yarn set:executorOptimismGoerli && yarn set:relayerOptimismGoerli && yarn deploy:greeterOptimismGoerli", + "lint-staged": "lint-staged", + "postinstall": "husky install", + "prepack": "pinst --disable && yarn clean && yarn compile && yarn typechain", + "postpack": "pinst --enable", + "test": "forge test -vv --gas-report", + "typechain": "typechain --target=ethers-v5 \"./out/**/*.json\" --out-dir \"./types\"", + "deploy:dispatcherOptimism": "forge script script/deploy/DeployToOptimism.s.sol:DeployMessageDispatcherToEthereumMainnet --rpc-url $MAINNET_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY -vv", + "deploy:executorOptimism": "forge script script/deploy/DeployToOptimism.s.sol:DeployMessageExecutorToOptimism --rpc-url $OPTIMISM_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY -vv", + "set:executorOptimism": "forge script script/deploy/DeployToOptimism.s.sol:SetMessageExecutor --ffi --rpc-url $MAINNET_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", + "set:dispatcherOptimism": "forge script script/deploy/DeployToOptimism.s.sol:SetMessageDispatcher --ffi --rpc-url $OPTIMISM_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", + "deploy:optimism": "yarn deploy:dispatcherOptimism && yarn deploy:executorOptimism && yarn set:executorOptimism && yarn set:dispatcherOptimism", + "deploy:dispatcherOptimismGoerli": "forge script script/deploy/DeployToOptimismGoerli.s.sol:DeployMessageDispatcherToGoerli --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY -vv", + "deploy:executorOptimismGoerli": "forge script script/deploy/DeployToOptimismGoerli.s.sol:DeployMessageExecutorToOptimismGoerli --rpc-url $OPTIMISM_GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY -vv", + "deploy:greeterOptimismGoerli": "forge script script/deploy/DeployToOptimismGoerli.s.sol:DeployGreeterToOptimismGoerli --ffi --rpc-url $OPTIMISM_GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY -vv", + "set:executorOptimismGoerli": "forge script script/deploy/DeployToOptimismGoerli.s.sol:SetMessageExecutor --ffi --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", + "set:dispatcherOptimismGoerli": "forge script script/deploy/DeployToOptimismGoerli.s.sol:SetMessageDispatcher --ffi --rpc-url $OPTIMISM_GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", + "deploy:optimismGoerli": "yarn deploy:dispatcherOptimismGoerli && yarn deploy:executorOptimismGoerli && yarn set:executorOptimismGoerli && yarn set:dispatcherOptimismGoerli && yarn deploy:greeterOptimismGoerli", "bridge:optimismGoerli": "forge script script/bridge/BridgeToOptimismGoerli.s.sol --ffi --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vvvv", - "deploy:relayerPolygonGoerli": "forge script script/deploy/DeployToMumbai.s.sol:DeployCrossChainRelayerToGoerli --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY -vv", - "deploy:executorPolygonGoerli": "forge script script/deploy/DeployToMumbai.s.sol:DeployCrossChainExecutorToMumbai --rpc-url $MUMBAI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $POLYGONSCAN_API_KEY -vv", - "deploy:greeterPolygonGoerli": "forge script script/deploy/DeployToMumbai.s.sol:DeployGreeterToMumbai --ffi --rpc-url $MUMBAI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $POLYGONSCAN_API_KEY -vv", - "set:executorPolygonGoerli": "forge script script/deploy/DeployToMumbai.s.sol:SetFxChildTunnel --ffi --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", - "set:relayerPolygonGoerli": "forge script script/deploy/DeployToMumbai.s.sol:SetFxRootTunnel --ffi --rpc-url $MUMBAI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", - "deploy:mumbai": "yarn deploy:relayerPolygonGoerli && yarn deploy:executorPolygonGoerli && yarn set:executorPolygonGoerli && yarn set:relayerPolygonGoerli && yarn deploy:greeterPolygonGoerli", + "deploy:dispatcherPolygonGoerli": "forge script script/deploy/DeployToMumbai.s.sol:DeployMessageDispatcherToGoerli --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY -vv", + "deploy:executorPolygonGoerli": "forge script script/deploy/DeployToMumbai.s.sol:DeployMessageExecutorToMumbai --rpc-url $MUMBAI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $POLYGONSCAN_API_KEY -vv", + "deploy:greeterPolygonGoerli": "forge script script/deploy/DeployToMumbai.s.sol:DeployGreeterToMumbai --ffi --rpc-url $MUMBAI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $POLYGONSCAN_API_KEY -vv", + "set:executorPolygonGoerli": "forge script script/deploy/DeployToMumbai.s.sol:SetFxChildTunnel --ffi --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", + "set:dispatcherPolygonGoerli": "forge script script/deploy/DeployToMumbai.s.sol:SetFxRootTunnel --ffi --rpc-url $MUMBAI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", + "deploy:mumbai": "yarn deploy:dispatcherPolygonGoerli && yarn deploy:executorPolygonGoerli && yarn set:executorPolygonGoerli && yarn set:dispatcherPolygonGoerli && yarn deploy:greeterPolygonGoerli", "bridge:mumbai": "forge script script/bridge/BridgeToMumbai.s.sol:BridgeToMumbai --ffi --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vvvv", - "deploy:relayerArbitrumGoerli": "forge script script/deploy/DeployToArbitrumGoerli.s.sol:DeployCrossChainRelayerToGoerli --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY -vv", - "deploy:executorArbitrumGoerli": "forge script script/deploy/DeployToArbitrumGoerli.s.sol:DeployCrossChainExecutorToArbitrumGoerli --rpc-url $ARBITRUM_GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $ARBITRUM_ETHERSCAN_API_KEY -vv", - "deploy:greeterArbitrumGoerli": "forge script script/deploy/DeployToArbitrumGoerli.s.sol:DeployGreeterToArbitrumGoerli --ffi --rpc-url $ARBITRUM_GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $ARBITRUM_ETHERSCAN_API_KEY -vv", - "set:executorArbitrumGoerli": "forge script script/deploy/DeployToArbitrumGoerli.s.sol:SetCrossChainExecutor --ffi --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", - "set:relayerArbitrumGoerli": "forge script script/deploy/DeployToArbitrumGoerli.s.sol:SetCrossChainRelayer --ffi --rpc-url $ARBITRUM_GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", - "deploy:arbitrumGoerli": "yarn deploy:relayerArbitrumGoerli && yarn deploy:executorArbitrumGoerli && yarn set:executorArbitrumGoerli && yarn set:relayerArbitrumGoerli && yarn deploy:greeterArbitrumGoerli", + "deploy:dispatcherArbitrumGoerli": "forge script script/deploy/DeployToArbitrumGoerli.s.sol:DeployMessageDispatcherToGoerli --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY -vv", + "deploy:executorArbitrumGoerli": "forge script script/deploy/DeployToArbitrumGoerli.s.sol:DeployMessageExecutorToArbitrumGoerli --rpc-url $ARBITRUM_GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $ARBITRUM_ETHERSCAN_API_KEY -vv", + "deploy:greeterArbitrumGoerli": "forge script script/deploy/DeployToArbitrumGoerli.s.sol:DeployGreeterToArbitrumGoerli --ffi --rpc-url $ARBITRUM_GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast --verify --etherscan-api-key $ARBITRUM_ETHERSCAN_API_KEY -vv", + "set:executorArbitrumGoerli": "forge script script/deploy/DeployToArbitrumGoerli.s.sol:SetMessageExecutor --ffi --rpc-url $GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", + "set:dispatcherArbitrumGoerli": "forge script script/deploy/DeployToArbitrumGoerli.s.sol:SetMessageDispatcher --ffi --rpc-url $ARBITRUM_GOERLI_RPC_URL --private-key $HDWALLET_PRIVATE_KEY --broadcast -vv", + "deploy:arbitrumGoerli": "yarn deploy:dispatcherArbitrumGoerli && yarn deploy:executorArbitrumGoerli && yarn set:executorArbitrumGoerli && yarn set:dispatcherArbitrumGoerli && yarn deploy:greeterArbitrumGoerli", "bridge:arbitrumGoerli": "CHAIN_ID=5 HARDHAT_NETWORK=goerli ts-node --files script/bridge/BridgeToArbitrumGoerli.ts", - "test": "forge test -vv --gas-report", - "coverage": "forge coverage --report lcov && lcov --remove lcov.info -o lcov.info 'test/contracts/mock/*' 'test/fork' 'test/unit' 'script/*' && genhtml lcov.info -o coverage", - "typechain": "typechain --target=ethers-v5 \"./out/**/*.json\" --out-dir \"./types\"", "fork:startMainnet": "CHAIN_ID=1 hardhat node --no-reset --no-deploy --port 8545", "fork:startArbitrum": "CHAIN_ID=42161 hardhat node --no-reset --no-deploy --port 8546", "fork:impersonateMainnet": "CHAIN_ID=1 hardhat --network localhostMainnet fork:impersonate", "fork:distributeMainnet": "CHAIN_ID=1 hardhat --network localhostMainnet fork:distribute", "fork:impersonateArbitrum": "CHAIN_ID=42161 hardhat --network localhostArbitrum fork:impersonate", "fork:distributeArbitrum": "CHAIN_ID=42161 hardhat --network localhostArbitrum fork:distribute", - "fork:deployRelayerArbitrumMainnet": "CHAIN_ID=1 hardhat --network localhostMainnet fork:deploy-relayer", + "fork:deployDispatcherArbitrumMainnet": "CHAIN_ID=1 hardhat --network localhostMainnet fork:deploy-dispatcher", "fork:deployExecutorArbitrumMainnet": "CHAIN_ID=42161 hardhat --network localhostArbitrum fork:deploy-executor", "fork:deployGreeterArbitrumMainnet": "CHAIN_ID=42161 hardhat --network localhostArbitrum fork:deploy-greeter", "fork:setExecutorArbitrumMainnet": "CHAIN_ID=1 hardhat --network localhostMainnet fork:set-executor", - "fork:setRelayerArbitrumMainnet": "CHAIN_ID=42161 hardhat --network localhostArbitrum fork:set-relayer", - "fork:relayCallsArbitrumMainnet": "CHAIN_ID=1 hardhat --network localhostMainnet fork:relay-calls", - "fork:executeCallsArbitrumMainnet": "CHAIN_ID=42161 hardhat --network localhostArbitrum fork:execute-calls", - "fork:runDeployMainnet": "wait-on tcp:8545 && yarn fork:impersonateMainnet && yarn fork:distributeMainnet && yarn fork:deployRelayerArbitrumMainnet", + "fork:setDispatcherArbitrumMainnet": "CHAIN_ID=42161 hardhat --network localhostArbitrum fork:set-dispatcher", + "fork:dispatchMessageBatchArbitrumMainnet": "CHAIN_ID=1 hardhat --network localhostMainnet fork:dispatch-message-batch", + "fork:executeMessageBatchArbitrumMainnet": "CHAIN_ID=42161 hardhat --network localhostArbitrum fork:execute-message-batch", + "fork:runDeployMainnet": "wait-on tcp:8545 && yarn fork:impersonateMainnet && yarn fork:distributeMainnet && yarn fork:deployDispatcherArbitrumMainnet", "fork:runDeployArbitrum": "wait-on tcp:8546 && yarn fork:impersonateArbitrum && yarn fork:distributeArbitrum && yarn fork:deployExecutorArbitrumMainnet && yarn fork:deployGreeterArbitrumMainnet", "fork:rmLocalhostDeployments": "rm -rf ./deployments/localhostMainnet && rm -rf ./deployments/localhostArbitrum", - "fork:runRelayCallsArbitrumMainnet": "npm-run-all -s fork:runDeployMainnet fork:runDeployArbitrum fork:setExecutorArbitrumMainnet fork:setRelayerArbitrumMainnet fork:relayCallsArbitrumMainnet", - "fork:startRelayCallsArbitrumMainnet": "yarn fork:rmLocalhostDeployments && npm-run-all -p -r fork:startMainnet fork:startArbitrum fork:runRelayCallsArbitrumMainnet", - "fork:runExecuteCallsArbitrumMainnet": "npm-run-all -s fork:runDeployMainnet fork:runDeployArbitrum fork:setExecutorArbitrumMainnet fork:setRelayerArbitrumMainnet fork:executeCallsArbitrumMainnet", - "fork:startExecuteCallsArbitrumMainnet": "yarn fork:rmLocalhostDeployments && npm-run-all -p -r fork:startMainnet fork:startArbitrum fork:runExecuteCallsArbitrumMainnet" + "fork:runDispatchMessageBatchArbitrumMainnet": "npm-run-all -s fork:runDeployMainnet fork:runDeployArbitrum fork:setExecutorArbitrumMainnet fork:setDispatcherArbitrumMainnet fork:dispatchMessageBatchArbitrumMainnet", + "fork:startDispatchMessageBatchArbitrumMainnet": "yarn fork:rmLocalhostDeployments && npm-run-all -p -r fork:startMainnet fork:startArbitrum fork:runDispatchMessageBatchArbitrumMainnet", + "fork:runExecuteMessageBatchArbitrumMainnet": "npm-run-all -s fork:runDeployMainnet fork:runDeployArbitrum fork:setExecutorArbitrumMainnet fork:setDispatcherArbitrumMainnet fork:executeMessageBatchArbitrumMainnet", + "fork:startExecuteMessageBatchArbitrumMainnet": "yarn fork:rmLocalhostDeployments && npm-run-all -p -r fork:startMainnet fork:startArbitrum fork:runExecuteMessageBatchArbitrumMainnet" }, "packageManager": "yarn@3.2.3", "devDependencies": { @@ -83,6 +91,7 @@ "kill-port": "2.0.1", "lint-staged": "13.0.3", "npm-run-all": "4.1.5", + "pinst": "3.0.0", "postinstall-postinstall": "2.1.0", "prettier": "2.7.1", "prettier-plugin-solidity": "1.0.0-beta.24", @@ -93,5 +102,10 @@ "typechain": "8.1.1", "typescript": "4.8.4", "wait-on": "6.0.1" - } + }, + "files": [ + "src/**", + "types/**", + "out/**" + ] } diff --git a/script/bridge/BridgeToArbitrumGoerli.ts b/script/bridge/BridgeToArbitrumGoerli.ts index 8120e84..f2bb8cf 100644 --- a/script/bridge/BridgeToArbitrumGoerli.ts +++ b/script/bridge/BridgeToArbitrumGoerli.ts @@ -6,12 +6,11 @@ import hre from 'hardhat'; import { ARBITRUM_GOERLI_CHAIN_ID, GOERLI_CHAIN_ID } from '../../Constants'; import { getContractAddress } from '../../helpers/getContract'; import { action, error as errorLog, info, success } from '../../helpers/log'; -import { CrossChainRelayerArbitrum } from '../../types'; -import { CallLib } from '../../types/ICrossChainRelayer'; -import CrossChainRelayerArbitrumArtifact from '../../out/EthereumToArbitrumRelayer.sol/CrossChainRelayerArbitrum.json'; +import { MessageDispatcherArbitrum } from '../../types'; +import MessageDispatcherArbitrumArtifact from '../../out/EthereumToArbitrumDispatcher.sol/MessageDispatcherArbitrum.json'; const main = async () => { - action('Relay calls from Ethereum to Arbitrum...'); + action('Dispatch message from Ethereum to Arbitrum...'); const { ethers: { @@ -26,21 +25,21 @@ const main = async () => { const l2Provider = new providers.JsonRpcProvider(process.env.ARBITRUM_GOERLI_RPC_URL); - info(`Caller is: ${deployer}`); + info(`Dispatcher is: ${deployer}`); - const crossChainRelayerArbitrumAddress = await getContractAddress( - 'CrossChainRelayerArbitrum', + const messageDispatcherArbitrumAddress = await getContractAddress( + 'MessageDispatcherArbitrum', GOERLI_CHAIN_ID, 'Forge', ); - const crossChainRelayerArbitrum = (await getContractAt( - CrossChainRelayerArbitrumArtifact.abi, - crossChainRelayerArbitrumAddress, - )) as CrossChainRelayerArbitrum; + const messageDispatcherArbitrum = (await getContractAt( + MessageDispatcherArbitrumArtifact.abi, + messageDispatcherArbitrumAddress, + )) as MessageDispatcherArbitrum; - const crossChainExecutorAddress = await getContractAddress( - 'CrossChainExecutorArbitrum', + const messageExecutorAddress = await getContractAddress( + 'MessageExecutorArbitrum', ARBITRUM_GOERLI_CHAIN_ID, 'Forge', ); @@ -48,23 +47,40 @@ const main = async () => { const greeterAddress = await getContractAddress('Greeter', ARBITRUM_GOERLI_CHAIN_ID, 'Forge'); const greeting = 'Hello from L1'; - const callData = new Interface(['function setGreeting(string)']).encodeFunctionData( + const messageData = new Interface(['function setGreeting(string)']).encodeFunctionData( 'setGreeting', [greeting], ); - const calls: CallLib.CallStruct[] = [ - { - target: greeterAddress, - data: callData, - }, - ]; + const dispatchMessageTransaction = await messageDispatcherArbitrum.dispatchMessage( + ARBITRUM_GOERLI_CHAIN_ID, + greeterAddress, + messageData, + ); + const dispatchMessageTransactionReceipt = await dispatchMessageTransaction.wait(); + + const dispatchedMessagesEventInterface = new Interface([ + 'event MessageDispatched(bytes32 indexed messageId, address indexed from, uint256 indexed toChainId, address to, bytes data)', + ]); + + const dispatchedMessagesEventLogs = dispatchedMessagesEventInterface.parseLog( + dispatchMessageTransactionReceipt.logs[0], + ); - const nextNonce = (await crossChainRelayerArbitrum.nonce()).add(1); + const [messageId] = dispatchedMessagesEventLogs.args; - const executeCallsData = new Interface([ - 'function executeCalls(uint256,address,(address,bytes)[])', - ]).encodeFunctionData('executeCalls', [nextNonce, deployer, [[greeterAddress, callData]]]); + success('Successfully dispatched message from Ethereum to Arbitrum!'); + info(`MessageId: ${messageId}`); + + const executeMessageData = new Interface([ + 'function executeMessage(address,bytes,bytes32,uint256,address)', + ]).encodeFunctionData('executeMessage', [ + greeterAddress, + messageData, + messageId, + GOERLI_CHAIN_ID, + deployer, + ]); const l1ToL2MessageGasEstimate = new L1ToL2MessageGasEstimator(l2Provider); const baseFee = await getBaseFee(l1Provider); @@ -73,16 +89,16 @@ const main = async () => { * The estimateAll method gives us the following values for sending an L1->L2 message * (1) maxSubmissionCost: The maximum cost to be paid for submitting the transaction * (2) gasLimit: The L2 gas limit - * (3) deposit: The total amount to deposit on L1 to cover L2 gas and L2 call value + * (3) deposit: The total amount to deposit on L1 to cover L2 gas and L2 message value */ const { deposit, gasLimit, maxSubmissionCost } = await l1ToL2MessageGasEstimate.estimateAll( { - from: crossChainRelayerArbitrumAddress, - to: crossChainExecutorAddress, + from: messageDispatcherArbitrumAddress, + to: messageExecutorAddress, l2CallValue: BigNumber.from(0), excessFeeRefundAddress: deployer, callValueRefundAddress: deployer, - data: executeCallsData, + data: executeMessageData, }, baseFee, l1Provider, @@ -90,34 +106,19 @@ const main = async () => { info(`Current retryable base submission price is: ${maxSubmissionCost.toString()}`); - const relayCallsTransaction = await crossChainRelayerArbitrum.relayCalls(calls, gasLimit); - const relayCallsTransactionReceipt = await relayCallsTransaction.wait(); - - const relayedCallsEventInterface = new Interface([ - 'event RelayedCalls(uint256 indexed nonce,address indexed sender, (address target,bytes data)[], uint256 gasLimit)', - ]); - - const relayedCallsEventLogs = relayedCallsEventInterface.parseLog( - relayCallsTransactionReceipt.logs[0], - ); - - const [relayCallsNonce] = relayedCallsEventLogs.args; - - success('Successfully relayed calls from Ethereum to Arbitrum!'); - info(`Nonce: ${relayCallsNonce}`); - - action('Process calls from Ethereum to Arbitrum...'); + action('Process message from Ethereum to Arbitrum...'); const gasPriceBid = await l2Provider.getGasPrice(); info(`L2 gas price: ${gasPriceBid.toString()}`); - info(`Sending greeting to L2 with ${deposit.toString()} callValue for L2 fees:`); + info(`Sending greeting to L2 with ${deposit.toString()} messageValue for L2 fees:`); - const processCallsTransaction = await crossChainRelayerArbitrum.processCalls( - relayCallsNonce, - calls, + const processMessageTransaction = await messageDispatcherArbitrum.processMessage( + messageId, deployer, + greeterAddress, + messageData, deployer, gasLimit, maxSubmissionCost, @@ -127,27 +128,27 @@ const main = async () => { }, ); - const processCallsTransactionReceipt = await processCallsTransaction.wait(); + const processMessageTransactionReceipt = await processMessageTransaction.wait(); - const processedCallsEventInterface = new Interface([ - 'event ProcessedCalls(uint256 indexed nonce, address indexed sender, uint256 indexed ticketId)', + const messageProcessedEventInterface = new Interface([ + 'event MessageProcessed(bytes32 indexed messageId, address indexed sender, uint256 indexed ticketId)', ]); - const processedCallsEventLogs = processedCallsEventInterface.parseLog( - processCallsTransactionReceipt.logs[2], + const messageProcessedEventLogs = messageProcessedEventInterface.parseLog( + processMessageTransactionReceipt.logs[2], ); - const [nonce, sender, ticketId] = processedCallsEventLogs.args; + const [processedMessageId, sender, ticketId] = messageProcessedEventLogs.args; - const receipt = await l1Provider.getTransactionReceipt(processCallsTransaction.hash); + const receipt = await l1Provider.getTransactionReceipt(processMessageTransaction.hash); const l1Receipt = new L1TransactionReceipt(receipt); const { retryableCreationId }: { retryableCreationId: string } = ( await l1Receipt.getL1ToL2Messages(l2Provider) )[0]; - success('Successfully processed calls from Ethereum to Arbitrum!'); - info(`Nonce: ${nonce.toString()}`); + success('Successfully processed message from Ethereum to Arbitrum!'); + info(`MessageId: ${processedMessageId.toString()}`); info(`Sender: ${sender}`); info(`TicketId: ${ticketId.toString()}`); info(`RetryableCreationId: ${retryableCreationId}`); diff --git a/script/bridge/BridgeToMumbai.s.sol b/script/bridge/BridgeToMumbai.s.sol index 783d07a..c0bea66 100644 --- a/script/bridge/BridgeToMumbai.s.sol +++ b/script/bridge/BridgeToMumbai.s.sol @@ -6,23 +6,21 @@ import "forge-std/Script.sol"; import { DeployedContracts } from "../helpers/DeployedContracts.sol"; -import { ICrossChainRelayer } from "../../src/interfaces/ICrossChainRelayer.sol"; -import { CrossChainRelayerPolygon } from "../../src/ethereum-polygon/EthereumToPolygonRelayer.sol"; -import "../../src/libraries/CallLib.sol"; +import { IMessageDispatcher } from "../../src/interfaces/IMessageDispatcher.sol"; +import { MessageDispatcherPolygon } from "../../src/ethereum-polygon/EthereumToPolygonDispatcher.sol"; +import "../../src/libraries/MessageLib.sol"; + +import { Greeter } from "../../test/contracts/Greeter.sol"; contract BridgeToMumbai is DeployedContracts { function bridgeToMumbai() public { - CrossChainRelayerPolygon _crossChainRelayer = _getCrossChainRelayerPolygon(); - address _greeter = address(_getGreeterPolygon()); - - CallLib.Call[] memory _calls = new CallLib.Call[](1); - - _calls[0] = CallLib.Call({ - target: _greeter, - data: abi.encodeWithSignature("setGreeting(string)", "Hello from L1") - }); + MessageDispatcherPolygon _messageDispatcher = _getMessageDispatcherPolygon(); - _crossChainRelayer.relayCalls(_calls, 1000000); + _messageDispatcher.dispatchMessage( + 80001, + address(_getGreeterPolygon()), + abi.encodeCall(Greeter.setGreeting, ("Hello from L1")) + ); } function run() public { diff --git a/script/bridge/BridgeToOptimismGoerli.s.sol b/script/bridge/BridgeToOptimismGoerli.s.sol index 13764d7..29a9e72 100644 --- a/script/bridge/BridgeToOptimismGoerli.s.sol +++ b/script/bridge/BridgeToOptimismGoerli.s.sol @@ -7,23 +7,21 @@ import "forge-std/Script.sol"; import { ICrossDomainMessenger as IOptimismBridge } from "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; import { DeployedContracts } from "../helpers/DeployedContracts.sol"; -import { ICrossChainRelayer } from "../../src/interfaces/ICrossChainRelayer.sol"; -import { CrossChainRelayerOptimism } from "../../src/ethereum-optimism/EthereumToOptimismRelayer.sol"; -import "../../src/libraries/CallLib.sol"; +import { IMessageDispatcher } from "../../src/interfaces/IMessageDispatcher.sol"; +import { MessageDispatcherOptimism } from "../../src/ethereum-optimism/EthereumToOptimismDispatcher.sol"; +import "../../src/libraries/MessageLib.sol"; + +import { Greeter } from "../../test/contracts/Greeter.sol"; contract BridgeToOptimismGoerli is DeployedContracts { function bridgeToOptimism() public { - CrossChainRelayerOptimism _crossChainRelayer = _getCrossChainRelayerOptimism(); - address _greeter = address(_getGreeterOptimism()); - - CallLib.Call[] memory _calls = new CallLib.Call[](1); - - _calls[0] = CallLib.Call({ - target: _greeter, - data: abi.encodeWithSignature("setGreeting(string)", "Hello from L1") - }); + MessageDispatcherOptimism _messageDispatcher = _getMessageDispatcherOptimismGoerli(); - _crossChainRelayer.relayCalls(_calls, 1000000); + _messageDispatcher.dispatchMessage( + 420, + address(_getGreeterOptimismGoerli()), + abi.encodeCall(Greeter.setGreeting, ("Hello from L1")) + ); } function run() public { diff --git a/script/deploy/DeployToArbitrumGoerli.s.sol b/script/deploy/DeployToArbitrumGoerli.s.sol index e9f01e2..28f1b54 100644 --- a/script/deploy/DeployToArbitrumGoerli.s.sol +++ b/script/deploy/DeployToArbitrumGoerli.s.sol @@ -6,63 +6,63 @@ import { Script } from "forge-std/Script.sol"; import { IInbox } from "@arbitrum/nitro-contracts/src/bridge/IInbox.sol"; import { DeployedContracts } from "../helpers/DeployedContracts.sol"; -import { CrossChainExecutorArbitrum } from "../../src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol"; -import { CrossChainRelayerArbitrum } from "../../src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol"; +import { MessageExecutorArbitrum } from "../../src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol"; +import { MessageDispatcherArbitrum } from "../../src/ethereum-arbitrum/EthereumToArbitrumDispatcher.sol"; import { Greeter } from "../../test/contracts/Greeter.sol"; -contract DeployCrossChainRelayerToGoerli is Script { +contract DeployMessageDispatcherToGoerli is Script { address public delayedInbox = 0x6BEbC4925716945D46F0Ec336D5C2564F419682C; function run() public { vm.broadcast(); - new CrossChainRelayerArbitrum(IInbox(delayedInbox)); + new MessageDispatcherArbitrum(IInbox(delayedInbox), 421613); vm.stopBroadcast(); } } -contract DeployCrossChainExecutorToArbitrumGoerli is Script { +contract DeployMessageExecutorToArbitrumGoerli is Script { function run() public { vm.broadcast(); - new CrossChainExecutorArbitrum(); + new MessageExecutorArbitrum(); vm.stopBroadcast(); } } -/// @dev Needs to be run after deploying CrossChainRelayer and CrossChainExecutor -contract SetCrossChainExecutor is DeployedContracts { - function setCrossChainExecutor() public { - CrossChainRelayerArbitrum _crossChainRelayer = _getCrossChainRelayerArbitrum(); - CrossChainExecutorArbitrum _crossChainExecutor = _getCrossChainExecutorArbitrum(); +/// @dev Needs to be run after deploying MessageDispatcher and MessageExecutor +contract SetMessageExecutor is DeployedContracts { + function setMessageExecutor() public { + MessageDispatcherArbitrum _messageDispatcher = _getMessageDispatcherArbitrum(); + MessageExecutorArbitrum _messageExecutor = _getMessageExecutorArbitrum(); - _crossChainRelayer.setExecutor(_crossChainExecutor); + _messageDispatcher.setExecutor(_messageExecutor); } function run() public { vm.broadcast(); - setCrossChainExecutor(); + setMessageExecutor(); vm.stopBroadcast(); } } -/// @dev Needs to be run after deploying CrossChainRelayer and CrossChainExecutor -contract SetCrossChainRelayer is DeployedContracts { - function setCrossChainRelayer() public { - CrossChainRelayerArbitrum _crossChainRelayer = _getCrossChainRelayerArbitrum(); - CrossChainExecutorArbitrum _crossChainExecutor = _getCrossChainExecutorArbitrum(); +/// @dev Needs to be run after deploying MessageDispatcher and MessageExecutor +contract SetMessageDispatcher is DeployedContracts { + function setMessageDispatcher() public { + MessageDispatcherArbitrum _messageDispatcher = _getMessageDispatcherArbitrum(); + MessageExecutorArbitrum _messageExecutor = _getMessageExecutorArbitrum(); - _crossChainExecutor.setRelayer(_crossChainRelayer); + _messageExecutor.setDispatcher(_messageDispatcher); } function run() public { vm.broadcast(); - setCrossChainRelayer(); + setMessageDispatcher(); vm.stopBroadcast(); } @@ -70,8 +70,8 @@ contract SetCrossChainRelayer is DeployedContracts { contract DeployGreeterToArbitrumGoerli is DeployedContracts { function deployGreeter() public { - CrossChainExecutorArbitrum _crossChainExecutor = _getCrossChainExecutorArbitrum(); - new Greeter(address(_crossChainExecutor), "Hello from L2"); + MessageExecutorArbitrum _messageExecutor = _getMessageExecutorArbitrum(); + new Greeter(address(_messageExecutor), "Hello from L2"); } function run() public { diff --git a/script/deploy/DeployToMumbai.s.sol b/script/deploy/DeployToMumbai.s.sol index d360928..d457821 100644 --- a/script/deploy/DeployToMumbai.s.sol +++ b/script/deploy/DeployToMumbai.s.sol @@ -5,43 +5,43 @@ pragma solidity 0.8.16; import { Script } from "forge-std/Script.sol"; import { DeployedContracts } from "../helpers/DeployedContracts.sol"; -import { CrossChainExecutorPolygon } from "../../src/ethereum-polygon/EthereumToPolygonExecutor.sol"; -import { CrossChainRelayerPolygon } from "../../src/ethereum-polygon/EthereumToPolygonRelayer.sol"; +import { MessageExecutorPolygon } from "../../src/ethereum-polygon/EthereumToPolygonExecutor.sol"; +import { MessageDispatcherPolygon } from "../../src/ethereum-polygon/EthereumToPolygonDispatcher.sol"; import { Greeter } from "../../test/contracts/Greeter.sol"; -contract DeployCrossChainRelayerToGoerli is Script { +contract DeployMessageDispatcherToGoerli is Script { address public checkpointManager = 0x2890bA17EfE978480615e330ecB65333b880928e; address public fxRoot = 0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA; function run() public { vm.broadcast(); - new CrossChainRelayerPolygon(checkpointManager, fxRoot); + new MessageDispatcherPolygon(checkpointManager, fxRoot, 80001); vm.stopBroadcast(); } } -contract DeployCrossChainExecutorToMumbai is Script { +contract DeployMessageExecutorToMumbai is Script { address public fxChild = 0xCf73231F28B7331BBe3124B907840A94851f9f11; function run() public { vm.broadcast(); - new CrossChainExecutorPolygon(fxChild); + new MessageExecutorPolygon(fxChild); vm.stopBroadcast(); } } -/// @dev Needs to be run after deploying CrossChainRelayer and CrossChainExecutor +/// @dev Needs to be run after deploying MessageDispatcher and MessageExecutor contract SetFxChildTunnel is DeployedContracts { function setFxChildTunnel() public { - CrossChainRelayerPolygon _crossChainRelayer = _getCrossChainRelayerPolygon(); - CrossChainExecutorPolygon _crossChainExecutor = _getCrossChainExecutorPolygon(); + MessageDispatcherPolygon _messageDispatcher = _getMessageDispatcherPolygon(); + MessageExecutorPolygon _messageExecutor = _getMessageExecutorPolygon(); - _crossChainRelayer.setFxChildTunnel(address(_crossChainExecutor)); + _messageDispatcher.setFxChildTunnel(address(_messageExecutor)); } function run() public { @@ -53,13 +53,13 @@ contract SetFxChildTunnel is DeployedContracts { } } -/// @dev Needs to be run after deploying CrossChainRelayer and CrossChainExecutor +/// @dev Needs to be run after deploying MessageDispatcher and MessageExecutor contract SetFxRootTunnel is DeployedContracts { function setFxRootTunnel() public { - CrossChainRelayerPolygon _crossChainRelayer = _getCrossChainRelayerPolygon(); - CrossChainExecutorPolygon _crossChainExecutor = _getCrossChainExecutorPolygon(); + MessageDispatcherPolygon _messageDispatcher = _getMessageDispatcherPolygon(); + MessageExecutorPolygon _messageExecutor = _getMessageExecutorPolygon(); - _crossChainExecutor.setFxRootTunnel(address(_crossChainRelayer)); + _messageExecutor.setFxRootTunnel(address(_messageDispatcher)); } function run() public { @@ -75,8 +75,8 @@ contract DeployGreeterToMumbai is DeployedContracts { function run() public { vm.broadcast(); - CrossChainExecutorPolygon _crossChainExecutor = _getCrossChainExecutorPolygon(); - new Greeter(address(_crossChainExecutor), "Hello from L2"); + MessageExecutorPolygon _messageExecutor = _getMessageExecutorPolygon(); + new Greeter(address(_messageExecutor), "Hello from L2"); vm.stopBroadcast(); } diff --git a/script/deploy/DeployToOptimism.s.sol b/script/deploy/DeployToOptimism.s.sol new file mode 100644 index 0000000..9a1f1c8 --- /dev/null +++ b/script/deploy/DeployToOptimism.s.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.16; + +import { Script } from "forge-std/Script.sol"; +import { ICrossDomainMessenger } from "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; +import { DeployedContracts } from "../helpers/DeployedContracts.sol"; + +import { MessageDispatcherOptimism } from "../../src/ethereum-optimism/EthereumToOptimismDispatcher.sol"; +import { MessageExecutorOptimism } from "../../src/ethereum-optimism/EthereumToOptimismExecutor.sol"; + +contract DeployMessageDispatcherToEthereumMainnet is Script { + address public proxyOVML1CrossDomainMessenger = 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1; + + function run() public { + vm.broadcast(); + + new MessageDispatcherOptimism(ICrossDomainMessenger(proxyOVML1CrossDomainMessenger), 10); + + vm.stopBroadcast(); + } +} + +contract DeployMessageExecutorToOptimism is Script { + address public l2CrossDomainMessenger = 0x4200000000000000000000000000000000000007; + + function run() public { + vm.broadcast(); + + new MessageExecutorOptimism(ICrossDomainMessenger(l2CrossDomainMessenger)); + + vm.stopBroadcast(); + } +} + +/// @dev Needs to be run after deploying MessageDispatcher and MessageExecutor +contract SetMessageExecutor is DeployedContracts { + function setMessageExecutor() public { + MessageDispatcherOptimism _messageDispatcher = _getMessageDispatcherOptimism(); + MessageExecutorOptimism _messageExecutor = _getMessageExecutorOptimism(); + + _messageDispatcher.setExecutor(_messageExecutor); + } + + function run() public { + vm.broadcast(); + + setMessageExecutor(); + + vm.stopBroadcast(); + } +} + +/// @dev Needs to be run after deploying MessageDispatcher and MessageExecutor +contract SetMessageDispatcher is DeployedContracts { + function setMessageDispatcher() public { + MessageDispatcherOptimism _messageDispatcher = _getMessageDispatcherOptimism(); + MessageExecutorOptimism _messageExecutor = _getMessageExecutorOptimism(); + + _messageExecutor.setDispatcher(_messageDispatcher); + } + + function run() public { + vm.broadcast(); + + setMessageDispatcher(); + + vm.stopBroadcast(); + } +} diff --git a/script/deploy/DeployToOptimismGoerli.s.sol b/script/deploy/DeployToOptimismGoerli.s.sol index ed2c82a..465b030 100644 --- a/script/deploy/DeployToOptimismGoerli.s.sol +++ b/script/deploy/DeployToOptimismGoerli.s.sol @@ -6,65 +6,65 @@ import { Script } from "forge-std/Script.sol"; import { ICrossDomainMessenger } from "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; import { DeployedContracts } from "../helpers/DeployedContracts.sol"; -import { CrossChainExecutorOptimism } from "../../src/ethereum-optimism/EthereumToOptimismExecutor.sol"; -import { CrossChainRelayerOptimism } from "../../src/ethereum-optimism/EthereumToOptimismRelayer.sol"; +import { MessageDispatcherOptimism } from "../../src/ethereum-optimism/EthereumToOptimismDispatcher.sol"; +import { MessageExecutorOptimism } from "../../src/ethereum-optimism/EthereumToOptimismExecutor.sol"; import { Greeter } from "../../test/contracts/Greeter.sol"; -contract DeployCrossChainRelayerToGoerli is Script { +contract DeployMessageDispatcherToGoerli is Script { address public proxyOVML1CrossDomainMessenger = 0x5086d1eEF304eb5284A0f6720f79403b4e9bE294; function run() public { vm.broadcast(); - new CrossChainRelayerOptimism(ICrossDomainMessenger(proxyOVML1CrossDomainMessenger)); + new MessageDispatcherOptimism(ICrossDomainMessenger(proxyOVML1CrossDomainMessenger), 420); vm.stopBroadcast(); } } -contract DeployCrossChainExecutorToOptimismGoerli is Script { +contract DeployMessageExecutorToOptimismGoerli is Script { address public l2CrossDomainMessenger = 0x4200000000000000000000000000000000000007; function run() public { vm.broadcast(); - new CrossChainExecutorOptimism(ICrossDomainMessenger(l2CrossDomainMessenger)); + new MessageExecutorOptimism(ICrossDomainMessenger(l2CrossDomainMessenger)); vm.stopBroadcast(); } } -/// @dev Needs to be run after deploying CrossChainRelayer and CrossChainExecutor -contract SetCrossChainExecutor is DeployedContracts { - function setCrossChainExecutor() public { - CrossChainRelayerOptimism _crossChainRelayer = _getCrossChainRelayerOptimism(); - CrossChainExecutorOptimism _crossChainExecutor = _getCrossChainExecutorOptimism(); +/// @dev Needs to be run after deploying MessageDispatcher and MessageExecutor +contract SetMessageExecutor is DeployedContracts { + function setMessageExecutor() public { + MessageDispatcherOptimism _messageDispatcher = _getMessageDispatcherOptimismGoerli(); + MessageExecutorOptimism _messageExecutor = _getMessageExecutorOptimismGoerli(); - _crossChainRelayer.setExecutor(_crossChainExecutor); + _messageDispatcher.setExecutor(_messageExecutor); } function run() public { vm.broadcast(); - setCrossChainExecutor(); + setMessageExecutor(); vm.stopBroadcast(); } } -/// @dev Needs to be run after deploying CrossChainRelayer and CrossChainExecutor -contract SetCrossChainRelayer is DeployedContracts { - function setCrossChainRelayer() public { - CrossChainRelayerOptimism _crossChainRelayer = _getCrossChainRelayerOptimism(); - CrossChainExecutorOptimism _crossChainExecutor = _getCrossChainExecutorOptimism(); +/// @dev Needs to be run after deploying MessageDispatcher and MessageExecutor +contract SetMessageDispatcher is DeployedContracts { + function setMessageDispatcher() public { + MessageDispatcherOptimism _messageDispatcher = _getMessageDispatcherOptimismGoerli(); + MessageExecutorOptimism _messageExecutor = _getMessageExecutorOptimismGoerli(); - _crossChainExecutor.setRelayer(_crossChainRelayer); + _messageExecutor.setDispatcher(_messageDispatcher); } function run() public { vm.broadcast(); - setCrossChainRelayer(); + setMessageDispatcher(); vm.stopBroadcast(); } @@ -72,8 +72,8 @@ contract SetCrossChainRelayer is DeployedContracts { contract DeployGreeterToOptimismGoerli is DeployedContracts { function deployGreeter() public { - CrossChainExecutorOptimism _crossChainExecutor = _getCrossChainExecutorOptimism(); - new Greeter(address(_crossChainExecutor), "Hello from L2"); + MessageExecutorOptimism _messageExecutor = _getMessageExecutorOptimismGoerli(); + new Greeter(address(_messageExecutor), "Hello from L2"); } function run() public { diff --git a/script/fork/bridge/BridgeToArbitrum.ts b/script/fork/bridge/BridgeToArbitrum.ts index 874c1de..777d5b7 100644 --- a/script/fork/bridge/BridgeToArbitrum.ts +++ b/script/fork/bridge/BridgeToArbitrum.ts @@ -11,7 +11,7 @@ import { ARBITRUM_CHAIN_ID, MAINNET_CHAIN_ID } from '../../../Constants'; import { getContractAddress } from '../../../helpers/getContract'; import { getChainName } from '../../../helpers/getChain'; import { action, error as errorLog, info, success } from '../../../helpers/log'; -import { CrossChainRelayerArbitrum, CrossChainExecutorArbitrum, Greeter } from '../../../types'; +import { MessageDispatcherArbitrum, MessageExecutorArbitrum, Greeter } from '../../../types'; import GreeterArtifact from '../../../out/Greeter.sol/Greeter.json'; const killHardhatNode = async (port: number, chainId: number) => { @@ -23,17 +23,17 @@ const killHardhatNode = async (port: number, chainId: number) => { }); }; -export const relayCalls = task( - 'fork:relay-calls', - 'Relay calls from Ethereum to Arbitrum', +export const dispatchMessageBatch = task( + 'fork:dispatch-message-batch', + 'Dispatch a batch of messages from Ethereum to Arbitrum', ).setAction(async (taskArguments, hre: HardhatRuntimeEnvironment) => { - action('Relay calls from Ethereum to Arbitrum...'); + action('Dispatch a batch of messages from Ethereum to Arbitrum...'); const { ethers: { getContract, provider: l1Provider, - utils: { defaultAbiCoder, Interface }, + utils: { formatBytes32String, Interface }, }, getNamedAccounts, } = hre; @@ -42,37 +42,40 @@ export const relayCalls = task( const l2Provider = new providers.JsonRpcProvider(process.env.ARBITRUM_RPC_URL); - info(`Caller is: ${deployer}`); + info(`Dispatcher is: ${deployer}`); - const crossChainRelayerArbitrum = (await getContract( - 'CrossChainRelayerArbitrum', - )) as CrossChainRelayerArbitrum; + const messageDispatcherArbitrum = (await getContract( + 'MessageDispatcherArbitrum', + )) as MessageDispatcherArbitrum; - const crossChainExecutorAddress = await getContractAddress( - 'CrossChainExecutorArbitrum', + const messageExecutorAddress = await getContractAddress( + 'MessageExecutorArbitrum', ARBITRUM_CHAIN_ID, ); const greeterAddress = await getContractAddress('Greeter', ARBITRUM_CHAIN_ID); const greeting = 'Hello from L1'; - const callData = new Interface(['function setGreeting(string)']).encodeFunctionData( + const messageData = new Interface(['function setGreeting(string)']).encodeFunctionData( 'setGreeting', [greeting], ); - const calls = [ + const messages = [ { - target: greeterAddress, - data: callData, + to: greeterAddress, + data: messageData, }, ]; - const nextNonce = (await crossChainRelayerArbitrum.nonce()).add(1); - - const executeCallsData = new Interface([ - 'function executeCalls(uint256,address,(address,bytes)[])', - ]).encodeFunctionData('executeCalls', [nextNonce, deployer, [[greeterAddress, callData]]]); + const executeMessageBatchData = new Interface([ + 'function executeMessageBatch((address,bytes)[],bytes32,uint256,address)', + ]).encodeFunctionData('executeMessageBatch', [ + [[greeterAddress, messageData]], + formatBytes32String(''), + MAINNET_CHAIN_ID, + deployer, + ]); const l1ToL2MessageGasEstimate = new L1ToL2MessageGasEstimator(l2Provider); const baseFee = await getBaseFee(l1Provider); @@ -81,16 +84,16 @@ export const relayCalls = task( * The estimateAll method gives us the following values for sending an L1->L2 message * (1) maxSubmissionCost: The maximum cost to be paid for submitting the transaction * (2) gasLimit: The L2 gas limit - * (3) deposit: The total amount to deposit on L1 to cover L2 gas and L2 call value + * (3) deposit: The total amount to deposit on L1 to cover L2 gas and L2 message value */ const { deposit, gasLimit, maxSubmissionCost } = await l1ToL2MessageGasEstimate.estimateAll( { - from: crossChainRelayerArbitrum.address, - to: crossChainExecutorAddress, + to: messageExecutorAddress, + from: messageDispatcherArbitrum.address, + data: executeMessageBatchData, l2CallValue: BigNumber.from(0), excessFeeRefundAddress: deployer, callValueRefundAddress: deployer, - data: executeCallsData, }, baseFee, l1Provider, @@ -98,33 +101,37 @@ export const relayCalls = task( info(`Current retryable base submission price is: ${maxSubmissionCost.toString()}`); - const relayCallsTransaction = await crossChainRelayerArbitrum.relayCalls(calls, gasLimit); - const relayCallsTransactionReceipt = await relayCallsTransaction.wait(); + const dispatchMessageBatchTransaction = await messageDispatcherArbitrum.dispatchMessageBatch( + ARBITRUM_CHAIN_ID, + messages, + ); + console.log('before dispatchMessageBatchTransactionReceipt'); + const dispatchMessageBatchTransactionReceipt = await dispatchMessageBatchTransaction.wait(); - const relayedCallsEventInterface = new Interface([ - 'event RelayedCalls(uint256 indexed nonce,address indexed sender, (address target,bytes data)[], uint256 gasLimit)', + const dispatchedMessagesEventInterface = new Interface([ + 'event MessageBatchDispatched(bytes32 indexed messageId, address indexed from, uint256 indexed toChainId, (address to,bytes data)[])', ]); - const relayedCallsEventLogs = relayedCallsEventInterface.parseLog( - relayCallsTransactionReceipt.logs[0], + const dispatchedMessagesEventLogs = dispatchedMessagesEventInterface.parseLog( + dispatchMessageBatchTransactionReceipt.logs[0], ); - const [relayCallsNonce] = relayedCallsEventLogs.args; + const [messageId] = dispatchedMessagesEventLogs.args; - success('Successfully relayed calls from Ethereum to Arbitrum!'); - info(`Nonce: ${relayCallsNonce}`); + success('Successfully dispatched messages from Ethereum to Arbitrum!'); + info(`MessageId: ${messageId}`); - action('Process calls from Ethereum to Arbitrum...'); + action('Process messages from Ethereum to Arbitrum...'); const gasPriceBid = await l2Provider.getGasPrice(); info(`L2 gas price: ${gasPriceBid.toString()}`); - info(`Sending greeting to L2 with ${deposit.toString()} callValue for L2 fees:`); + info(`Sending greeting to L2 with ${deposit.toString()} messageValue for L2 fees:`); - const processCallsTransaction = await crossChainRelayerArbitrum.processCalls( - relayCallsNonce, - calls, + const processMessageBatchTransaction = await messageDispatcherArbitrum.processMessageBatch( + messageId, + messages, deployer, deployer, gasLimit, @@ -135,55 +142,55 @@ export const relayCalls = task( }, ); - const processCallsTransactionReceipt = await processCallsTransaction.wait(); + const processMessageBatchTransactionReceipt = await processMessageBatchTransaction.wait(); - const processedCallsEventInterface = new Interface([ - 'event ProcessedCalls(uint256 indexed nonce, address indexed sender, uint256 indexed ticketId)', + const processedMessagesEventInterface = new Interface([ + 'event MessageBatchProcessed(bytes32 indexed messageId, address indexed sender, uint256 indexed ticketId)', ]); - const processedCallsEventLogs = processedCallsEventInterface.parseLog( - processCallsTransactionReceipt.logs[2], + const processedMessagesEventLogs = processedMessagesEventInterface.parseLog( + processMessageBatchTransactionReceipt.logs[2], ); - const [nonce, sender, ticketId] = processedCallsEventLogs.args; + const [processedMessageId, sender, ticketId] = processedMessagesEventLogs.args; - const receipt = await l1Provider.getTransactionReceipt(processCallsTransaction.hash); + const receipt = await l1Provider.getTransactionReceipt(processMessageBatchTransaction.hash); const l1Receipt = new L1TransactionReceipt(receipt); const { retryableCreationId }: { retryableCreationId: string } = ( await l1Receipt.getL1ToL2Messages(l2Provider) )[0]; - success('Successfully processed calls from Ethereum to Arbitrum!'); + success('Successfully processed messages from Ethereum to Arbitrum!'); info(`Sender: ${sender}`); - info(`Nonce: ${nonce.toString()}`); + info(`MessageId: ${processedMessageId.toString()}`); info(`TicketId: ${ticketId.toString()}`); info(`RetryableCreationId: ${retryableCreationId}`); await killHardhatNode(8545, MAINNET_CHAIN_ID); }); -export const executeCalls = task( - 'fork:execute-calls', - 'Execute calls from Ethereum on Arbitrum', +export const executeMessageBatch = task( + 'fork:execute-message-batch', + 'Execute a batch of messages from Ethereum on Arbitrum', ).setAction(async (taskArguments, hre: HardhatRuntimeEnvironment) => { - action('Execute calls from Ethereum on Arbitrum...'); + action('Execute a batch of messages from Ethereum on Arbitrum...'); const { ethers: { getContract, getContractAt, - utils: { Interface }, + utils: { formatBytes32String, Interface }, }, getNamedAccounts, } = hre; const { deployer } = await getNamedAccounts(); - info(`Caller is: ${deployer}`); + info(`Dispatcher is: ${deployer}`); - const crossChainRelayerArbitrumAddress = await getContractAddress( - 'CrossChainRelayerArbitrum', + const messageDispatcherArbitrumAddress = await getContractAddress( + 'MessageDispatcherArbitrum', MAINNET_CHAIN_ID, ); @@ -192,34 +199,34 @@ export const executeCalls = task( const greeter = (await getContractAt(GreeterArtifact.abi, greeterAddress)) as Greeter; const greeting = 'Hello from L1'; - const callData = new Interface(['function setGreeting(string)']).encodeFunctionData( + const messageData = new Interface(['function setGreeting(string)']).encodeFunctionData( 'setGreeting', [greeting], ); - const calls = [ + const messages = [ { - target: greeterAddress, - data: callData, + to: greeterAddress, + data: messageData, }, ]; - const crossChainExecutorArbitrum = (await getContract( - 'CrossChainExecutorArbitrum', - )) as CrossChainExecutorArbitrum; + const messageExecutorArbitrum = (await getContract( + 'MessageExecutorArbitrum', + )) as MessageExecutorArbitrum; info(`Greeting before: ${await greeter.callStatic.greeting()}`); await processL1ToL2Tx( - crossChainRelayerArbitrumAddress, + messageDispatcherArbitrumAddress, async (signer: SignerWithAddress) => - await crossChainExecutorArbitrum + await messageExecutorArbitrum .connect(signer) - .executeCalls(BigNumber.from(1), deployer, calls), + .executeMessageBatch(messages, formatBytes32String(''), MAINNET_CHAIN_ID, deployer), hre, ); - success('Successfully executed calls from Ethereum on Arbitrum!'); + success('Successfully executed messages from Ethereum on Arbitrum!'); info(`Greeting after: ${await greeter.callStatic.greeting()}`); await killHardhatNode(8546, ARBITRUM_CHAIN_ID); diff --git a/script/fork/deploy/DeployToArbitrum.ts b/script/fork/deploy/DeployToArbitrum.ts index db0f789..5216743 100644 --- a/script/fork/deploy/DeployToArbitrum.ts +++ b/script/fork/deploy/DeployToArbitrum.ts @@ -4,13 +4,13 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { ARBITRUM_CHAIN_ID, MAINNET_CHAIN_ID, DELAYED_INBOX } from '../../../Constants'; import { getContractAddress } from '../../../helpers/getContract'; import { action, info, success } from '../../../helpers/log'; -import { CrossChainRelayerArbitrum, CrossChainExecutorArbitrum } from '../../../types'; +import { MessageDispatcherArbitrum, MessageExecutorArbitrum } from '../../../types'; -export const deployRelayer = task( - 'fork:deploy-relayer', - 'Deploy Arbitrum Relayer on Ethereum', +export const deployDispatcher = task( + 'fork:deploy-dispatcher', + 'Deploy Arbitrum Dispatcher on Ethereum', ).setAction(async (taskArguments, hre: HardhatRuntimeEnvironment) => { - action('Deploy Relayer on Ethereum...'); + action('Deploy Dispatcher on Ethereum...'); const { deployments: { deploy }, @@ -21,12 +21,12 @@ export const deployRelayer = task( info(`Deployer is: ${deployer}`); - const { address } = await deploy('CrossChainRelayerArbitrum', { + const { address } = await deploy('MessageDispatcherArbitrum', { from: deployer, - args: [DELAYED_INBOX], + args: [DELAYED_INBOX, ARBITRUM_CHAIN_ID], }); - success(`Arbitrum Relayer deployed on Ethereum at address: ${address}`); + success(`Arbitrum Dispatcher deployed on Ethereum at address: ${address}`); }); export const deployExecutor = task( @@ -44,7 +44,7 @@ export const deployExecutor = task( info(`Deployer is: ${deployer}`); - const { address } = await deploy('CrossChainExecutorArbitrum', { + const { address } = await deploy('MessageExecutorArbitrum', { from: deployer, }); @@ -64,54 +64,56 @@ export const deployGreeter = task('fork:deploy-greeter', 'Deploy Greeter on Arbi info(`Deployer is: ${deployer}`); - const crossChainExecutorArbitrumAddress = await getContractAddress( - 'CrossChainExecutorArbitrum', + const messageExecutorArbitrumAddress = await getContractAddress( + 'MessageExecutorArbitrum', ARBITRUM_CHAIN_ID, ); const { address } = await deploy('Greeter', { from: deployer, - args: [crossChainExecutorArbitrumAddress, 'Hello from L2'], + args: [messageExecutorArbitrumAddress, 'Hello from L2'], }); success(`Arbitrum Greeter deployed on Arbitrum at address: ${address}`); }, ); -export const setExecutor = task('fork:set-executor', 'Set Executor on Arbitrum Relayer').setAction( - async (taskArguments, hre: HardhatRuntimeEnvironment) => { - action('Set Executor on Arbitrum Relayer...'); +export const setExecutor = task( + 'fork:set-executor', + 'Set Executor on Arbitrum Dispatcher', +).setAction(async (taskArguments, hre: HardhatRuntimeEnvironment) => { + action('Set Executor on Arbitrum Dispatcher...'); - const crossChainRelayerArbitrum = (await hre.ethers.getContract( - 'CrossChainRelayerArbitrum', - )) as CrossChainRelayerArbitrum; + const messageDispatcherArbitrum = (await hre.ethers.getContract( + 'MessageDispatcherArbitrum', + )) as MessageDispatcherArbitrum; - const crossChainExecutorArbitrumAddress = await getContractAddress( - 'CrossChainExecutorArbitrum', - ARBITRUM_CHAIN_ID, - ); + const messageExecutorArbitrumAddress = await getContractAddress( + 'MessageExecutorArbitrum', + ARBITRUM_CHAIN_ID, + ); - await crossChainRelayerArbitrum.setExecutor(crossChainExecutorArbitrumAddress); + await messageDispatcherArbitrum.setExecutor(messageExecutorArbitrumAddress); - success('Executor set on Arbitrum Relayer!'); - }, -); + success('Executor set on Arbitrum Dispatcher!'); +}); -export const setRelayer = task('fork:set-relayer', 'Set Relayer on Arbitrum Executor').setAction( - async (taskArguments, hre: HardhatRuntimeEnvironment) => { - action('Set Relayer on Arbitrum Executor...'); +export const setDispatcher = task( + 'fork:set-dispatcher', + 'Set Dispatcher on Arbitrum Executor', +).setAction(async (taskArguments, hre: HardhatRuntimeEnvironment) => { + action('Set Dispatcher on Arbitrum Executor...'); - const crossChainExecutorArbitrum = (await hre.ethers.getContract( - 'CrossChainExecutorArbitrum', - )) as CrossChainExecutorArbitrum; + const messageExecutorArbitrum = (await hre.ethers.getContract( + 'MessageExecutorArbitrum', + )) as MessageExecutorArbitrum; - const crossChainRelayerArbitrumAddress = await getContractAddress( - 'CrossChainRelayerArbitrum', - MAINNET_CHAIN_ID, - ); + const messageDispatcherArbitrumAddress = await getContractAddress( + 'MessageDispatcherArbitrum', + MAINNET_CHAIN_ID, + ); - await crossChainExecutorArbitrum.setRelayer(crossChainRelayerArbitrumAddress); + await messageExecutorArbitrum.setDispatcher(messageDispatcherArbitrumAddress); - success('Relayer set on Arbitrum Executor!'); - }, -); + success('Dispatcher set on Arbitrum Executor!'); +}); diff --git a/script/fork/helpers/arbitrum.ts b/script/fork/helpers/arbitrum.ts index 948c61f..220b28c 100644 --- a/script/fork/helpers/arbitrum.ts +++ b/script/fork/helpers/arbitrum.ts @@ -14,7 +14,7 @@ const applyAlias = (address: string) => export const processL1ToL2Tx = async ( from: string, - call: (signer: SignerWithAddress) => Promise, + message: (signer: SignerWithAddress) => Promise, hre: HardhatRuntimeEnvironment, ) => { const { @@ -37,5 +37,5 @@ export const processL1ToL2Tx = async ( }), ) .then(() => getSigner(fromAliased)) - .then(async (signer) => await call(signer)); + .then(async (signer) => await message(signer)); }; diff --git a/script/helpers/DeployedContracts.sol b/script/helpers/DeployedContracts.sol index 41badad..bcf42d6 100644 --- a/script/helpers/DeployedContracts.sol +++ b/script/helpers/DeployedContracts.sol @@ -6,18 +6,19 @@ import { Script } from "forge-std/Script.sol"; import { stdJson } from "forge-std/StdJson.sol"; import "solidity-stringutils/strings.sol"; -import { CrossChainRelayerOptimism } from "../../src/ethereum-optimism/EthereumToOptimismRelayer.sol"; -import { CrossChainExecutorOptimism } from "../../src/ethereum-optimism/EthereumToOptimismExecutor.sol"; +import { MessageDispatcherOptimism } from "../../src/ethereum-optimism/EthereumToOptimismDispatcher.sol"; +import { MessageExecutorOptimism } from "../../src/ethereum-optimism/EthereumToOptimismExecutor.sol"; -import { CrossChainRelayerPolygon } from "../../src/ethereum-polygon/EthereumToPolygonRelayer.sol"; -import { CrossChainExecutorPolygon } from "../../src/ethereum-polygon/EthereumToPolygonExecutor.sol"; +import { MessageDispatcherPolygon } from "../../src/ethereum-polygon/EthereumToPolygonDispatcher.sol"; +import { MessageExecutorPolygon } from "../../src/ethereum-polygon/EthereumToPolygonExecutor.sol"; -import { CrossChainRelayerArbitrum } from "../../src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol"; -import { CrossChainExecutorArbitrum } from "../../src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol"; +import { MessageDispatcherArbitrum } from "../../src/ethereum-arbitrum/EthereumToArbitrumDispatcher.sol"; +import { MessageExecutorArbitrum } from "../../src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol"; import { Greeter } from "../../test/contracts/Greeter.sol"; -string constant OP_GOERLI_PATH = "/broadcast/DeployToOptimismGoerli.s.sol/420/"; +// Testnet deployment paths +string constant OPTIMISM_GOERLI_PATH = "/broadcast/DeployToOptimismGoerli.s.sol/420/"; string constant MUMBAI_PATH = "/broadcast/DeployToMumbai.s.sol/80001/"; string constant ARBITRUM_PATH = "/broadcast/DeployToArbitrumGoerli.s.sol/421613/"; @@ -84,44 +85,68 @@ abstract contract DeployedContracts is Script { /* ============ Getters ============ */ /* ============ Optimism ============ */ - function _getCrossChainRelayerOptimism() internal returns (CrossChainRelayerOptimism) { + /* ============ Mainnet ============ */ + function _getMessageDispatcherOptimism() internal returns (MessageDispatcherOptimism) { return - CrossChainRelayerOptimism( + MessageDispatcherOptimism( _getContractAddress( - "CrossChainRelayerOptimism", + "MessageDispatcherOptimism", + "/broadcast/DeployToOptimism.s.sol/1/", + "dispatcher-not-found" + ) + ); + } + + function _getMessageExecutorOptimism() internal returns (MessageExecutorOptimism) { + return + MessageExecutorOptimism( + _getContractAddress( + "MessageExecutorOptimism", + "/broadcast/DeployToOptimism.s.sol/10/", + "executor-not-found" + ) + ); + } + + /* ============ Testnet ============ */ + function _getMessageDispatcherOptimismGoerli() internal returns (MessageDispatcherOptimism) { + return + MessageDispatcherOptimism( + _getContractAddress( + "MessageDispatcherOptimism", "/broadcast/DeployToOptimismGoerli.s.sol/5/", - "relayer-not-found" + "dispatcher-not-found" ) ); } - function _getCrossChainExecutorOptimism() internal returns (CrossChainExecutorOptimism) { + function _getMessageExecutorOptimismGoerli() internal returns (MessageExecutorOptimism) { return - CrossChainExecutorOptimism( - _getContractAddress("CrossChainExecutorOptimism", OP_GOERLI_PATH, "executor-not-found") + MessageExecutorOptimism( + _getContractAddress("MessageExecutorOptimism", OPTIMISM_GOERLI_PATH, "executor-not-found") ); } - function _getGreeterOptimism() internal returns (Greeter) { - return Greeter(_getContractAddress("Greeter", OP_GOERLI_PATH, "greeter-not-found")); + function _getGreeterOptimismGoerli() internal returns (Greeter) { + return Greeter(_getContractAddress("Greeter", OPTIMISM_GOERLI_PATH, "greeter-not-found")); } /* ============ Polygon ============ */ - function _getCrossChainRelayerPolygon() internal returns (CrossChainRelayerPolygon) { + function _getMessageDispatcherPolygon() internal returns (MessageDispatcherPolygon) { return - CrossChainRelayerPolygon( + MessageDispatcherPolygon( _getContractAddress( - "CrossChainRelayerPolygon", + "MessageDispatcherPolygon", "/broadcast/DeployToMumbai.s.sol/5/", - "relayer-not-found" + "dispatcher-not-found" ) ); } - function _getCrossChainExecutorPolygon() internal returns (CrossChainExecutorPolygon) { + function _getMessageExecutorPolygon() internal returns (MessageExecutorPolygon) { return - CrossChainExecutorPolygon( - _getContractAddress("CrossChainExecutorPolygon", MUMBAI_PATH, "executor-not-found") + MessageExecutorPolygon( + _getContractAddress("MessageExecutorPolygon", MUMBAI_PATH, "executor-not-found") ); } @@ -130,21 +155,21 @@ abstract contract DeployedContracts is Script { } /* ============ Arbitrum ============ */ - function _getCrossChainRelayerArbitrum() internal returns (CrossChainRelayerArbitrum) { + function _getMessageDispatcherArbitrum() internal returns (MessageDispatcherArbitrum) { return - CrossChainRelayerArbitrum( + MessageDispatcherArbitrum( _getContractAddress( - "CrossChainRelayerArbitrum", + "MessageDispatcherArbitrum", "/broadcast/DeployToArbitrumGoerli.s.sol/5/", - "relayer-not-found" + "dispatcher-not-found" ) ); } - function _getCrossChainExecutorArbitrum() internal returns (CrossChainExecutorArbitrum) { + function _getMessageExecutorArbitrum() internal returns (MessageExecutorArbitrum) { return - CrossChainExecutorArbitrum( - _getContractAddress("CrossChainExecutorArbitrum", ARBITRUM_PATH, "executor-not-found") + MessageExecutorArbitrum( + _getContractAddress("MessageExecutorArbitrum", ARBITRUM_PATH, "executor-not-found") ); } diff --git a/src/abstract/ExecutorAware.sol b/src/abstract/ExecutorAware.sol index b7e7a72..049bba9 100644 --- a/src/abstract/ExecutorAware.sol +++ b/src/abstract/ExecutorAware.sol @@ -4,12 +4,12 @@ pragma solidity 0.8.16; /** * @title ExecutorAware abstract contract - * @notice The ExecutorAware contract allows contracts on a receiving chain to execute calls from an origin chain. - * These calls are sent by the `CrossChainRelayer` contract which live on the origin chain. - * The `CrossChainExecutor` contract on the receiving chain executes these calls + * @notice The ExecutorAware contract allows contracts on a receiving chain to execute messages from an origin chain. + * These messages are sent by the `MessageDispatcher` contract which live on the origin chain. + * The `MessageExecutor` contract on the receiving chain executes these messages * and then forward them to an ExecutorAware contract on the receiving chain. * @dev This contract implements EIP-2771 (https://eips.ethereum.org/EIPS/eip-2771) - * to ensure that calls are sent by a trusted `CrossChainExecutor` contract. + * to ensure that messages are sent by a trusted `MessageExecutor` contract. */ abstract contract ExecutorAware { /* ============ Variables ============ */ @@ -21,7 +21,7 @@ abstract contract ExecutorAware { /** * @notice ExecutorAware constructor. - * @param _executor Address of the `CrossChainExecutor` contract + * @param _executor Address of the `MessageExecutor` contract */ constructor(address _executor) { require(_executor != address(0), "executor-not-zero-address"); @@ -41,29 +41,43 @@ abstract contract ExecutorAware { /* ============ Internal Functions ============ */ /** - * @notice Retrieve signer address from call data. - * @return _signer Address of the signer + * @notice Retrieve messageId from message data. + * @return _msgDataMessageId ID uniquely identifying the message that was executed */ - function _msgSender() internal view returns (address payable _signer) { - _signer = payable(msg.sender); + function _messageId() internal pure returns (bytes32 _msgDataMessageId) { + _msgDataMessageId; - if (msg.data.length >= 20 && isTrustedExecutor(_signer)) { + if (msg.data.length >= 84) { assembly { - _signer := shr(96, calldataload(sub(calldatasize(), 20))) + _msgDataMessageId := calldataload(sub(calldatasize(), 84)) } } } /** - * @notice Retrieve nonce from call data. - * @return _callDataNonce Nonce uniquely identifying the message that was executed + * @notice Retrieve fromChainId from message data. + * @return _msgDataFromChainId ID of the chain that dispatched the messages */ - function _nonce() internal pure returns (uint256 _callDataNonce) { - _callDataNonce; + function _fromChainId() internal pure returns (uint256 _msgDataFromChainId) { + _msgDataFromChainId; if (msg.data.length >= 52) { assembly { - _callDataNonce := calldataload(sub(calldatasize(), 52)) + _msgDataFromChainId := calldataload(sub(calldatasize(), 52)) + } + } + } + + /** + * @notice Retrieve signer address from message data. + * @return _signer Address of the signer + */ + function _msgSender() internal view returns (address payable _signer) { + _signer = payable(msg.sender); + + if (msg.data.length >= 20 && isTrustedExecutor(_signer)) { + assembly { + _signer := shr(96, calldataload(sub(calldatasize(), 20))) } } } diff --git a/src/ethereum-arbitrum/EthereumToArbitrumDispatcher.sol b/src/ethereum-arbitrum/EthereumToArbitrumDispatcher.sol new file mode 100644 index 0000000..f8b687a --- /dev/null +++ b/src/ethereum-arbitrum/EthereumToArbitrumDispatcher.sol @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.16; + +import { IInbox } from "@arbitrum/nitro-contracts/src/bridge/IInbox.sol"; + +import { IMessageExecutor } from "../interfaces/IMessageExecutor.sol"; +import { IMessageDispatcher, ISingleMessageDispatcher } from "../interfaces/ISingleMessageDispatcher.sol"; +import { IBatchedMessageDispatcher } from "../interfaces/IBatchedMessageDispatcher.sol"; + +import "../libraries/MessageLib.sol"; + +/** + * @title MessageDispatcherArbitrum contract + * @notice The MessageDispatcherArbitrum contract allows a user or contract to send messages from Ethereum to Arbitrum. + * It lives on the Ethereum chain and communicates with the `MessageExecutorArbitrum` contract on the Arbitrum chain. + */ +contract MessageDispatcherArbitrum is ISingleMessageDispatcher, IBatchedMessageDispatcher { + /* ============ Events ============ */ + + /** + * @notice Emitted once a message has been processed and put in the Arbitrum inbox. + * @dev Using the `ticketId`, this message can be reexecuted for some fixed amount of time if it reverts. + * @param messageId ID uniquely identifying the messages + * @param sender Address who processed the messages + * @param ticketId Id of the newly created retryable ticket + */ + event MessageProcessed( + bytes32 indexed messageId, + address indexed sender, + uint256 indexed ticketId + ); + + /** + * @notice Emitted once a message has been processed and put in the Arbitrum inbox. + * @dev Using the `ticketId`, this message can be reexecuted for some fixed amount of time if it reverts. + * @param messageId ID uniquely identifying the messages + * @param sender Address who processed the messages + * @param ticketId Id of the newly created retryable ticket + */ + event MessageBatchProcessed( + bytes32 indexed messageId, + address indexed sender, + uint256 indexed ticketId + ); + + /* ============ Variables ============ */ + + /// @notice Address of the Arbitrum inbox on the Ethereum chain. + IInbox public immutable inbox; + + /// @notice Address of the executor contract on the Arbitrum chain. + IMessageExecutor internal executor; + + /// @notice Nonce used to compute unique `messageId`s. + uint256 internal nonce; + + /// @notice ID of the chain receiving the dispatched messages. i.e.: 42161 for Mainnet, 421613 for Goerli. + uint256 internal immutable toChainId; + + /** + * @notice Hash of transactions that were dispatched in `dispatchMessage` or `dispatchMessageBatch`. + * txHash => boolean + * @dev Ensure that messages passed to `processMessage` and `processMessageBatch` have been dispatched first. + */ + mapping(bytes32 => bool) public dispatched; + + /* ============ Constructor ============ */ + + /** + * @notice MessageDispatcher constructor. + * @param _inbox Address of the Arbitrum inbox on Ethereum + * @param _toChainId ID of the chain receiving the dispatched messages + */ + constructor(IInbox _inbox, uint256 _toChainId) { + require(address(_inbox) != address(0), "Dispatcher/inbox-not-zero-adrs"); + require(_toChainId != 0, "Dispatcher/chainId-not-zero"); + + inbox = _inbox; + toChainId = _toChainId; + } + + /* ============ External Functions ============ */ + + /// @inheritdoc ISingleMessageDispatcher + function dispatchMessage( + uint256 _toChainId, + address _to, + bytes calldata _data + ) external returns (bytes32) { + _checkToChainId(_toChainId); + + uint256 _nonce = _incrementNonce(); + bytes32 _messageId = MessageLib.computeMessageId(_nonce, msg.sender, _to, _data); + + dispatched[_getMessageTxHash(_messageId, msg.sender, _to, _data)] = true; + + emit MessageDispatched(_messageId, msg.sender, _toChainId, _to, _data); + + return _messageId; + } + + /// @inheritdoc IBatchedMessageDispatcher + function dispatchMessageBatch(uint256 _toChainId, MessageLib.Message[] calldata _messages) + external + returns (bytes32) + { + _checkToChainId(_toChainId); + + uint256 _nonce = _incrementNonce(); + bytes32 _messageId = MessageLib.computeMessageBatchId(_nonce, msg.sender, _messages); + + dispatched[_getMessageBatchTxHash(_messageId, msg.sender, _messages)] = true; + + emit MessageBatchDispatched(_messageId, msg.sender, _toChainId, _messages); + + return _messageId; + } + + /** + * @notice Process message that has been dispatched. + * @dev The transaction hash must match the one stored in the `dispatched` mapping. + * @dev `_from` is passed as `callValueRefundAddress` cause this address can cancel the retryably ticket. + * @dev We store `_message` in memory to avoid a stack too deep error. + * @param _messageId ID of the message to process + * @param _from Address who dispatched the `_data` + * @param _to Address that will receive the message + * @param _data Data that was dispatched + * @param _refundAddress Address that will receive the `excessFeeRefund` amount if any + * @param _gasLimit Maximum amount of gas required for the `_messages` to be executed + * @param _maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee + * @param _gasPriceBid Gas price bid for L2 execution + * @return uint256 Id of the retryable ticket that was created + */ + function processMessage( + bytes32 _messageId, + address _from, + address _to, + bytes calldata _data, + address _refundAddress, + uint256 _gasLimit, + uint256 _maxSubmissionCost, + uint256 _gasPriceBid + ) external payable returns (uint256) { + require( + dispatched[_getMessageTxHash(_messageId, _from, _to, _data)], + "Dispatcher/msg-not-dispatched" + ); + + address _executorAddress = address(executor); + _checkProcessParams(_executorAddress, _refundAddress); + + bytes memory _message = MessageLib.encodeMessage(_to, _data, _messageId, block.chainid, _from); + + uint256 _ticketID = _createRetryableTicket( + _executorAddress, + _maxSubmissionCost, + _refundAddress, + _from, + _gasLimit, + _gasPriceBid, + _message + ); + + emit MessageProcessed(_messageId, msg.sender, _ticketID); + + return _ticketID; + } + + /** + * @notice Process messages that have been dispatched. + * @dev The transaction hash must match the one stored in the `dispatched` mapping. + * @dev `_from` is passed as `messageValueRefundAddress` cause this address can cancel the retryably ticket. + * @dev We store `_message` in memory to avoid a stack too deep error. + * @param _messageId ID of the messages to process + * @param _messages Array of messages being processed + * @param _from Address who dispatched the `_messages` + * @param _refundAddress Address that will receive the `excessFeeRefund` amount if any + * @param _gasLimit Maximum amount of gas required for the `_messages` to be executed + * @param _maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee + * @param _gasPriceBid Gas price bid for L2 execution + * @return uint256 Id of the retryable ticket that was created + */ + function processMessageBatch( + bytes32 _messageId, + MessageLib.Message[] calldata _messages, + address _from, + address _refundAddress, + uint256 _gasLimit, + uint256 _maxSubmissionCost, + uint256 _gasPriceBid + ) external payable returns (uint256) { + require( + dispatched[_getMessageBatchTxHash(_messageId, _from, _messages)], + "Dispatcher/msges-not-dispatched" + ); + + address _executorAddress = address(executor); + _checkProcessParams(_executorAddress, _refundAddress); + + bytes memory _messageBatch = MessageLib.encodeMessageBatch( + _messages, + _messageId, + block.chainid, + _from + ); + + uint256 _ticketID = _createRetryableTicket( + _executorAddress, + _maxSubmissionCost, + _refundAddress, + _from, + _gasLimit, + _gasPriceBid, + _messageBatch + ); + + emit MessageBatchProcessed(_messageId, msg.sender, _ticketID); + + return _ticketID; + } + + /** + * @notice Set executor contract address. + * @dev Will revert if it has already been set. + * @param _executor Address of the executor contract on the Arbitrum chain + */ + function setExecutor(IMessageExecutor _executor) external { + require(address(executor) == address(0), "Dispatcher/executor-already-set"); + executor = _executor; + } + + /** + * @notice Get transaction hash for a single message. + * @dev The transaction hash is used to ensure that only messages that were dispatched are processed. + * @param _messageId ID uniquely identifying the message that was dispatched + * @param _from Address who dispatched the message + * @param _to Address that will receive the message + * @param _data Data that was dispatched + * @return bytes32 Transaction hash + */ + function getMessageTxHash( + bytes32 _messageId, + address _from, + address _to, + bytes calldata _data + ) external view returns (bytes32) { + return _getMessageTxHash(_messageId, _from, _to, _data); + } + + /** + * @notice Get transaction hash for a batch of messages. + * @dev The transaction hash is used to ensure that only messages that were dispatched are processed. + * @param _messageId ID uniquely identifying the messages that were dispatched + * @param _from Address who dispatched the messages + * @param _messages Array of messages that were dispatched + * @return bytes32 Transaction hash + */ + function getMessageBatchTxHash( + bytes32 _messageId, + address _from, + MessageLib.Message[] calldata _messages + ) external view returns (bytes32) { + return _getMessageBatchTxHash(_messageId, _from, _messages); + } + + /// @inheritdoc IMessageDispatcher + function getMessageExecutorAddress(uint256 _toChainId) external view returns (address) { + _checkToChainId(_toChainId); + return address(executor); + } + + /* ============ Internal Functions ============ */ + + /** + * @notice Get transaction hash for a single message. + * @dev The transaction hash is used to ensure that only messages that were dispatched are processed. + * @param _messageId ID uniquely identifying the message that was dispatched + * @param _from Address who dispatched the message + * @param _to Address that will receive the message + * @param _data Data that was dispatched + * @return bytes32 Transaction hash + */ + function _getMessageTxHash( + bytes32 _messageId, + address _from, + address _to, + bytes memory _data + ) internal view returns (bytes32) { + return keccak256(abi.encode(address(this), _messageId, _from, _to, _data)); + } + + /** + * @notice Get transaction hash for a batch of messages. + * @dev The transaction hash is used to ensure that only messages that were dispatched are processed. + * @param _messageId ID uniquely identifying the messages that were dispatched + * @param _from Address who dispatched the messages + * @param _messages Array of messages that were dispatched + * @return bytes32 Transaction hash + */ + function _getMessageBatchTxHash( + bytes32 _messageId, + address _from, + MessageLib.Message[] memory _messages + ) internal view returns (bytes32) { + return keccak256(abi.encode(address(this), _messageId, _from, _messages)); + } + + /** + * @notice Check toChainId to ensure messages can be dispatched to this chain. + * @dev Will revert if `_toChainId` is not supported. + * @param _toChainId ID of the chain receiving the message + */ + function _checkToChainId(uint256 _toChainId) internal view { + require(_toChainId == toChainId, "Dispatcher/chainId-not-supported"); + } + + /** + * @notice Check process parameters to ensure messages can be dispatched. + * @dev Will revert if `executor` is not set. + * @dev Will revert if `_refund` is address zero. + * @param _executor Address of the executor contract on the Optimism chain + * @param _refund Address that will receive the `excessFeeRefund` amount if any + */ + function _checkProcessParams(address _executor, address _refund) internal pure { + require(_executor != address(0), "Dispatcher/executor-not-set"); + require(_refund != address(0), "Dispatcher/refund-not-zero-adrs"); + } + + /** + * @notice Helper to increment nonce. + * @return uint256 Incremented nonce + */ + function _incrementNonce() internal returns (uint256) { + unchecked { + nonce++; + } + + return nonce; + } + + /** + * @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts + * @dev all msg.value will be deposited to `_callValueRefundAddress` on L2 + * @dev `_gasLimit` and `_gasPriceBid` should not be set to 1 as that is used to trigger the RetryableData error + * @param _to Destination L2 contract address + * @param _maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee + * @param _excessFeeRefundAddress `_gasLimit` x `_gasPriceBid` - execution cost gets credited here on L2 balance + * @param _callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled + * @param _gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) + * @param _gasPriceBid Price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) + * @param _data ABI encoded data of L2 message + * @return uint256 Unique message number of the retryable transaction + */ + function _createRetryableTicket( + address _to, + uint256 _maxSubmissionCost, + address _excessFeeRefundAddress, + address _callValueRefundAddress, + uint256 _gasLimit, + uint256 _gasPriceBid, + bytes memory _data + ) internal returns (uint256) { + return + inbox.createRetryableTicket{ value: msg.value }( + _to, + 0, // l2CallValue + _maxSubmissionCost, + _excessFeeRefundAddress, + _callValueRefundAddress, + _gasLimit, + _gasPriceBid, + _data + ); + } +} diff --git a/src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol b/src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol index eb4b89f..7118c95 100644 --- a/src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol +++ b/src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol @@ -4,79 +4,86 @@ pragma solidity 0.8.16; import { AddressAliasHelper } from "@arbitrum/nitro-contracts/src/libraries/AddressAliasHelper.sol"; -import "../interfaces/ICrossChainExecutor.sol"; -import "../libraries/CallLib.sol"; +import "../interfaces/IMessageExecutor.sol"; +import "../libraries/MessageLib.sol"; /** - * @title CrossChainExecutorArbitrum contract - * @notice The CrossChainExecutorArbitrum contract executes calls from the Ethereum chain. - * These calls are sent by the `CrossChainRelayerArbitrum` contract which lives on the Ethereum chain. + * @title MessageExecutorArbitrum contract + * @notice The MessageExecutorArbitrum contract executes messages from the Ethereum chain. + * These messages are sent by the `MessageDispatcherArbitrum` contract which lives on the Ethereum chain. */ -contract CrossChainExecutorArbitrum is ICrossChainExecutor { - /* ============ Custom Errors ============ */ - - /** - * @notice Emitted when a batch of calls fails to execute. - * @param relayer Address of the contract that relayed the calls on the origin chain - * @param nonce Nonce to uniquely identify the batch of calls that failed to execute - */ - error ExecuteCallsFailed(ICrossChainRelayer relayer, uint256 nonce); - +contract MessageExecutorArbitrum is IMessageExecutor { /* ============ Variables ============ */ - /// @notice Address of the relayer contract on the Ethereum chain. - ICrossChainRelayer public relayer; + /// @notice Address of the dispatcher contract on the Ethereum chain. + IMessageDispatcher public dispatcher; /** - * @notice Nonce to uniquely identify the batch of calls that were executed. - * nonce => boolean - * @dev Ensure that batch of calls cannot be replayed once they have been executed. + * @notice ID uniquely identifying the messages that were executed. + * messageId => boolean + * @dev Ensure that messages cannot be replayed once they have been executed. */ - mapping(uint256 => bool) public executed; + mapping(bytes32 => bool) public executed; /* ============ External Functions ============ */ - /// @inheritdoc ICrossChainExecutor - function executeCalls( - uint256 _nonce, - address _sender, - CallLib.Call[] calldata _calls + /// @inheritdoc IMessageExecutor + function executeMessage( + address _to, + bytes calldata _data, + bytes32 _messageId, + uint256 _fromChainId, + address _from ) external { - ICrossChainRelayer _relayer = relayer; - _isAuthorized(_relayer); + IMessageDispatcher _dispatcher = dispatcher; + _isAuthorized(_dispatcher); + + bool _executedMessageId = executed[_messageId]; + executed[_messageId] = true; - bool _executedNonce = executed[_nonce]; - executed[_nonce] = true; + MessageLib.executeMessage(_to, _data, _messageId, _fromChainId, _from, _executedMessageId); + + emit MessageIdExecuted(_fromChainId, _messageId); + } + + /// @inheritdoc IMessageExecutor + function executeMessageBatch( + MessageLib.Message[] calldata _messages, + bytes32 _messageId, + uint256 _fromChainId, + address _from + ) external { + IMessageDispatcher _dispatcher = dispatcher; + _isAuthorized(_dispatcher); - bool _callsExecuted = CallLib.executeCalls(_nonce, _sender, _calls, _executedNonce); + bool _executedMessageId = executed[_messageId]; + executed[_messageId] = true; - if (!_callsExecuted) { - revert ExecuteCallsFailed(_relayer, _nonce); - } + MessageLib.executeMessageBatch(_messages, _messageId, _fromChainId, _from, _executedMessageId); - emit ExecutedCalls(_relayer, _nonce); + emit MessageIdExecuted(_fromChainId, _messageId); } /** - * @notice Set relayer contract address. + * @notice Set dispatcher contract address. * @dev Will revert if it has already been set. - * @param _relayer Address of the relayer contract on the Ethereum chain + * @param _dispatcher Address of the dispatcher contract on the Ethereum chain */ - function setRelayer(ICrossChainRelayer _relayer) external { - require(address(relayer) == address(0), "Executor/relayer-already-set"); - relayer = _relayer; + function setDispatcher(IMessageDispatcher _dispatcher) external { + require(address(dispatcher) == address(0), "Executor/dispatcher-already-set"); + dispatcher = _dispatcher; } /* ============ Internal Functions ============ */ /** - * @notice Check that the message came from the `relayer` on the Ethereum chain. + * @notice Check that the message came from the `dispatcher` on the Ethereum chain. * @dev We check that the sender is the L1 contract's L2 alias. - * @param _relayer Address of the relayer on the Ethereum chain + * @param _dispatcher Address of the dispatcher on the Ethereum chain */ - function _isAuthorized(ICrossChainRelayer _relayer) internal view { + function _isAuthorized(IMessageDispatcher _dispatcher) internal view { require( - msg.sender == AddressAliasHelper.applyL1ToL2Alias(address(_relayer)), + msg.sender == AddressAliasHelper.applyL1ToL2Alias(address(_dispatcher)), "Executor/sender-unauthorized" ); } diff --git a/src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol b/src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol deleted file mode 100644 index 5326af6..0000000 --- a/src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.16; - -import { IInbox } from "@arbitrum/nitro-contracts/src/bridge/IInbox.sol"; - -import { ICrossChainExecutor } from "../interfaces/ICrossChainExecutor.sol"; -import { ICrossChainRelayer } from "../interfaces/ICrossChainRelayer.sol"; -import "../libraries/CallLib.sol"; - -/** - * @title CrossChainRelayerArbitrum contract - * @notice The CrossChainRelayerArbitrum contract allows a user or contract to send messages from Ethereum to Arbitrum. - * It lives on the Ethereum chain and communicates with the `CrossChainExecutorArbitrum` contract on the Arbitrum chain. - */ -contract CrossChainRelayerArbitrum is ICrossChainRelayer { - /* ============ Events ============ */ - - /** - * @notice Emitted once a message has been processed and put in the Arbitrum inbox. - * @dev Using the `ticketId`, this message can be reexecuted for some fixed amount of time if it reverts. - * @param nonce Nonce to uniquely idenfity the batch of calls - * @param sender Address who processed the calls - * @param ticketId Id of the newly created retryable ticket - */ - event ProcessedCalls(uint256 indexed nonce, address indexed sender, uint256 indexed ticketId); - - /* ============ Variables ============ */ - - /// @notice Address of the Arbitrum inbox on the Ethereum chain. - IInbox public immutable inbox; - - /// @notice Address of the executor contract on the Arbitrum chain. - ICrossChainExecutor public executor; - - /// @notice Nonce to uniquely idenfity each batch of calls. - uint256 public nonce; - - /** - * @notice Hash of transactions that were relayed in `relayCalls`. - * txHash => boolean - * @dev Ensure that messages passed to `processCalls` have been relayed first. - */ - mapping(bytes32 => bool) public relayed; - - /* ============ Constructor ============ */ - - /** - * @notice CrossChainRelayer constructor. - * @param _inbox Address of the Arbitrum inbox on Ethereum - */ - constructor(IInbox _inbox) { - require(address(_inbox) != address(0), "Relayer/inbox-not-zero-address"); - inbox = _inbox; - } - - /* ============ External Functions ============ */ - - /// @inheritdoc ICrossChainRelayer - function relayCalls(CallLib.Call[] calldata _calls, uint256 _gasLimit) - external - returns (uint256) - { - unchecked { - nonce++; - } - - uint256 _nonce = nonce; - - relayed[_getTxHash(_nonce, _calls, msg.sender, _gasLimit)] = true; - - emit RelayedCalls(_nonce, msg.sender, _calls, _gasLimit); - - return _nonce; - } - - /** - * @notice Process calls that have been relayed. - * @dev The transaction hash must match the one stored in the `relayed` mapping. - * @dev `_sender` is passed as `callValueRefundAddress` cause this address can cancel the retryably ticket. - * @dev We store `_data` in memory to avoid a stack too deep error. - * @param _nonce Nonce of the batch of calls to process - * @param _calls Array of calls being processed - * @param _sender Address who relayed the `_calls` - * @param _refundAddress Address that will receive the `excessFeeRefund` amount if any - * @param _gasLimit Maximum amount of gas required for the `_calls` to be executed - * @param _maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee - * @param _gasPriceBid Gas price bid for L2 execution - * @return uint256 Id of the retryable ticket that was created - */ - function processCalls( - uint256 _nonce, - CallLib.Call[] calldata _calls, - address _sender, - address _refundAddress, - uint256 _gasLimit, - uint256 _maxSubmissionCost, - uint256 _gasPriceBid - ) external payable returns (uint256) { - require(relayed[_getTxHash(_nonce, _calls, _sender, _gasLimit)], "Relayer/calls-not-relayed"); - - address _executorAddress = address(executor); - require(_executorAddress != address(0), "Relayer/executor-not-set"); - - require(_refundAddress != address(0), "Relayer/refund-address-not-zero"); - - bytes memory _data = abi.encodeWithSelector( - ICrossChainExecutor.executeCalls.selector, - _nonce, - _sender, - _calls - ); - - uint256 _ticketID = inbox.createRetryableTicket{ value: msg.value }( - _executorAddress, - 0, - _maxSubmissionCost, - _refundAddress, - _sender, - _gasLimit, - _gasPriceBid, - _data - ); - - emit ProcessedCalls(_nonce, msg.sender, _ticketID); - - return _ticketID; - } - - /** - * @notice Set executor contract address. - * @dev Will revert if it has already been set. - * @param _executor Address of the executor contract on the Arbitrum chain - */ - function setExecutor(ICrossChainExecutor _executor) external { - require(address(executor) == address(0), "Relayer/executor-already-set"); - executor = _executor; - } - - /** - * @notice Get transaction hash. - * @dev The transaction hash is used to ensure that only calls that were relayed are processed. - * @param _nonce Nonce uniquely identifying the batch of calls that were relayed - * @param _calls Array of calls that were relayed - * @param _sender Address who relayed the calls - * @param _gasLimit Maximum amount of gas that will be consumed by the calls - * @return bytes32 Transaction hash - */ - function getTxHash( - uint256 _nonce, - CallLib.Call[] calldata _calls, - address _sender, - uint256 _gasLimit - ) external view returns (bytes32) { - return _getTxHash(_nonce, _calls, _sender, _gasLimit); - } - - /* ============ Internal Functions ============ */ - - /** - * @notice Get transaction hash. - * @dev The transaction hash is used to ensure that only calls that were relayed are processed. - * @param _nonce Nonce uniquely identifying the batch of calls that were relayed - * @param _calls Array of calls that were relayed - * @param _sender Address who relayed the calls - * @param _gasLimit Maximum amount of gas that will be consumed by the calls - * @return bytes32 Transaction hash - */ - function _getTxHash( - uint256 _nonce, - CallLib.Call[] calldata _calls, - address _sender, - uint256 _gasLimit - ) internal view returns (bytes32) { - return keccak256(abi.encode(address(this), _nonce, _calls, _sender, _gasLimit)); - } -} diff --git a/src/ethereum-optimism/EthereumToOptimismDispatcher.sol b/src/ethereum-optimism/EthereumToOptimismDispatcher.sol new file mode 100644 index 0000000..2149bb1 --- /dev/null +++ b/src/ethereum-optimism/EthereumToOptimismDispatcher.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.16; + +import { ICrossDomainMessenger } from "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; + +import { IMessageExecutor } from "../interfaces/IMessageExecutor.sol"; +import { IMessageDispatcher, ISingleMessageDispatcher } from "../interfaces/ISingleMessageDispatcher.sol"; +import { IBatchedMessageDispatcher } from "../interfaces/IBatchedMessageDispatcher.sol"; + +import "../libraries/MessageLib.sol"; + +/** + * @title MessageDispatcherOptimism contract + * @notice The MessageDispatcherOptimism contract allows a user or contract to send messages from Ethereum to Optimism. + * It lives on the Ethereum chain and communicates with the `MessageExecutorOptimism` contract on the Optimism chain. + */ +contract MessageDispatcherOptimism is ISingleMessageDispatcher, IBatchedMessageDispatcher { + /* ============ Variables ============ */ + + /// @notice Address of the Optimism cross domain messenger on the Ethereum chain. + ICrossDomainMessenger public immutable crossDomainMessenger; + + /// @notice Address of the executor contract on the Optimism chain. + IMessageExecutor internal executor; + + /// @notice Nonce used to compute unique `messageId`s. + uint256 internal nonce; + + /// @notice ID of the chain receiving the dispatched messages. i.e.: 10 for Mainnet, 420 for Goerli. + uint256 internal immutable toChainId; + + /// @notice Free gas limit on Optimism + uint32 internal constant GAS_LIMIT = uint32(1920000); + + /* ============ Constructor ============ */ + + /** + * @notice MessageDispatcherOptimism constructor. + * @param _crossDomainMessenger Address of the Optimism cross domain messenger + * @param _toChainId ID of the chain receiving the dispatched messages + */ + constructor(ICrossDomainMessenger _crossDomainMessenger, uint256 _toChainId) { + require(address(_crossDomainMessenger) != address(0), "Dispatcher/CDM-not-zero-address"); + require(_toChainId != 0, "Dispatcher/chainId-not-zero"); + + crossDomainMessenger = _crossDomainMessenger; + toChainId = _toChainId; + } + + /* ============ External Functions ============ */ + + /// @inheritdoc ISingleMessageDispatcher + function dispatchMessage( + uint256 _toChainId, + address _to, + bytes calldata _data + ) external returns (bytes32) { + address _executorAddress = _getMessageExecutorAddress(_toChainId); + _checkExecutor(_executorAddress); + + uint256 _nonce = _incrementNonce(); + bytes32 _messageId = MessageLib.computeMessageId(_nonce, msg.sender, _to, _data); + + _sendMessage( + _executorAddress, + MessageLib.encodeMessage(_to, _data, _messageId, block.chainid, msg.sender) + ); + + emit MessageDispatched(_messageId, msg.sender, _toChainId, _to, _data); + + return _messageId; + } + + /// @inheritdoc IBatchedMessageDispatcher + function dispatchMessageBatch(uint256 _toChainId, MessageLib.Message[] calldata _messages) + external + returns (bytes32) + { + address _executorAddress = _getMessageExecutorAddress(_toChainId); + _checkExecutor(_executorAddress); + + uint256 _nonce = _incrementNonce(); + bytes32 _messageId = MessageLib.computeMessageBatchId(_nonce, msg.sender, _messages); + + _sendMessage( + _executorAddress, + MessageLib.encodeMessageBatch(_messages, _messageId, block.chainid, msg.sender) + ); + + emit MessageBatchDispatched(_messageId, msg.sender, _toChainId, _messages); + + return _messageId; + } + + /// @inheritdoc IMessageDispatcher + function getMessageExecutorAddress(uint256 _toChainId) external view returns (address) { + return _getMessageExecutorAddress(_toChainId); + } + + /** + * @notice Set executor contract address. + * @dev Will revert if it has already been set. + * @param _executor Address of the executor contract on the Optimism chain + */ + function setExecutor(IMessageExecutor _executor) external { + require(address(executor) == address(0), "Dispatcher/executor-already-set"); + executor = _executor; + } + + /* ============ Internal Functions ============ */ + + /** + * @notice Check toChainId to ensure messages can be dispatched to this chain. + * @dev Will revert if `_toChainId` is not supported. + * @param _toChainId ID of the chain receiving the message + */ + function _checkToChainId(uint256 _toChainId) internal view { + require(_toChainId == toChainId, "Dispatcher/chainId-not-supported"); + } + + /** + * @notice Check dispatch parameters to ensure messages can be dispatched. + * @dev Will revert if `executor` is not set. + * @param _executor Address of the executor contract on the Optimism chain + */ + function _checkExecutor(address _executor) internal pure { + require(_executor != address(0), "Dispatcher/executor-not-set"); + } + + /** + * @notice Retrieves address of the MessageExecutor contract on the receiving chain. + * @dev Will revert if `_toChainId` is not supported. + * @param _toChainId ID of the chain with which MessageDispatcher is communicating + * @return address MessageExecutor contract address + */ + function _getMessageExecutorAddress(uint256 _toChainId) internal view returns (address) { + _checkToChainId(_toChainId); + return address(executor); + } + + /** + * @notice Helper to increment nonce. + * @return uint256 Incremented nonce + */ + function _incrementNonce() internal returns (uint256) { + unchecked { + nonce++; + } + + return nonce; + } + + /** + * @notice Dispatch message to Optimism chain. + * @param _executor Address of the executor contract on the Optimism chain + * @param _message Message dispatched + */ + function _sendMessage(address _executor, bytes memory _message) internal { + crossDomainMessenger.sendMessage(_executor, _message, GAS_LIMIT); + } +} diff --git a/src/ethereum-optimism/EthereumToOptimismExecutor.sol b/src/ethereum-optimism/EthereumToOptimismExecutor.sol index 9a11cc3..4f276ca 100644 --- a/src/ethereum-optimism/EthereumToOptimismExecutor.sol +++ b/src/ethereum-optimism/EthereumToOptimismExecutor.sol @@ -4,43 +4,34 @@ pragma solidity 0.8.16; import { ICrossDomainMessenger } from "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; -import "../interfaces/ICrossChainExecutor.sol"; -import "../libraries/CallLib.sol"; +import "../interfaces/IMessageExecutor.sol"; +import "../libraries/MessageLib.sol"; /** - * @title CrossChainExecutorOptimism contract - * @notice The CrossChainExecutorOptimism contract executes calls from the Ethereum chain. - * These calls are sent by the `CrossChainRelayerOptimism` contract which lives on the Ethereum chain. + * @title MessageExecutorOptimism contract + * @notice The MessageExecutorOptimism contract executes messages from the Ethereum chain. + * These messages are sent by the `MessageDispatcherOptimism` contract which lives on the Ethereum chain. */ -contract CrossChainExecutorOptimism is ICrossChainExecutor { - /* ============ Custom Errors ============ */ - - /** - * @notice Emitted when a batch of calls fails to execute. - * @param relayer Address of the contract that relayed the calls on the origin chain - * @param nonce Nonce to uniquely identify the batch of calls that failed to execute - */ - error ExecuteCallsFailed(ICrossChainRelayer relayer, uint256 nonce); - +contract MessageExecutorOptimism is IMessageExecutor { /* ============ Variables ============ */ /// @notice Address of the Optimism cross domain messenger on the Optimism chain. ICrossDomainMessenger public immutable crossDomainMessenger; - /// @notice Address of the relayer contract on the Ethereum chain. - ICrossChainRelayer public relayer; + /// @notice Address of the dispatcher contract on the Ethereum chain. + IMessageDispatcher public dispatcher; /** - * @notice Nonce to uniquely identify the batch of calls that were executed - * nonce => boolean - * @dev Ensure that batch of calls cannot be replayed once they have been executed. + * @notice Mapping to uniquely identify the messages that were executed + * messageId => boolean + * @dev Ensure that messages cannot be replayed once they have been executed. */ - mapping(uint256 => bool) public executed; + mapping(bytes32 => bool) public executed; /* ============ Constructor ============ */ /** - * @notice CrossChainExecutorOptimism constructor. + * @notice MessageExecutorOptimism constructor. * @param _crossDomainMessenger Address of the Optimism cross domain messenger on the Optimism chain */ constructor(ICrossDomainMessenger _crossDomainMessenger) { @@ -50,49 +41,65 @@ contract CrossChainExecutorOptimism is ICrossChainExecutor { /* ============ External Functions ============ */ - /// @inheritdoc ICrossChainExecutor - function executeCalls( - uint256 _nonce, - address _sender, - CallLib.Call[] calldata _calls + /// @inheritdoc IMessageExecutor + function executeMessage( + address _to, + bytes calldata _data, + bytes32 _messageId, + uint256 _fromChainId, + address _from ) external { - ICrossChainRelayer _relayer = relayer; - _isAuthorized(_relayer); + IMessageDispatcher _dispatcher = dispatcher; + _isAuthorized(_dispatcher); + + bool _executedMessageId = executed[_messageId]; + executed[_messageId] = true; - bool _executedNonce = executed[_nonce]; - executed[_nonce] = true; + MessageLib.executeMessage(_to, _data, _messageId, _fromChainId, _from, _executedMessageId); + + emit MessageIdExecuted(_fromChainId, _messageId); + } + + /// @inheritdoc IMessageExecutor + function executeMessageBatch( + MessageLib.Message[] calldata _messages, + bytes32 _messageId, + uint256 _fromChainId, + address _from + ) external { + IMessageDispatcher _dispatcher = dispatcher; + _isAuthorized(_dispatcher); - bool _callsExecuted = CallLib.executeCalls(_nonce, _sender, _calls, _executedNonce); + bool _executedMessageId = executed[_messageId]; + executed[_messageId] = true; - if (!_callsExecuted) { - revert ExecuteCallsFailed(_relayer, _nonce); - } + MessageLib.executeMessageBatch(_messages, _messageId, _fromChainId, _from, _executedMessageId); - emit ExecutedCalls(_relayer, _nonce); + emit MessageIdExecuted(_fromChainId, _messageId); } /** - * @notice Set relayer contract address. + * @notice Set dispatcher contract address. * @dev Will revert if it has already been set. - * @param _relayer Address of the relayer contract on the Ethereum chain + * @param _dispatcher Address of the dispatcher contract on the Ethereum chain */ - function setRelayer(ICrossChainRelayer _relayer) external { - require(address(relayer) == address(0), "Executor/relayer-already-set"); - relayer = _relayer; + function setDispatcher(IMessageDispatcher _dispatcher) external { + require(address(dispatcher) == address(0), "Executor/dispatcher-already-set"); + dispatcher = _dispatcher; } /* ============ Internal Functions ============ */ /** - * @notice Check if sender is authorized to call `executeCalls`. - * @param _relayer Address of the relayer on the Ethereum chain + * @notice Check if sender is authorized to message `executeMessageBatch`. + * @param _dispatcher Address of the dispatcher on the Ethereum chain */ - function _isAuthorized(ICrossChainRelayer _relayer) internal view { + function _isAuthorized(IMessageDispatcher _dispatcher) internal view { ICrossDomainMessenger _crossDomainMessenger = crossDomainMessenger; require( msg.sender == address(_crossDomainMessenger) && - _crossDomainMessenger.xDomainMessageSender() == address(_relayer), + _crossDomainMessenger.xDomainMessageSender() == address(_dispatcher), "Executor/sender-unauthorized" ); } diff --git a/src/ethereum-optimism/EthereumToOptimismRelayer.sol b/src/ethereum-optimism/EthereumToOptimismRelayer.sol deleted file mode 100644 index 2ea326c..0000000 --- a/src/ethereum-optimism/EthereumToOptimismRelayer.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.16; - -import { ICrossDomainMessenger } from "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; - -import { ICrossChainExecutor } from "../interfaces/ICrossChainExecutor.sol"; -import { ICrossChainRelayer } from "../interfaces/ICrossChainRelayer.sol"; -import "../libraries/CallLib.sol"; - -/** - * @title CrossChainRelayerOptimism contract - * @notice The CrossChainRelayerOptimism contract allows a user or contract to send messages from Ethereum to Optimism. - * It lives on the Ethereum chain and communicates with the `CrossChainExecutorOptimism` contract on the Optimism chain. - */ -contract CrossChainRelayerOptimism is ICrossChainRelayer { - /* ============ Variables ============ */ - - /// @notice Address of the Optimism cross domain messenger on the Ethereum chain. - ICrossDomainMessenger public immutable crossDomainMessenger; - - /// @notice Address of the executor contract on the Optimism chain. - ICrossChainExecutor public executor; - - /// @notice Nonce to uniquely idenfity each batch of calls. - uint256 internal nonce; - - /* ============ Constructor ============ */ - - /** - * @notice CrossChainRelayerOptimism constructor. - * @param _crossDomainMessenger Address of the Optimism cross domain messenger - */ - constructor(ICrossDomainMessenger _crossDomainMessenger) { - require(address(_crossDomainMessenger) != address(0), "Relayer/CDM-not-zero-address"); - crossDomainMessenger = _crossDomainMessenger; - } - - /* ============ External Functions ============ */ - - /// @inheritdoc ICrossChainRelayer - function relayCalls(CallLib.Call[] calldata _calls, uint256 _gasLimit) - external - returns (uint256) - { - address _executorAddress = address(executor); - require(_executorAddress != address(0), "Relayer/executor-not-set"); - - unchecked { - nonce++; - } - - uint256 _nonce = nonce; - - crossDomainMessenger.sendMessage( - _executorAddress, - abi.encodeWithSelector(ICrossChainExecutor.executeCalls.selector, _nonce, msg.sender, _calls), - uint32(_gasLimit) - ); - - emit RelayedCalls(_nonce, msg.sender, _calls, _gasLimit); - - return _nonce; - } - - /** - * @notice Set executor contract address. - * @dev Will revert if it has already been set. - * @param _executor Address of the executor contract on the Optimism chain - */ - function setExecutor(ICrossChainExecutor _executor) external { - require(address(executor) == address(0), "Relayer/executor-already-set"); - executor = _executor; - } -} diff --git a/src/ethereum-polygon/EthereumToPolygonDispatcher.sol b/src/ethereum-polygon/EthereumToPolygonDispatcher.sol new file mode 100644 index 0000000..6a5e61b --- /dev/null +++ b/src/ethereum-polygon/EthereumToPolygonDispatcher.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.16; + +import { FxBaseRootTunnel } from "@maticnetwork/fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; + +import { IMessageExecutor } from "../interfaces/IMessageExecutor.sol"; +import { IMessageDispatcher, ISingleMessageDispatcher } from "../interfaces/ISingleMessageDispatcher.sol"; +import { IBatchedMessageDispatcher } from "../interfaces/IBatchedMessageDispatcher.sol"; + +import "../libraries/MessageLib.sol"; + +/** + * @title MessageDispatcherPolygon contract + * @notice The MessageDispatcherPolygon contract allows a user or contract to send messages from Ethereum to Polygon. + * It lives on the Ethereum chain and communicates with the `MessageExecutorPolygon` contract on the Polygon chain. + */ +contract MessageDispatcherPolygon is + ISingleMessageDispatcher, + IBatchedMessageDispatcher, + FxBaseRootTunnel +{ + /* ============ Variables ============ */ + + /// @notice Nonce used to compute unique `messageId`s. + uint256 internal nonce; + + /// @notice ID of the chain receiving the dispatched messages. i.e.: 137 for Mainnet, 80001 for Mumbai. + uint256 internal immutable toChainId; + + /* ============ Constructor ============ */ + + /** + * @notice MessageDispatcherPolygon constructor. + * @param _checkpointManager Address of the root chain manager contract on Ethereum + * @param _fxRoot Address of the state sender contract on Ethereum + * @param _toChainId ID of the chain receiving the dispatched messages + */ + constructor( + address _checkpointManager, + address _fxRoot, + uint256 _toChainId + ) FxBaseRootTunnel(_checkpointManager, _fxRoot) { + require(_toChainId != 0, "Dispatcher/chainId-not-zero"); + toChainId = _toChainId; + } + + /* ============ External Functions ============ */ + + /// @inheritdoc ISingleMessageDispatcher + function dispatchMessage( + uint256 _toChainId, + address _to, + bytes calldata _data + ) external returns (bytes32) { + _checkDispatchParams(_toChainId); + + uint256 _nonce = _incrementNonce(); + bytes32 _messageId = MessageLib.computeMessageId(_nonce, msg.sender, _to, _data); + + MessageLib.Message[] memory _messages = new MessageLib.Message[](1); + _messages[0] = MessageLib.Message({ to: _to, data: _data }); + + _sendMessageToChild(abi.encode(_messages, _messageId, block.chainid, msg.sender)); + + emit MessageDispatched(_messageId, msg.sender, _toChainId, _to, _data); + + return _messageId; + } + + /// @inheritdoc IBatchedMessageDispatcher + function dispatchMessageBatch(uint256 _toChainId, MessageLib.Message[] calldata _messages) + external + returns (bytes32) + { + _checkDispatchParams(_toChainId); + + uint256 _nonce = _incrementNonce(); + bytes32 _messageId = MessageLib.computeMessageBatchId(_nonce, msg.sender, _messages); + + _sendMessageToChild(abi.encode(_messages, _messageId, block.chainid, msg.sender)); + + emit MessageBatchDispatched(_messageId, msg.sender, _toChainId, _messages); + + return _messageId; + } + + /// @inheritdoc IMessageDispatcher + function getMessageExecutorAddress(uint256 _chainId) external view returns (address) { + _checkToChainId(_chainId); + return address(fxChildTunnel); + } + + /* ============ Internal Functions ============ */ + + /** + * @notice Check toChainId to ensure messages can be dispatched to this chain. + * @dev Will revert if `_toChainId` is not supported. + * @param _toChainId ID of the chain receiving the message + */ + function _checkToChainId(uint256 _toChainId) internal view { + require(_toChainId == toChainId, "Dispatcher/chainId-not-supported"); + } + + /** + * @notice Check dispatch parameters to ensure messages can be dispatched. + * @dev Will revert if `fxChildTunnel` is not set. + * @dev Will revert if `_toChainId` is not supported. + * @param _toChainId ID of the chain receiving the message + */ + function _checkDispatchParams(uint256 _toChainId) internal view { + require(address(fxChildTunnel) != address(0), "Dispatcher/fxChildTunnel-not-set"); + _checkToChainId(_toChainId); + } + + /** + * @notice Helper to increment nonce. + * @return uint256 Incremented nonce + */ + function _incrementNonce() internal returns (uint256) { + unchecked { + nonce++; + } + + return nonce; + } + + /** + * @inheritdoc FxBaseRootTunnel + * @dev This contract must not be used to receive and execute messages from Polygon. + * We need to implement the following function to be able to inherit from FxBaseRootTunnel. + */ + function _processMessageFromChild(bytes memory data) internal override { + /// no-op + } +} diff --git a/src/ethereum-polygon/EthereumToPolygonExecutor.sol b/src/ethereum-polygon/EthereumToPolygonExecutor.sol index d651327..24f7c9b 100644 --- a/src/ethereum-polygon/EthereumToPolygonExecutor.sol +++ b/src/ethereum-polygon/EthereumToPolygonExecutor.sol @@ -4,45 +4,36 @@ pragma solidity 0.8.16; import { FxBaseChildTunnel } from "@maticnetwork/fx-portal/contracts/tunnel/FxBaseChildTunnel.sol"; -import "../libraries/CallLib.sol"; +import "../libraries/MessageLib.sol"; /** - * @title CrossChainExecutorPolygon contract - * @notice The CrossChainExecutorPolygon contract executes calls from the Ethereum chain. - * These calls are sent by the `CrossChainRelayerPolygon` contract which lives on the Ethereum chain. + * @title MessageExecutorPolygon contract + * @notice The MessageExecutorPolygon contract executes messages from the Ethereum chain. + * These messages are sent by the `MessageDispatcherPolygon` contract which lives on the Ethereum chain. */ -contract CrossChainExecutorPolygon is FxBaseChildTunnel { - /* ============ Custom Errors ============ */ - - /** - * @notice Emitted when a batch of calls fails to execute. - * @param relayer Address of the contract that relayed the calls on the origin chain - * @param nonce Nonce to uniquely identify the batch of calls that failed to execute - */ - error ExecuteCallsFailed(address relayer, uint256 nonce); - +contract MessageExecutorPolygon is FxBaseChildTunnel { /* ============ Events ============ */ /** - * @notice Emitted when calls have successfully been executed. - * @param relayer Address of the contract that relayed the calls - * @param nonce Nonce to uniquely identify the batch of calls that were executed + * @notice Emitted when a message has successfully been executed. + * @param fromChainId ID of the chain that dispatched the message + * @param messageId ID uniquely identifying the message that was executed */ - event ExecutedCalls(address indexed relayer, uint256 indexed nonce); + event MessageIdExecuted(uint256 indexed fromChainId, bytes32 indexed messageId); /* ============ Variables ============ */ /** - * @notice Nonce to uniquely identify the batch of calls that were executed. - * nonce => boolean - * @dev Ensure that batch of calls cannot be replayed once they have been executed. + * @notice ID uniquely identifying the messages that were executed. + * messageId => boolean + * @dev Ensure that messages cannot be replayed once they have been executed. */ - mapping(uint256 => bool) public executed; + mapping(bytes32 => bool) public executed; /* ============ Constructor ============ */ /** - * @notice CrossChainExecutorPolygon constructor. + * @notice MessageExecutorPolygon constructor. * @param _fxChild Address of the FxChild contract on the Polygon chain */ constructor(address _fxChild) FxBaseChildTunnel(_fxChild) {} @@ -55,20 +46,38 @@ contract CrossChainExecutorPolygon is FxBaseChildTunnel { address _sender, bytes memory _data ) internal override validateSender(_sender) { - (uint256 _nonce, address _callsSender, CallLib.Call[] memory _calls) = abi.decode( - _data, - (uint256, address, CallLib.Call[]) - ); - - bool _executedNonce = executed[_nonce]; - executed[_nonce] = true; - - bool _callsExecuted = CallLib.executeCalls(_nonce, _callsSender, _calls, _executedNonce); - - if (!_callsExecuted) { - revert ExecuteCallsFailed(_sender, _nonce); + ( + MessageLib.Message[] memory _messages, + bytes32 _messageId, + uint256 _fromChainId, + address _from + ) = abi.decode(_data, (MessageLib.Message[], bytes32, uint256, address)); + + bool _executedMessageId = executed[_messageId]; + executed[_messageId] = true; + + if (_messages.length == 1) { + MessageLib.Message memory _message = _messages[0]; + MessageLib.executeMessage( + _message.to, + _message.data, + _messageId, + _fromChainId, + _from, + _executedMessageId + ); + + emit MessageIdExecuted(_fromChainId, _messageId); + } else { + MessageLib.executeMessageBatch( + _messages, + _messageId, + _fromChainId, + _from, + _executedMessageId + ); + + emit MessageIdExecuted(_fromChainId, _messageId); } - - emit ExecutedCalls(_sender, _nonce); } } diff --git a/src/ethereum-polygon/EthereumToPolygonRelayer.sol b/src/ethereum-polygon/EthereumToPolygonRelayer.sol deleted file mode 100644 index 560e193..0000000 --- a/src/ethereum-polygon/EthereumToPolygonRelayer.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.16; - -import { FxBaseRootTunnel } from "@maticnetwork/fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; - -import { ICrossChainRelayer } from "../interfaces/ICrossChainRelayer.sol"; -import "../libraries/CallLib.sol"; - -/** - * @title CrossChainRelayerPolygon contract - * @notice The CrossChainRelayerPolygon contract allows a user or contract to send messages from Ethereum to Polygon. - * It lives on the Ethereum chain and communicates with the `CrossChainExecutorPolygon` contract on the Polygon chain. - */ -contract CrossChainRelayerPolygon is ICrossChainRelayer, FxBaseRootTunnel { - /* ============ Variables ============ */ - - /// @notice Nonce to uniquely idenfity each batch of calls. - uint256 internal nonce; - - /* ============ Constructor ============ */ - - /** - * @notice CrossChainRelayerPolygon constructor. - * @param _checkpointManager Address of the root chain manager contract on Ethereum - * @param _fxRoot Address of the state sender contract on Ethereum - */ - constructor(address _checkpointManager, address _fxRoot) - FxBaseRootTunnel(_checkpointManager, _fxRoot) - {} - - /* ============ External Functions ============ */ - - /// @inheritdoc ICrossChainRelayer - function relayCalls(CallLib.Call[] calldata _calls, uint256 _gasLimit) - external - returns (uint256) - { - require(address(fxChildTunnel) != address(0), "Relayer/fxChildTunnel-not-set"); - - unchecked { - nonce++; - } - - uint256 _nonce = nonce; - - _sendMessageToChild(abi.encode(_nonce, msg.sender, _calls)); - - emit RelayedCalls(_nonce, msg.sender, _calls, _gasLimit); - - return _nonce; - } - - /* ============ Internal Functions ============ */ - - /** - * @inheritdoc FxBaseRootTunnel - * @dev This contract must not be used to receive and execute messages from Polygon. - * We need to implement the following function to be able to inherit from FxBaseRootTunnel. - */ - function _processMessageFromChild(bytes memory data) internal override { - /// no-op - } -} diff --git a/src/interfaces/IBatchedMessageDispatcher.sol b/src/interfaces/IBatchedMessageDispatcher.sol new file mode 100644 index 0000000..f563f2c --- /dev/null +++ b/src/interfaces/IBatchedMessageDispatcher.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.16; + +import "./IMessageDispatcher.sol"; + +/** + * @title ERC-5164: Cross-Chain Execution Standard, optional BatchMessageDispatcher extension + * @dev See https://eips.ethereum.org/EIPS/eip-5164 + */ +interface IBatchedMessageDispatcher is IMessageDispatcher { + /** + * @notice Dispatch `messages` to the receiving chain. + * @dev Must compute and return an ID uniquely identifying the `messages`. + * @dev Must emit the `MessageBatchDispatched` event when successfully dispatched. + * @param toChainId ID of the receiving chain + * @param messages Array of Message dispatched + * @return bytes32 ID uniquely identifying the `messages` + */ + function dispatchMessageBatch(uint256 toChainId, MessageLib.Message[] calldata messages) + external + returns (bytes32); +} diff --git a/src/interfaces/ICrossChainExecutor.sol b/src/interfaces/ICrossChainExecutor.sol deleted file mode 100644 index 08cd1e9..0000000 --- a/src/interfaces/ICrossChainExecutor.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.16; - -import "./ICrossChainRelayer.sol"; - -import "../libraries/CallLib.sol"; - -/** - * @title CrossChainExecutor interface - * @notice CrossChainExecutor interface of the ERC-5164 standard as defined in the EIP. - */ -interface ICrossChainExecutor { - /** - * @notice Emitted when calls have successfully been executed. - * @param relayer Address of the contract that relayed the calls on the origin chain - * @param nonce Nonce to uniquely identify the batch of calls - */ - event ExecutedCalls(ICrossChainRelayer indexed relayer, uint256 indexed nonce); - - /** - * @notice Execute calls from the origin chain. - * @dev Should authenticate that the call has been performed by the bridge transport layer. - * @dev Must revert if a call fails. - * @dev Must emit the `ExecutedCalls` event once calls have been executed. - * @param nonce Nonce to uniquely idenfity the batch of calls - * @param sender Address of the sender on the origin chain - * @param calls Array of calls being executed - */ - function executeCalls( - uint256 nonce, - address sender, - CallLib.Call[] calldata calls - ) external; -} diff --git a/src/interfaces/ICrossChainRelayer.sol b/src/interfaces/ICrossChainRelayer.sol deleted file mode 100644 index bb5056a..0000000 --- a/src/interfaces/ICrossChainRelayer.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.16; - -import "../libraries/CallLib.sol"; - -/** - * @title CrossChainRelayer interface - * @notice CrossChainRelayer interface of the ERC-5164 standard as defined in the EIP. - * @dev Use `ICrossChainRelayerPayable` if the bridge you want to integrate requires a payment in the native currency. - */ -interface ICrossChainRelayer { - /** - * @notice Custom error emitted if the `gasLimit` passed to `relayCalls` - * is greater than the one provided for free on the receiving chain. - * @param gasLimit Gas limit passed to `relayCalls` - * @param maxGasLimit Gas limit provided for free on the receiving chain - */ - error GasLimitTooHigh(uint256 gasLimit, uint256 maxGasLimit); - - /** - * @notice Emitted when calls have successfully been relayed to the executor chain. - * @param nonce Nonce to uniquely idenfity the batch of calls - * @param sender Address of the sender - * @param calls Array of calls being relayed - * @param gasLimit Maximum amount of gas required for the `calls` to be executed - */ - event RelayedCalls( - uint256 indexed nonce, - address indexed sender, - CallLib.Call[] calls, - uint256 gasLimit - ); - - /** - * @notice Relay the calls to the receiving chain. - * @dev Must increment a `nonce` so that the batch of calls can be uniquely identified. - * @dev Must emit the `RelayedCalls` event when successfully called. - * @param calls Array of calls being relayed - * @param gasLimit Maximum amount of gas required for the `calls` to be executed - * @return uint256 Nonce to uniquely idenfity the batch of calls - */ - function relayCalls(CallLib.Call[] calldata calls, uint256 gasLimit) external returns (uint256); -} diff --git a/src/interfaces/ICrossChainRelayerPayable.sol b/src/interfaces/ICrossChainRelayerPayable.sol deleted file mode 100644 index 83d7a96..0000000 --- a/src/interfaces/ICrossChainRelayerPayable.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.16; - -import "../libraries/CallLib.sol"; - -/** - * @title CrossChainRelayer interface - * @notice CrossChainRelayer interface of the ERC-5164 standard as defined in the EIP. - */ -interface ICrossChainRelayerPayable { - /** - * @notice Custom error emitted if the `gasLimit` passed to `relayCalls` - * is greater than the one provided for free on the receiving chain. - * @param gasLimit Gas limit passed to `relayCalls` - * @param maxGasLimit Gas limit provided for free on the receiving chain - */ - error GasLimitTooHigh(uint256 gasLimit, uint256 maxGasLimit); - - /** - * @notice Emitted when calls have successfully been relayed to the executor chain. - * @param nonce Nonce to uniquely idenfity the batch of calls - * @param sender Address of the sender - * @param calls Array of calls being relayed - * @param gasLimit Maximum amount of gas required for the `calls` to be executed - */ - event RelayedCalls( - uint256 indexed nonce, - address indexed sender, - CallLib.Call[] calls, - uint256 gasLimit - ); - - /** - * @notice Relay the calls to the receiving chain. - * @dev Must increment a `nonce` so that the batch of calls can be uniquely identified. - * @dev Must emit the `RelayedCalls` event when successfully called. - * @dev May require payment. Some bridges requires payment in the native currency, so the function is payable. - * @param calls Array of calls being relayed - * @param gasLimit Maximum amount of gas required for the `calls` to be executed - * @return uint256 Nonce to uniquely idenfity the batch of calls - */ - function relayCalls(CallLib.Call[] calldata calls, uint256 gasLimit) - external - payable - returns (uint256); -} diff --git a/src/interfaces/IMessageDispatcher.sol b/src/interfaces/IMessageDispatcher.sol new file mode 100644 index 0000000..39eb9af --- /dev/null +++ b/src/interfaces/IMessageDispatcher.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.16; + +import "../libraries/MessageLib.sol"; + +/** + * @title ERC-5164: Cross-Chain Execution Standard + * @dev See https://eips.ethereum.org/EIPS/eip-5164 + */ +interface IMessageDispatcher { + /** + * @notice Emitted when a message has successfully been dispatched to the executor chain. + * @param messageId ID uniquely identifying the message + * @param from Address that dispatched the message + * @param toChainId ID of the chain receiving the message + * @param to Address that will receive the message + * @param data Data that was dispatched + */ + event MessageDispatched( + bytes32 indexed messageId, + address indexed from, + uint256 indexed toChainId, + address to, + bytes data + ); + + /** + * @notice Emitted when a batch of messages has successfully been dispatched to the executor chain. + * @param messageId ID uniquely identifying the messages + * @param from Address that dispatched the messages + * @param toChainId ID of the chain receiving the messages + * @param messages Array of Message that was dispatched + */ + event MessageBatchDispatched( + bytes32 indexed messageId, + address indexed from, + uint256 indexed toChainId, + MessageLib.Message[] messages + ); + + /** + * @notice Retrieves address of the MessageExecutor contract on the receiving chain. + * @dev Must revert if `toChainId` is not supported. + * @param toChainId ID of the chain with which MessageDispatcher is communicating + * @return address MessageExecutor contract address + */ + function getMessageExecutorAddress(uint256 toChainId) external returns (address); +} diff --git a/src/interfaces/IMessageExecutor.sol b/src/interfaces/IMessageExecutor.sol new file mode 100644 index 0000000..a5e26e0 --- /dev/null +++ b/src/interfaces/IMessageExecutor.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.16; + +import "./IMessageDispatcher.sol"; + +import "../libraries/MessageLib.sol"; + +/** + * @title MessageExecutor interface + * @notice MessageExecutor interface of the ERC-5164 standard as defined in the EIP. + */ +interface IMessageExecutor { + /** + * @notice Emitted when a message has successfully been executed. + * @param fromChainId ID of the chain that dispatched the message + * @param messageId ID uniquely identifying the message that was executed + */ + event MessageIdExecuted(uint256 indexed fromChainId, bytes32 indexed messageId); + + /** + * @notice Execute message from the origin chain. + * @dev Should authenticate that the call has been performed by the bridge transport layer. + * @dev Must revert if the message fails. + * @dev Must emit the `MessageIdExecuted` event once the message has been executed. + * @param to Address that will receive `data` + * @param data Data forwarded to address `to` + * @param messageId ID uniquely identifying the message + * @param fromChainId ID of the chain that dispatched the message + * @param from Address of the sender on the origin chain + */ + function executeMessage( + address to, + bytes calldata data, + bytes32 messageId, + uint256 fromChainId, + address from + ) external; + + /** + * @notice Execute a batch messages from the origin chain. + * @dev Should authenticate that the call has been performed by the bridge transport layer. + * @dev Must revert if one of the messages fails. + * @dev Must emit the `MessageIdExecuted` event once messages have been executed. + * @param messages Array of messages being executed + * @param messageId ID uniquely identifying the messages + * @param fromChainId ID of the chain that dispatched the messages + * @param from Address of the sender on the origin chain + */ + function executeMessageBatch( + MessageLib.Message[] calldata messages, + bytes32 messageId, + uint256 fromChainId, + address from + ) external; +} diff --git a/src/interfaces/ISingleMessageDispatcher.sol b/src/interfaces/ISingleMessageDispatcher.sol new file mode 100644 index 0000000..fc62253 --- /dev/null +++ b/src/interfaces/ISingleMessageDispatcher.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.16; + +import "./IMessageDispatcher.sol"; + +/** + * @title ERC-5164: Cross-Chain Execution Standard, optional SingleMessageDispatcher extension + * @dev See https://eips.ethereum.org/EIPS/eip-5164 + */ +interface ISingleMessageDispatcher is IMessageDispatcher { + /** + * @notice Dispatch a message to the receiving chain. + * @dev Must compute and return an ID uniquely identifying the message. + * @dev Must emit the `MessageDispatched` event when successfully dispatched. + * @param toChainId ID of the receiving chain + * @param to Address on the receiving chain that will receive `data` + * @param data Data dispatched to the receiving chain + * @return bytes32 ID uniquely identifying the message + */ + function dispatchMessage( + uint256 toChainId, + address to, + bytes calldata data + ) external returns (bytes32); +} diff --git a/src/libraries/CallLib.sol b/src/libraries/CallLib.sol deleted file mode 100644 index a364106..0000000 --- a/src/libraries/CallLib.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.16; - -/** - * @title CallLib - * @notice Library to declare and manipulate Call(s). - */ -library CallLib { - /* ============ Structs ============ */ - - /** - * @notice Call data structure - * @param target Address that will be called on the receiving chain - * @param data Data that will be sent to the `target` address - */ - struct Call { - address target; - bytes data; - } - - /* ============ Events ============ */ - - /** - * @notice Emitted if a call to a target contract fails. - * @param nonce Nonce to uniquely idenfity the batch of calls - * @param callIndex Index of the call - * @param errorData Error data returned by the call - */ - event CallFailure(uint256 nonce, uint256 callIndex, bytes errorData); - - /** - * @notice Emitted if a call to a target contract succeeds. - * @param nonce Nonce to uniquely idenfity the batch of calls - * @param callIndex Index of the call - * @param successData Error data returned by the call - */ - event CallSuccess(uint256 nonce, uint256 callIndex, bytes successData); - - /* ============ Custom Errors ============ */ - - /** - * @notice Emitted when a batch of calls has already been executed. - * @param nonce Nonce to uniquely identify the batch of calls that were re-executed - */ - error CallsAlreadyExecuted(uint256 nonce); - - /* ============ Internal Functions ============ */ - - /** - * @notice Execute calls from the origin chain. - * @dev Will revert if `_calls` have already been executed. - * @param _nonce Nonce to uniquely idenfity the batch of calls - * @param _sender Address of the sender on the origin chain - * @param _calls Array of calls being executed - * @param _executedNonce Whether `_calls` have already been executed or not - * @return bool Whether the batch of calls was executed successfully or not - */ - function executeCalls( - uint256 _nonce, - address _sender, - Call[] memory _calls, - bool _executedNonce - ) internal returns (bool) { - if (_executedNonce) { - revert CallsAlreadyExecuted(_nonce); - } - - uint256 _callsLength = _calls.length; - - for (uint256 _callIndex; _callIndex < _callsLength; ) { - Call memory _call = _calls[_callIndex]; - - require(_call.target.code.length > 0, "CallLib/no-contract-at-target"); - - (bool _success, bytes memory _returnData) = _call.target.call( - abi.encodePacked(_call.data, _nonce, _sender) - ); - - if (!_success) { - emit CallFailure(_nonce, _callIndex, _returnData); - return false; - } - - emit CallSuccess(_nonce, _callIndex, _returnData); - - unchecked { - _callIndex++; - } - } - - return true; - } -} diff --git a/src/libraries/MessageLib.sol b/src/libraries/MessageLib.sol new file mode 100644 index 0000000..9583760 --- /dev/null +++ b/src/libraries/MessageLib.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.16; + +import { IMessageExecutor } from "../interfaces/IMessageExecutor.sol"; + +/** + * @title MessageLib + * @notice Library to declare and manipulate Message(s). + */ +library MessageLib { + /* ============ Structs ============ */ + + /** + * @notice Message data structure + * @param to Address that will be dispatched on the receiving chain + * @param data Data that will be sent to the `to` address + */ + struct Message { + address to; + bytes data; + } + + /* ============ Events ============ */ + + /* ============ Custom Errors ============ */ + + /** + * @notice Emitted when a messageId has already been executed. + * @param messageId ID uniquely identifying the message or message batch that were re-executed + */ + error MessageIdAlreadyExecuted(bytes32 messageId); + + /** + * @notice Emitted if a call to a contract fails. + * @param messageId ID uniquely identifying the message + * @param errorData Error data returned by the call + */ + error MessageFailure(bytes32 messageId, bytes errorData); + + /** + * @notice Emitted if a call to a contract fails inside a batch of messages. + * @param messageId ID uniquely identifying the batch of messages + * @param messageIndex Index of the message + * @param errorData Error data returned by the call + */ + error MessageBatchFailure(bytes32 messageId, uint256 messageIndex, bytes errorData); + + /* ============ Internal Functions ============ */ + + /** + * @notice Helper to compute messageId. + * @param nonce Monotonically increased nonce to ensure uniqueness + * @param from Address that dispatched the message + * @param to Address that will receive the message + * @param data Data that was dispatched + * @return bytes32 ID uniquely identifying the message that was dispatched + */ + function computeMessageId( + uint256 nonce, + address from, + address to, + bytes memory data + ) internal pure returns (bytes32) { + return keccak256(abi.encode(nonce, from, to, data)); + } + + /** + * @notice Helper to compute messageId for a batch of messages. + * @param nonce Monotonically increased nonce to ensure uniqueness + * @param from Address that dispatched the messages + * @param messages Array of Message dispatched + * @return bytes32 ID uniquely identifying the message that was dispatched + */ + function computeMessageBatchId( + uint256 nonce, + address from, + Message[] memory messages + ) internal pure returns (bytes32) { + return keccak256(abi.encode(nonce, from, messages)); + } + + /** + * @notice Helper to encode message for execution by the MessageExecutor. + * @param to Address that will receive the message + * @param data Data that will be dispatched + * @param messageId ID uniquely identifying the message being dispatched + * @param fromChainId ID of the chain that dispatched the message + * @param from Address that dispatched the message + */ + function encodeMessage( + address to, + bytes memory data, + bytes32 messageId, + uint256 fromChainId, + address from + ) internal pure returns (bytes memory) { + return + abi.encodeCall(IMessageExecutor.executeMessage, (to, data, messageId, fromChainId, from)); + } + + /** + * @notice Helper to encode a batch of messages for execution by the MessageExecutor. + * @param messages Array of Message that will be dispatched + * @param messageId ID uniquely identifying the batch of messages being dispatched + * @param fromChainId ID of the chain that dispatched the batch of messages + * @param from Address that dispatched the batch of messages + */ + function encodeMessageBatch( + Message[] memory messages, + bytes32 messageId, + uint256 fromChainId, + address from + ) internal pure returns (bytes memory) { + return + abi.encodeCall( + IMessageExecutor.executeMessageBatch, + (messages, messageId, fromChainId, from) + ); + } + + /** + * @notice Execute message from the origin chain. + * @dev Will revert if `message` has already been executed. + * @param to Address that will receive the message + * @param data Data that was dispatched + * @param messageId ID uniquely identifying message + * @param fromChainId ID of the chain that dispatched the `message` + * @param from Address of the sender on the origin chain + * @param executedMessageId Whether `message` has already been executed or not + */ + function executeMessage( + address to, + bytes memory data, + bytes32 messageId, + uint256 fromChainId, + address from, + bool executedMessageId + ) internal { + if (executedMessageId) { + revert MessageIdAlreadyExecuted(messageId); + } + + _requireContract(to); + + (bool _success, bytes memory _returnData) = to.call( + abi.encodePacked(data, messageId, fromChainId, from) + ); + + if (!_success) { + revert MessageFailure(messageId, _returnData); + } + } + + /** + * @notice Execute messages from the origin chain. + * @dev Will revert if `messages` have already been executed. + * @param messages Array of messages being executed + * @param messageId Nonce to uniquely identify the messages + * @param from Address of the sender on the origin chain + * @param fromChainId ID of the chain that dispatched the `messages` + * @param executedMessageId Whether `messages` have already been executed or not + */ + function executeMessageBatch( + Message[] memory messages, + bytes32 messageId, + uint256 fromChainId, + address from, + bool executedMessageId + ) internal { + if (executedMessageId) { + revert MessageIdAlreadyExecuted(messageId); + } + + uint256 _messagesLength = messages.length; + + for (uint256 _messageIndex; _messageIndex < _messagesLength; ) { + Message memory _message = messages[_messageIndex]; + _requireContract(_message.to); + + (bool _success, bytes memory _returnData) = _message.to.call( + abi.encodePacked(_message.data, messageId, fromChainId, from) + ); + + if (!_success) { + revert MessageBatchFailure(messageId, _messageIndex, _returnData); + } + + unchecked { + _messageIndex++; + } + } + } + + /** + * @notice Check that the call is being made to a contract. + * @param to Address to check + */ + function _requireContract(address to) internal view { + require(to.code.length > 0, "MessageLib/no-contract-at-to"); + } +} diff --git a/test/contracts/Greeter.sol b/test/contracts/Greeter.sol index a735d9d..78ac99c 100644 --- a/test/contracts/Greeter.sol +++ b/test/contracts/Greeter.sol @@ -9,9 +9,10 @@ contract Greeter is ExecutorAware { event SetGreeting( string greeting, - uint256 nonce, // nonce of the message that was executed - address l1Sender, // _msgSender() is the address who called `relayCalls` on the origin chain - address l2Sender // CrossChainExecutor contract + bytes32 messageId, // ID of the message that was executed + uint256 fromChainId, // ID of the chain that dispatched the message + address from, // _msgSender() is the address who dispatched the message on the origin chain + address l2Sender // MessageExecutor contract ); constructor(address _executor, string memory _greeting) ExecutorAware(_executor) { @@ -26,6 +27,6 @@ contract Greeter is ExecutorAware { require(isTrustedExecutor(msg.sender), "Greeter/sender-not-executor"); greeting = _greeting; - emit SetGreeting(_greeting, _nonce(), _msgSender(), msg.sender); + emit SetGreeting(_greeting, _messageId(), _fromChainId(), _msgSender(), msg.sender); } } diff --git a/test/fork/EthereumToOptimismFork.t.sol b/test/fork/EthereumToOptimismFork.t.sol index dd7b19a..1767ae2 100644 --- a/test/fork/EthereumToOptimismFork.t.sol +++ b/test/fork/EthereumToOptimismFork.t.sol @@ -8,21 +8,21 @@ import { ICrossDomainMessenger } from "@eth-optimism/contracts/libraries/bridge/ import { L2CrossDomainMessenger } from "@eth-optimism/contracts/L2/messaging/L2CrossDomainMessenger.sol"; import { AddressAliasHelper } from "@eth-optimism/contracts/standards/AddressAliasHelper.sol"; -import { ICrossChainRelayer } from "../../src/interfaces/ICrossChainRelayer.sol"; -import { ICrossChainExecutor } from "../../src/interfaces/ICrossChainExecutor.sol"; +import { IMessageDispatcher } from "../../src/interfaces/IMessageDispatcher.sol"; +import { IMessageExecutor } from "../../src/interfaces/IMessageExecutor.sol"; -import "../../src/ethereum-optimism/EthereumToOptimismRelayer.sol"; +import "../../src/ethereum-optimism/EthereumToOptimismDispatcher.sol"; import "../../src/ethereum-optimism/EthereumToOptimismExecutor.sol"; -import "../../src/libraries/CallLib.sol"; +import "../../src/libraries/MessageLib.sol"; -import "../contracts/Greeter.sol"; +import { Greeter } from "../contracts/Greeter.sol"; contract EthereumToOptimismForkTest is Test { uint256 public mainnetFork; uint256 public optimismFork; - CrossChainRelayerOptimism public relayer; - CrossChainExecutorOptimism public executor; + MessageDispatcherOptimism public dispatcher; + MessageExecutorOptimism public executor; Greeter public greeter; address public proxyOVML1CrossDomainMessenger = 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1; @@ -32,19 +32,34 @@ contract EthereumToOptimismForkTest is Test { string public l2Greeting = "Hello from L2"; uint256 public nonce = 1; + uint256 public toChainId = 10; + uint256 public fromChainId = 1; /* ============ Events to test ============ */ + event MessageDispatched( + bytes32 indexed messageId, + address indexed from, + uint256 indexed toChainId, + address to, + bytes data + ); - event RelayedCalls( - uint256 indexed nonce, - address indexed sender, - CallLib.Call[] calls, - uint256 gasLimit + event MessageBatchDispatched( + bytes32 indexed messageId, + address indexed from, + uint256 indexed toChainId, + MessageLib.Message[] messages ); - event ExecutedCalls(ICrossChainRelayer indexed relayer, uint256 indexed nonce); + event MessageIdExecuted(uint256 indexed fromChainId, bytes32 indexed messageId); - event SetGreeting(string greeting, uint256 nonce, address l1Sender, address l2Sender); + event SetGreeting( + string greeting, + bytes32 messageId, + uint256 fromChainId, + address from, + address l2Sender + ); /* ============ Setup ============ */ @@ -53,18 +68,21 @@ contract EthereumToOptimismForkTest is Test { optimismFork = vm.createFork(vm.rpcUrl("optimism")); } - function deployRelayer() public { + function deployDispatcher() public { vm.selectFork(mainnetFork); - relayer = new CrossChainRelayerOptimism(ICrossDomainMessenger(proxyOVML1CrossDomainMessenger)); + dispatcher = new MessageDispatcherOptimism( + ICrossDomainMessenger(proxyOVML1CrossDomainMessenger), + toChainId + ); - vm.makePersistent(address(relayer)); + vm.makePersistent(address(dispatcher)); } function deployExecutor() public { vm.selectFork(optimismFork); - executor = new CrossChainExecutorOptimism(ICrossDomainMessenger(l2CrossDomainMessenger)); + executor = new MessageExecutorOptimism(ICrossDomainMessenger(l2CrossDomainMessenger)); vm.makePersistent(address(executor)); } @@ -78,44 +96,46 @@ contract EthereumToOptimismForkTest is Test { } function deployAll() public { - deployRelayer(); + deployDispatcher(); deployExecutor(); deployGreeter(); } function setExecutor() public { vm.selectFork(mainnetFork); - relayer.setExecutor(executor); + dispatcher.setExecutor(executor); } - function setRelayer() public { + function setDispatcher() public { vm.selectFork(optimismFork); - executor.setRelayer(relayer); + executor.setDispatcher(dispatcher); } function setAll() public { setExecutor(); - setRelayer(); + setDispatcher(); } /* ============ Tests ============ */ - function testRelayer() public { - deployRelayer(); + function testDispatcher() public { + deployDispatcher(); deployExecutor(); setExecutor(); - assertEq(address(relayer.crossDomainMessenger()), proxyOVML1CrossDomainMessenger); - assertEq(address(relayer.executor()), address(executor)); + address _executorAddress = dispatcher.getMessageExecutorAddress(toChainId); + + assertEq(address(dispatcher.crossDomainMessenger()), proxyOVML1CrossDomainMessenger); + assertEq(_executorAddress, address(executor)); } function testExecutor() public { - deployRelayer(); + deployDispatcher(); deployExecutor(); - setRelayer(); + setDispatcher(); assertEq(address(executor.crossDomainMessenger()), l2CrossDomainMessenger); - assertEq(address(executor.relayer()), address(relayer)); + assertEq(address(executor.dispatcher()), address(dispatcher)); } function testGreeter() public { @@ -125,49 +145,110 @@ contract EthereumToOptimismForkTest is Test { assertEq(greeter.greeting(), l2Greeting); } - /* ============ relayCalls ============ */ - function testRelayCalls() public { + /* ============ dispatchMessage ============ */ + function testDispatchMessage() public { deployAll(); setAll(); vm.selectFork(mainnetFork); - CallLib.Call[] memory _calls = new CallLib.Call[](1); + address _to = address(greeter); + bytes memory _data = abi.encodeCall(Greeter.setGreeting, (l1Greeting)); + + bytes32 _expectedMessageId = MessageLib.computeMessageId(nonce, address(this), _to, _data); + + vm.expectEmit(true, true, true, true, address(dispatcher)); + emit MessageDispatched(_expectedMessageId, address(this), toChainId, _to, _data); + + bytes32 _messageId = dispatcher.dispatchMessage(toChainId, _to, _data); + + assertEq(_messageId, _expectedMessageId); + } + + function testDispatchMessageChainIdNotSupported() public { + deployAll(); + + vm.selectFork(mainnetFork); + + address _to = address(greeter); + bytes memory _data = abi.encodeCall(Greeter.setGreeting, (l1Greeting)); + + vm.expectRevert(bytes("Dispatcher/chainId-not-supported")); + + dispatcher.dispatchMessage(42161, _to, _data); + } + + function testDispatchMessageExecutorNotSet() public { + deployAll(); + + vm.selectFork(mainnetFork); + + address _to = address(greeter); + bytes memory _data = abi.encodeCall(Greeter.setGreeting, (l1Greeting)); + + vm.expectRevert(bytes("Dispatcher/executor-not-set")); + + dispatcher.dispatchMessage(toChainId, _to, _data); + } - _calls[0] = CallLib.Call({ - target: address(greeter), - data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) + /* ============ dispatchMessageBatch ============ */ + function testDispatchMessageBatch() public { + deployAll(); + setAll(); + + vm.selectFork(mainnetFork); + + MessageLib.Message[] memory _messages = new MessageLib.Message[](1); + _messages[0] = MessageLib.Message({ + to: address(greeter), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) }); - vm.expectEmit(true, true, true, true, address(relayer)); + bytes32 _expectedMessageId = MessageLib.computeMessageBatchId(nonce, address(this), _messages); - emit RelayedCalls(nonce, address(this), _calls, 200000); + vm.expectEmit(true, true, true, true, address(dispatcher)); + emit MessageBatchDispatched(_expectedMessageId, address(this), toChainId, _messages); - uint256 _nonce = relayer.relayCalls(_calls, 200000); + bytes32 _messageId = dispatcher.dispatchMessageBatch(toChainId, _messages); - assertEq(_nonce, nonce); + assertEq(_messageId, _expectedMessageId); } - function testExecutorNotSet() public { + function testDispatchMessageBatchChainIdNotSupported() public { deployAll(); vm.selectFork(mainnetFork); - CallLib.Call[] memory _calls = new CallLib.Call[](1); + MessageLib.Message[] memory _messages = new MessageLib.Message[](1); + _messages[0] = MessageLib.Message({ + to: address(greeter), + data: abi.encodeCall(Greeter.setGreeting, l1Greeting) + }); + + vm.expectRevert(bytes("Dispatcher/chainId-not-supported")); + + dispatcher.dispatchMessageBatch(42161, _messages); + } + + function testDispatchMessageBatchExecutorNotSet() public { + deployAll(); + + vm.selectFork(mainnetFork); - _calls[0] = CallLib.Call({ - target: address(greeter), - data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) + MessageLib.Message[] memory _messages = new MessageLib.Message[](1); + _messages[0] = MessageLib.Message({ + to: address(greeter), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) }); - vm.expectRevert(bytes("Relayer/executor-not-set")); + vm.expectRevert(bytes("Dispatcher/executor-not-set")); - relayer.relayCalls(_calls, 200000); + dispatcher.dispatchMessageBatch(toChainId, _messages); } - /* ============ executeCalls ============ */ + /* ============ executeMessage ============ */ - function testExecuteCalls() public { + function testExecuteMessage() public { deployAll(); setAll(); @@ -175,31 +256,83 @@ contract EthereumToOptimismForkTest is Test { assertEq(greeter.greet(), l2Greeting); - CallLib.Call[] memory _calls = new CallLib.Call[](1); + L2CrossDomainMessenger l2Bridge = L2CrossDomainMessenger(l2CrossDomainMessenger); + + vm.startPrank(AddressAliasHelper.applyL1ToL2Alias(proxyOVML1CrossDomainMessenger)); + + address _to = address(greeter); + bytes memory _data = abi.encodeCall(Greeter.setGreeting, (l1Greeting)); + + bytes32 _expectedMessageId = MessageLib.computeMessageId(nonce, address(this), _to, _data); + + vm.expectEmit(true, true, true, true, address(greeter)); + emit SetGreeting(l1Greeting, _expectedMessageId, fromChainId, address(this), address(executor)); + + vm.expectEmit(true, true, true, true, address(executor)); + emit MessageIdExecuted(fromChainId, _expectedMessageId); + + l2Bridge.relayMessage( + address(executor), + address(dispatcher), + abi.encodeCall( + IMessageExecutor.executeMessage, + (_to, _data, _expectedMessageId, fromChainId, address(this)) + ), + l2Bridge.messageNonce() + 1 + ); + + assertEq(greeter.greet(), l1Greeting); + } + + function testExecuteMessageIsUnauthorized() public { + deployAll(); + setAll(); + + vm.selectFork(optimismFork); + + address _to = address(greeter); + bytes memory _data = abi.encodeCall(Greeter.setGreeting, (l1Greeting)); + + vm.expectRevert(bytes("Executor/sender-unauthorized")); + + bytes32 _messageId = MessageLib.computeMessageId(nonce, address(this), _to, _data); + executor.executeMessage(_to, _data, _messageId, fromChainId, address(this)); + } + + /* ============ executeMessageBatch ============ */ - _calls[0] = CallLib.Call({ - target: address(greeter), - data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) + function testExecuteMessageBatch() public { + deployAll(); + setAll(); + + vm.selectFork(optimismFork); + + assertEq(greeter.greet(), l2Greeting); + + MessageLib.Message[] memory _messages = new MessageLib.Message[](1); + _messages[0] = MessageLib.Message({ + to: address(greeter), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) }); L2CrossDomainMessenger l2Bridge = L2CrossDomainMessenger(l2CrossDomainMessenger); vm.startPrank(AddressAliasHelper.applyL1ToL2Alias(proxyOVML1CrossDomainMessenger)); + bytes32 _expectedMessageId = MessageLib.computeMessageBatchId(nonce, address(this), _messages); + vm.expectEmit(true, true, true, true, address(greeter)); - emit SetGreeting(l1Greeting, nonce, address(this), address(executor)); + emit SetGreeting(l1Greeting, _expectedMessageId, fromChainId, address(this), address(executor)); vm.expectEmit(true, true, true, true, address(executor)); - emit ExecutedCalls(relayer, nonce); + emit MessageIdExecuted(fromChainId, _expectedMessageId); l2Bridge.relayMessage( address(executor), - address(relayer), - abi.encodeWithSignature( - "executeCalls(uint256,address,(address,bytes)[])", - nonce, - address(this), - _calls + address(dispatcher), + abi.encodeCall( + IMessageExecutor.executeMessageBatch, + (_messages, _expectedMessageId, fromChainId, address(this)) ), l2Bridge.messageNonce() + 1 ); @@ -207,22 +340,22 @@ contract EthereumToOptimismForkTest is Test { assertEq(greeter.greet(), l1Greeting); } - function testIsAuthorized() public { + function testExecuteMessageBatchIsUnauthorized() public { deployAll(); setAll(); vm.selectFork(optimismFork); - CallLib.Call[] memory _calls = new CallLib.Call[](1); - - _calls[0] = CallLib.Call({ - target: address(greeter), - data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) + MessageLib.Message[] memory _messages = new MessageLib.Message[](1); + _messages[0] = MessageLib.Message({ + to: address(greeter), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) }); vm.expectRevert(bytes("Executor/sender-unauthorized")); - executor.executeCalls(nonce, address(this), _calls); + bytes32 _messageId = MessageLib.computeMessageBatchId(nonce, address(this), _messages); + executor.executeMessageBatch(_messages, _messageId, fromChainId, address(this)); } /* ============ Setters ============ */ diff --git a/test/fork/EthereumToPolygonFork.t.sol b/test/fork/EthereumToPolygonFork.t.sol index 94fe803..fa61eea 100644 --- a/test/fork/EthereumToPolygonFork.t.sol +++ b/test/fork/EthereumToPolygonFork.t.sol @@ -4,21 +4,21 @@ pragma solidity 0.8.16; import "forge-std/Test.sol"; -import { ICrossChainRelayer } from "../../src/interfaces/ICrossChainRelayer.sol"; -import { ICrossChainExecutor } from "../../src/interfaces/ICrossChainExecutor.sol"; +import { IMessageDispatcher } from "../../src/interfaces/IMessageDispatcher.sol"; +import { IMessageExecutor } from "../../src/interfaces/IMessageExecutor.sol"; -import "../../src/ethereum-polygon/EthereumToPolygonRelayer.sol"; +import "../../src/ethereum-polygon/EthereumToPolygonDispatcher.sol"; import "../../src/ethereum-polygon/EthereumToPolygonExecutor.sol"; -import "../../src/libraries/CallLib.sol"; +import "../../src/libraries/MessageLib.sol"; -import "../contracts/Greeter.sol"; +import { Greeter } from "../contracts/Greeter.sol"; contract EthereumToPolygonForkTest is Test { uint256 public mainnetFork; uint256 public polygonFork; - CrossChainRelayerPolygon public relayer; - CrossChainExecutorPolygon public executor; + MessageDispatcherPolygon public dispatcher; + MessageExecutorPolygon public executor; Greeter public greeter; address public checkpointManager = 0x86E4Dc95c7FBdBf52e33D563BbDB00823894C287; @@ -29,23 +29,38 @@ contract EthereumToPolygonForkTest is Test { string public l2Greeting = "Hello from L2"; uint256 public nonce = 1; + uint256 public toChainId = 137; + uint256 public fromChainId = 1; /* ============ Events to test ============ */ + event MessageDispatched( + bytes32 indexed messageId, + address indexed from, + uint256 indexed toChainId, + address to, + bytes data + ); - event RelayedCalls( - uint256 indexed nonce, - address indexed sender, - CallLib.Call[] calls, - uint256 gasLimit + event MessageBatchDispatched( + bytes32 indexed messageId, + address indexed from, + uint256 indexed toChainId, + MessageLib.Message[] messages ); - event ExecutedCalls(ICrossChainRelayer indexed relayer, uint256 indexed nonce); + event MessageIdExecuted(uint256 indexed fromChainId, bytes32 indexed messageId); - event SetGreeting(string greeting, uint256 nonce, address l1Sender, address l2Sender); + event SetGreeting( + string greeting, + bytes32 messageId, + uint256 fromChainId, + address from, + address l2Sender + ); /* ============ Errors to test ============ */ - error CallFailure(uint256 callIndex, bytes errorData); + error MessageFailure(uint256 messageIndex, bytes errorData); /* ============ Setup ============ */ @@ -54,18 +69,18 @@ contract EthereumToPolygonForkTest is Test { polygonFork = vm.createFork(vm.rpcUrl("polygon")); } - function deployRelayer() public { + function deployDispatcher() public { vm.selectFork(mainnetFork); - relayer = new CrossChainRelayerPolygon(checkpointManager, fxRoot); + dispatcher = new MessageDispatcherPolygon(checkpointManager, fxRoot, toChainId); - vm.makePersistent(address(relayer)); + vm.makePersistent(address(dispatcher)); } function deployExecutor() public { vm.selectFork(polygonFork); - executor = new CrossChainExecutorPolygon(fxChild); + executor = new MessageExecutorPolygon(fxChild); vm.makePersistent(address(executor)); } @@ -79,19 +94,19 @@ contract EthereumToPolygonForkTest is Test { } function deployAll() public { - deployRelayer(); + deployDispatcher(); deployExecutor(); deployGreeter(); } function setFxChildTunnel() public { vm.selectFork(mainnetFork); - relayer.setFxChildTunnel(address(executor)); + dispatcher.setFxChildTunnel(address(executor)); } function setFxRootTunnel() public { vm.selectFork(polygonFork); - executor.setFxRootTunnel(address(relayer)); + executor.setFxRootTunnel(address(dispatcher)); } function setAll() public { @@ -100,25 +115,24 @@ contract EthereumToPolygonForkTest is Test { } /* ============ Tests ============ */ - - function testRelayer() public { - deployRelayer(); + function testDispatcher() public { + deployDispatcher(); deployExecutor(); setFxChildTunnel(); - assertEq(address(relayer.checkpointManager()), checkpointManager); - assertEq(address(relayer.fxRoot()), fxRoot); + assertEq(address(dispatcher.checkpointManager()), checkpointManager); + assertEq(address(dispatcher.fxRoot()), fxRoot); - assertEq(relayer.fxChildTunnel(), address(executor)); + assertEq(dispatcher.fxChildTunnel(), address(executor)); } function testExecutor() public { deployExecutor(); - deployRelayer(); + deployDispatcher(); setFxRootTunnel(); assertEq(executor.fxChild(), fxChild); - assertEq(executor.fxRootTunnel(), address(relayer)); + assertEq(executor.fxRootTunnel(), address(dispatcher)); } function testGreeter() public { @@ -128,46 +142,192 @@ contract EthereumToPolygonForkTest is Test { assertEq(greeter.greeting(), l2Greeting); } - function testRelayCalls() public { + /* ============ dispatchMessage ============ */ + function testDispatchMessage() public { deployAll(); setAll(); vm.selectFork(mainnetFork); - CallLib.Call[] memory _calls = new CallLib.Call[](1); + address _to = address(greeter); + bytes memory _data = abi.encodeCall(Greeter.setGreeting, (l1Greeting)); - _calls[0] = CallLib.Call({ - target: address(greeter), - data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) - }); + bytes32 _expectedMessageId = MessageLib.computeMessageId(nonce, address(this), _to, _data); + + vm.expectEmit(true, true, true, true, address(dispatcher)); + emit MessageDispatched(_expectedMessageId, address(this), toChainId, _to, _data); + + bytes32 _messageId = dispatcher.dispatchMessage(toChainId, _to, _data); + assertEq(_messageId, _expectedMessageId); + } + + function testDispatchMessageFxChildTunnelNotSet() public { + deployAll(); + + vm.selectFork(mainnetFork); + + address _to = address(greeter); + bytes memory _data = abi.encodeCall(Greeter.setGreeting, (l1Greeting)); + + vm.expectRevert(bytes("Dispatcher/fxChildTunnel-not-set")); + dispatcher.dispatchMessage(toChainId, _to, _data); + } - vm.expectEmit(true, true, true, true, address(relayer)); + function testDispatchMessageChainIdNotSupported() public { + deployAll(); + setAll(); + + vm.selectFork(mainnetFork); + + address _to = address(greeter); + bytes memory _data = abi.encodeCall(Greeter.setGreeting, (l1Greeting)); + + vm.expectRevert(bytes("Dispatcher/chainId-not-supported")); + dispatcher.dispatchMessage(10, _to, _data); + } + + /* ============ dispatchMessageBatch ============ */ + function testDispatchMessageBatch() public { + deployAll(); + setAll(); + + vm.selectFork(mainnetFork); + + MessageLib.Message[] memory _messages = new MessageLib.Message[](1); + _messages[0] = MessageLib.Message({ + to: address(greeter), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) + }); - emit RelayedCalls(nonce, address(this), _calls, 200000); + bytes32 _expectedMessageId = MessageLib.computeMessageBatchId(nonce, address(this), _messages); - uint256 _nonce = relayer.relayCalls(_calls, 200000); + vm.expectEmit(true, true, true, true, address(dispatcher)); + emit MessageBatchDispatched(_expectedMessageId, address(this), toChainId, _messages); - assertEq(_nonce, nonce); + bytes32 _messageId = dispatcher.dispatchMessageBatch(toChainId, _messages); + assertEq(_messageId, _expectedMessageId); } - function testFxChildTunnelNotSet() public { + function testDispatchMessageBatchFxChildTunnelNotSet() public { deployAll(); vm.selectFork(mainnetFork); - CallLib.Call[] memory _calls = new CallLib.Call[](1); + MessageLib.Message[] memory _messages = new MessageLib.Message[](1); + _messages[0] = MessageLib.Message({ + to: address(greeter), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) + }); + + vm.expectRevert(bytes("Dispatcher/fxChildTunnel-not-set")); + dispatcher.dispatchMessageBatch(toChainId, _messages); + } + + function testDispatchMessageBatchChainIdNotSupported() public { + deployAll(); + setAll(); + + vm.selectFork(mainnetFork); - _calls[0] = CallLib.Call({ - target: address(greeter), - data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) + MessageLib.Message[] memory _messages = new MessageLib.Message[](1); + _messages[0] = MessageLib.Message({ + to: address(greeter), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) }); - vm.expectRevert(bytes("Relayer/fxChildTunnel-not-set")); + vm.expectRevert(bytes("Dispatcher/chainId-not-supported")); + dispatcher.dispatchMessageBatch(10, _messages); + } + + /* ============ executeMessage ============ */ + function testExecuteMessage() public { + deployAll(); + setAll(); + + vm.selectFork(polygonFork); + + assertEq(greeter.greet(), l2Greeting); + + address _to = address(greeter); + bytes memory _data = abi.encodeCall(Greeter.setGreeting, (l1Greeting)); + + MessageLib.Message[] memory _messages = new MessageLib.Message[](1); + _messages[0] = MessageLib.Message({ to: _to, data: _data }); + + vm.startPrank(fxChild); + + bytes32 _expectedMessageId = MessageLib.computeMessageId(nonce, address(this), _to, _data); + + vm.expectEmit(true, true, true, true, address(greeter)); + emit SetGreeting(l1Greeting, _expectedMessageId, fromChainId, address(this), address(executor)); + + vm.expectEmit(true, true, true, true, address(executor)); + emit MessageIdExecuted(fromChainId, _expectedMessageId); + + executor.processMessageFromRoot( + 1, + address(dispatcher), + abi.encode(_messages, _expectedMessageId, fromChainId, address(this)) + ); + + assertEq(greeter.greet(), l1Greeting); + } + + function testExecuteMessageToNotZeroAddress() public { + deployAll(); + setAll(); + + vm.selectFork(polygonFork); + + assertEq(greeter.greet(), l2Greeting); + + address _to = address(0); + bytes memory _data = abi.encodeCall(Greeter.setGreeting, (l1Greeting)); + + MessageLib.Message[] memory _messages = new MessageLib.Message[](1); + _messages[0] = MessageLib.Message({ to: _to, data: _data }); + + vm.startPrank(fxChild); + + bytes32 _expectedMessageId = MessageLib.computeMessageId(nonce, address(this), _to, _data); + + vm.expectRevert(bytes("MessageLib/no-contract-at-to")); + executor.processMessageFromRoot( + 1, + address(dispatcher), + abi.encode(_messages, _expectedMessageId, fromChainId, address(this)) + ); + } + + function testExecuteMessageFailure() public { + deployAll(); + setAll(); + + vm.selectFork(polygonFork); + + address _to = address(this); + bytes memory _data = abi.encodeCall(Greeter.setGreeting, (l1Greeting)); + + MessageLib.Message[] memory _messages = new MessageLib.Message[](1); + _messages[0] = MessageLib.Message({ to: _to, data: _data }); + + vm.startPrank(fxChild); + + bytes32 _expectedMessageId = MessageLib.computeMessageId(nonce, address(this), _to, _data); + + vm.expectRevert( + abi.encodeWithSelector(MessageLib.MessageFailure.selector, _expectedMessageId, bytes("")) + ); - relayer.relayCalls(_calls, 200000); + executor.processMessageFromRoot( + 1, + address(dispatcher), + abi.encode(_messages, _expectedMessageId, fromChainId, address(this)) + ); } - function testExecuteCalls() public { + /* ============ executeMessageBatch ============ */ + function testExecuteMessageBatch() public { deployAll(); setAll(); @@ -175,27 +335,37 @@ contract EthereumToPolygonForkTest is Test { assertEq(greeter.greet(), l2Greeting); - CallLib.Call[] memory _calls = new CallLib.Call[](1); + MessageLib.Message[] memory _messages = new MessageLib.Message[](2); + _messages[0] = MessageLib.Message({ + to: address(greeter), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) + }); - _calls[0] = CallLib.Call({ - target: address(greeter), - data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) + _messages[1] = MessageLib.Message({ + to: address(greeter), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) }); vm.startPrank(fxChild); + bytes32 _expectedMessageId = MessageLib.computeMessageBatchId(nonce, address(this), _messages); + vm.expectEmit(true, true, true, true, address(greeter)); - emit SetGreeting(l1Greeting, nonce, address(this), address(executor)); + emit SetGreeting(l1Greeting, _expectedMessageId, fromChainId, address(this), address(executor)); vm.expectEmit(true, true, true, true, address(executor)); - emit ExecutedCalls(relayer, nonce); + emit MessageIdExecuted(fromChainId, _expectedMessageId); - executor.processMessageFromRoot(1, address(relayer), abi.encode(nonce, address(this), _calls)); + executor.processMessageFromRoot( + 1, + address(dispatcher), + abi.encode(_messages, _expectedMessageId, fromChainId, address(this)) + ); assertEq(greeter.greet(), l1Greeting); } - function testExecuteCallsTargetNotZeroAddress() public { + function testExecuteMessageBatchToNotZeroAddress() public { deployAll(); setAll(); @@ -203,45 +373,67 @@ contract EthereumToPolygonForkTest is Test { assertEq(greeter.greet(), l2Greeting); - CallLib.Call[] memory _calls = new CallLib.Call[](1); + MessageLib.Message[] memory _messages = new MessageLib.Message[](2); + _messages[0] = MessageLib.Message({ + to: address(0), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) + }); - _calls[0] = CallLib.Call({ - target: address(0), - data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) + _messages[0] = MessageLib.Message({ + to: address(0), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) }); vm.startPrank(fxChild); - vm.expectRevert(bytes("CallLib/no-contract-at-target")); - executor.processMessageFromRoot(1, address(relayer), abi.encode(nonce, address(this), _calls)); + bytes32 _expectedMessageId = MessageLib.computeMessageBatchId(nonce, address(this), _messages); + + vm.expectRevert(bytes("MessageLib/no-contract-at-to")); + executor.processMessageFromRoot( + 1, + address(dispatcher), + abi.encode(_messages, _expectedMessageId, fromChainId, address(this)) + ); } - function testCallFailure() public { + function testExecuteMessageBatchFailure() public { deployAll(); setAll(); vm.selectFork(polygonFork); - CallLib.Call[] memory _calls = new CallLib.Call[](1); + MessageLib.Message[] memory _messages = new MessageLib.Message[](2); + _messages[0] = MessageLib.Message({ + to: address(this), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) + }); - _calls[0] = CallLib.Call({ - target: address(this), - data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) + _messages[1] = MessageLib.Message({ + to: address(this), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) }); vm.startPrank(fxChild); + bytes32 _expectedMessageId = MessageLib.computeMessageBatchId(nonce, address(this), _messages); + vm.expectRevert( abi.encodeWithSelector( - CrossChainExecutorPolygon.ExecuteCallsFailed.selector, - address(relayer), - 1 + MessageLib.MessageBatchFailure.selector, + _expectedMessageId, + 0, + bytes("") ) ); - executor.processMessageFromRoot(1, address(relayer), abi.encode(nonce, address(this), _calls)); + executor.processMessageFromRoot( + 1, + address(dispatcher), + abi.encode(_messages, _expectedMessageId, fromChainId, address(this)) + ); } + /* ============ setGreeting ============ */ function testSetGreetingError() public { deployAll(); @@ -251,4 +443,27 @@ contract EthereumToPolygonForkTest is Test { greeter.setGreeting(l2Greeting); } + + /* ============ getMessageExecutorAddress ============ */ + function testGetMessageExecutorAddress() public { + deployAll(); + setAll(); + + vm.selectFork(polygonFork); + + address _executorAddress = dispatcher.getMessageExecutorAddress(toChainId); + + assertEq(_executorAddress, address(executor)); + } + + function testGetMessageExecutorAddressChainIdUnsupported() public { + deployAll(); + setAll(); + + vm.selectFork(polygonFork); + + vm.expectRevert(bytes("Dispatcher/chainId-not-supported")); + + dispatcher.getMessageExecutorAddress(10); + } } diff --git a/test/unit/ethereum-arbitrum/EthereumToArbitrumDispatcher.t.sol b/test/unit/ethereum-arbitrum/EthereumToArbitrumDispatcher.t.sol new file mode 100644 index 0000000..1909f02 --- /dev/null +++ b/test/unit/ethereum-arbitrum/EthereumToArbitrumDispatcher.t.sol @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.16; + +import "forge-std/Test.sol"; + +import { IInbox } from "@arbitrum/nitro-contracts/src/bridge/IInbox.sol"; + +import { MessageDispatcherArbitrum } from "../../../src/ethereum-arbitrum/EthereumToArbitrumDispatcher.sol"; +import { MessageExecutorArbitrum } from "../../../src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol"; +import { IMessageDispatcher } from "../../../src/interfaces/IMessageDispatcher.sol"; +import "../../../src/libraries/MessageLib.sol"; + +import { Greeter } from "../../contracts/Greeter.sol"; +import { ArbInbox } from "../../contracts/mock/ArbInbox.sol"; + +contract MessageDispatcherArbitrumUnitTest is Test { + ArbInbox public inbox = new ArbInbox(); + MessageExecutorArbitrum public executor = + MessageExecutorArbitrum(0x77E395E2bfCE67C718C8Ab812c86328EaE356f07); + Greeter public greeter = Greeter(0x720003dC4EA5aCDA0204823B98E014f095E667f8); + + address public from = 0xa3a935315931A09A4e9B8A865517Cc18923497Ad; + address public attacker = 0xdBdDa361Db11Adf8A51dab8a511a8ee89128E89A; + + uint256 public gasLimit = 1000000; + + uint256 public toChainId = 42161; + + uint256 public maxSubmissionCost = 1 ether; + uint256 public gasPriceBid = 500; + uint256 public nonce = 1; + + string public l1Greeting = "Hello from L1"; + + MessageDispatcherArbitrum public dispatcher; + MessageLib.Message[] public messages; + + /* ============ Events to test ============ */ + event MessageDispatched( + bytes32 indexed messageId, + address indexed from, + uint256 indexed toChainId, + address to, + bytes data + ); + + event MessageBatchDispatched( + bytes32 indexed messageId, + address indexed from, + uint256 indexed toChainId, + MessageLib.Message[] messages + ); + + event MessageProcessed( + bytes32 indexed messageId, + address indexed sender, + uint256 indexed ticketId + ); + + event MessageBatchProcessed( + bytes32 indexed messageId, + address indexed sender, + uint256 indexed ticketId + ); + + /* ============ Setup ============ */ + function setUp() public { + dispatcher = new MessageDispatcherArbitrum(inbox, toChainId); + + messages.push( + MessageLib.Message({ + to: address(greeter), + data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) + }) + ); + } + + function setExecutor() public { + dispatcher.setExecutor(executor); + } + + /* ============ Constructor ============ */ + function testConstructor() public { + assertEq(address(dispatcher.inbox()), address(inbox)); + } + + function testConstructorInboxFail() public { + vm.expectRevert(bytes("Dispatcher/inbox-not-zero-adrs")); + dispatcher = new MessageDispatcherArbitrum(IInbox(address(0)), toChainId); + } + + function testConstructorToChainIdFail() public { + vm.expectRevert(bytes("Dispatcher/chainId-not-zero")); + dispatcher = new MessageDispatcherArbitrum(inbox, 0); + } + + /* ============ Dispatch ============ */ + function testDispatchMessage() public { + setExecutor(); + + MessageLib.Message memory _message = messages[0]; + bytes32 _expectedMessageId = MessageLib.computeMessageId( + nonce, + address(this), + _message.to, + _message.data + ); + + vm.expectEmit(true, true, true, true, address(dispatcher)); + emit MessageDispatched( + _expectedMessageId, + address(this), + toChainId, + _message.to, + _message.data + ); + + bytes32 _messageId = dispatcher.dispatchMessage(toChainId, _message.to, _message.data); + + assertEq(_messageId, _expectedMessageId); + + bytes32 _txHash = dispatcher.getMessageTxHash( + _messageId, + address(this), + _message.to, + _message.data + ); + assertTrue(dispatcher.dispatched(_txHash)); + } + + function testDispatchMessageBatch() public { + setExecutor(); + + bytes32 _expectedMessageId = MessageLib.computeMessageBatchId(nonce, address(this), messages); + + vm.expectEmit(true, true, true, true, address(dispatcher)); + emit MessageBatchDispatched(_expectedMessageId, address(this), toChainId, messages); + + bytes32 _messageId = dispatcher.dispatchMessageBatch(toChainId, messages); + assertEq(_messageId, _expectedMessageId); + + bytes32 _txHash = dispatcher.getMessageBatchTxHash(_messageId, address(this), messages); + assertTrue(dispatcher.dispatched(_txHash)); + } + + /* ============ Process ============ */ + + function testProcessMessage() public { + setExecutor(); + + MessageLib.Message memory _message = messages[0]; + bytes32 _messageId = dispatcher.dispatchMessage(toChainId, _message.to, _message.data); + + bytes32 _expectedMessageId = MessageLib.computeMessageId( + nonce, + address(this), + _message.to, + _message.data + ); + + assertEq(_messageId, _expectedMessageId); + + uint256 _randomNumber = inbox.generateRandomNumber(); + + vm.expectEmit(true, true, true, true, address(dispatcher)); + emit MessageProcessed(_messageId, address(this), _randomNumber); + + uint256 _ticketId = dispatcher.processMessage( + _messageId, + address(this), + _message.to, + _message.data, + msg.sender, + gasLimit, + maxSubmissionCost, + gasPriceBid + ); + + assertEq(_ticketId, _randomNumber); + } + + function testProcessMessageBatch() public { + setExecutor(); + + bytes32 _messageId = dispatcher.dispatchMessageBatch(toChainId, messages); + bytes32 _expectedMessageId = MessageLib.computeMessageBatchId(nonce, address(this), messages); + + assertEq(_messageId, _expectedMessageId); + + uint256 _randomNumber = inbox.generateRandomNumber(); + + vm.expectEmit(true, true, true, true, address(dispatcher)); + emit MessageBatchProcessed(_messageId, address(this), _randomNumber); + + uint256 _ticketId = dispatcher.processMessageBatch( + _messageId, + messages, + address(this), + msg.sender, + gasLimit, + maxSubmissionCost, + gasPriceBid + ); + + assertEq(_ticketId, _randomNumber); + } + + /* ============ Requires ============ */ + + function testMessageNotDispatched() public { + setExecutor(); + + MessageLib.Message memory _message = messages[0]; + bytes32 _messageId = MessageLib.computeMessageId( + nonce, + address(this), + _message.to, + _message.data + ); + + vm.expectRevert(bytes("Dispatcher/msg-not-dispatched")); + dispatcher.processMessage( + _messageId, + address(this), + _message.to, + _message.data, + msg.sender, + gasLimit, + maxSubmissionCost, + gasPriceBid + ); + } + + function testMessagesNotDispatched() public { + setExecutor(); + + bytes32 _messageId = MessageLib.computeMessageBatchId(nonce, address(this), messages); + + vm.expectRevert(bytes("Dispatcher/msges-not-dispatched")); + dispatcher.processMessageBatch( + _messageId, + messages, + address(this), + msg.sender, + gasLimit, + maxSubmissionCost, + gasPriceBid + ); + } + + function testChainIdNotSupported() public { + setExecutor(); + + MessageLib.Message memory _message = messages[0]; + + vm.expectRevert(bytes("Dispatcher/chainId-not-supported")); + dispatcher.dispatchMessage(10, _message.to, _message.data); + } + + function testExecutorNotSet() public { + bytes32 _messageId = dispatcher.dispatchMessageBatch(toChainId, messages); + + vm.expectRevert(bytes("Dispatcher/executor-not-set")); + dispatcher.processMessageBatch( + _messageId, + messages, + address(this), + msg.sender, + gasLimit, + maxSubmissionCost, + gasPriceBid + ); + } + + function testRefundAddressNotZero() public { + setExecutor(); + + bytes32 _messageId = dispatcher.dispatchMessageBatch(toChainId, messages); + + vm.expectRevert(bytes("Dispatcher/refund-not-zero-adrs")); + dispatcher.processMessageBatch( + _messageId, + messages, + address(this), + address(0), + gasLimit, + maxSubmissionCost, + gasPriceBid + ); + } + + /* ============ Getters ============ */ + function testGetMessageTxHash() public { + MessageLib.Message memory _message = messages[0]; + bytes32 _messageId = MessageLib.computeMessageId(nonce, from, _message.to, _message.data); + + bytes32 _txHash = dispatcher.getMessageTxHash(_messageId, from, _message.to, _message.data); + bytes32 _txHashMatch = keccak256( + abi.encode(address(dispatcher), _messageId, from, _message.to, _message.data) + ); + + assertEq(_txHash, _txHashMatch); + } + + function testGetMessageTxHashFail() public { + MessageLib.Message memory _message = messages[0]; + bytes32 _messageId = MessageLib.computeMessageId(nonce, from, _message.to, _message.data); + + bytes32 _txHash = dispatcher.getMessageTxHash(_messageId, from, _message.to, _message.data); + bytes32 _txHashForged = keccak256( + abi.encode(address(dispatcher), _messageId, attacker, _message.to, _message.data) + ); + + assertTrue(_txHash != _txHashForged); + } + + function testGetMessageBatchTxHash() public { + bytes32 _messageId = MessageLib.computeMessageBatchId(nonce, from, messages); + + bytes32 _txHash = dispatcher.getMessageBatchTxHash(_messageId, from, messages); + bytes32 _txHashMatch = keccak256(abi.encode(address(dispatcher), _messageId, from, messages)); + + assertEq(_txHash, _txHashMatch); + } + + function testGetMessageBatchTxHashFail() public { + bytes32 _messageId = MessageLib.computeMessageBatchId(nonce, from, messages); + + bytes32 _txHash = dispatcher.getMessageBatchTxHash(_messageId, from, messages); + bytes32 _txHashForged = keccak256( + abi.encode(address(dispatcher), _messageId, attacker, messages) + ); + + assertTrue(_txHash != _txHashForged); + } + + function testGetMessageExecutorAddress() public { + setExecutor(); + + address _executorAddress = dispatcher.getMessageExecutorAddress(toChainId); + assertEq(_executorAddress, address(executor)); + } + + function testGetMessageExecutorAddressFail() public { + setExecutor(); + + vm.expectRevert(bytes("Dispatcher/chainId-not-supported")); + dispatcher.getMessageExecutorAddress(10); + } + + /* ============ Setters ============ */ + function testSetExecutor() public { + setExecutor(); + + address _executorAddress = dispatcher.getMessageExecutorAddress(toChainId); + assertEq(_executorAddress, address(executor)); + } + + function testSetExecutorFail() public { + setExecutor(); + + vm.expectRevert(bytes("Dispatcher/executor-already-set")); + dispatcher.setExecutor(MessageExecutorArbitrum(address(0))); + } +} diff --git a/test/unit/ethereum-arbitrum/EthereumToArbitrumExecutor.t.sol b/test/unit/ethereum-arbitrum/EthereumToArbitrumExecutor.t.sol index c192fe5..93b57eb 100644 --- a/test/unit/ethereum-arbitrum/EthereumToArbitrumExecutor.t.sol +++ b/test/unit/ethereum-arbitrum/EthereumToArbitrumExecutor.t.sol @@ -7,138 +7,246 @@ import "forge-std/Test.sol"; import { AddressAliasHelper } from "@arbitrum/nitro-contracts/src/libraries/AddressAliasHelper.sol"; import { IInbox } from "@arbitrum/nitro-contracts/src/bridge/IInbox.sol"; -import { ICrossChainRelayer } from "../../../src/interfaces/ICrossChainRelayer.sol"; -import { CrossChainExecutorArbitrum } from "../../../src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol"; -import { CrossChainRelayerArbitrum } from "../../../src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol"; -import "../../../src/libraries/CallLib.sol"; +import { IMessageDispatcher } from "../../../src/interfaces/IMessageDispatcher.sol"; +import { MessageExecutorArbitrum } from "../../../src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol"; +import { MessageDispatcherArbitrum } from "../../../src/ethereum-arbitrum/EthereumToArbitrumDispatcher.sol"; +import "../../../src/libraries/MessageLib.sol"; import { Greeter } from "../../contracts/Greeter.sol"; -contract CrossChainExecutorArbitrumUnitTest is Test { - CrossChainRelayerArbitrum public relayer = - CrossChainRelayerArbitrum(0x77E395E2bfCE67C718C8Ab812c86328EaE356f07); +contract MessageExecutorArbitrumUnitTest is Test { + MessageDispatcherArbitrum public dispatcher = + MessageDispatcherArbitrum(0x77E395E2bfCE67C718C8Ab812c86328EaE356f07); - address public relayerAlias = AddressAliasHelper.applyL1ToL2Alias(address(relayer)); + address public dispatcherAlias = AddressAliasHelper.applyL1ToL2Alias(address(dispatcher)); - address public sender = 0xa3a935315931A09A4e9B8A865517Cc18923497Ad; + address public from = 0xa3a935315931A09A4e9B8A865517Cc18923497Ad; address public attacker = 0xdBdDa361Db11Adf8A51dab8a511a8ee89128E89A; uint256 public nonce = 1; + uint256 public fromChainId = 1; string public l1Greeting = "Hello from L1"; - CallLib.Call[] public calls; - CrossChainExecutorArbitrum public executor; + MessageLib.Message[] public messages; + MessageExecutorArbitrum public executor; Greeter public greeter; /* ============ Events to test ============ */ + event MessageIdExecuted(uint256 indexed fromChainId, bytes32 indexed messageId); - event ExecutedCalls(ICrossChainRelayer indexed relayer, uint256 indexed nonce); - - event SetGreeting(string greeting, uint256 nonce, address l1Sender, address l2Sender); + event SetGreeting( + string greeting, + bytes32 messageId, + uint256 fromChainId, + address from, + address l2Sender + ); /* ============ Setup ============ */ function setUp() public { - executor = new CrossChainExecutorArbitrum(); + executor = new MessageExecutorArbitrum(); greeter = new Greeter(address(executor), "Hello from L2"); } - function pushCalls(address _target) public { - calls.push( - CallLib.Call({ - target: _target, - data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) - }) + function pushMessages(address _to) public { + messages.push( + MessageLib.Message({ to: _to, data: abi.encodeCall(Greeter.setGreeting, (l1Greeting)) }) ); } - function setRelayer() public { - executor.setRelayer(relayer); + function setDispatcher() public { + executor.setDispatcher(dispatcher); } - /* ============ executeCalls ============ */ + /* ============ ExecuteMessage ============ */ + function testExecuteMessage() public { + setDispatcher(); + pushMessages(address(greeter)); - function testExecuteCalls() public { - setRelayer(); - pushCalls(address(greeter)); + vm.startPrank(dispatcherAlias); - vm.startPrank(relayerAlias); + MessageLib.Message memory _message = messages[0]; + bytes32 _messageId = MessageLib.computeMessageId( + nonce, + address(this), + _message.to, + _message.data + ); vm.expectEmit(true, true, true, true, address(greeter)); - emit SetGreeting(l1Greeting, nonce, sender, address(executor)); + emit SetGreeting(l1Greeting, _messageId, fromChainId, from, address(executor)); vm.expectEmit(true, true, true, true, address(executor)); - emit CallLib.CallSuccess(1, 0, bytes("")); + emit MessageIdExecuted(fromChainId, _messageId); - vm.expectEmit(true, true, true, true, address(executor)); - emit ExecutedCalls(relayer, nonce); + executor.executeMessage(_message.to, _message.data, _messageId, fromChainId, from); + + assertTrue(executor.executed(_messageId)); + } + + function testExecuteMessageIdAlreadyExecuted() public { + setDispatcher(); + pushMessages(address(greeter)); + + MessageLib.Message memory _message = messages[0]; + bytes32 _messageId = MessageLib.computeMessageId( + nonce, + address(this), + _message.to, + _message.data + ); + + vm.startPrank(dispatcherAlias); + executor.executeMessage(_message.to, _message.data, _messageId, fromChainId, from); + + vm.expectRevert( + abi.encodeWithSelector(MessageLib.MessageIdAlreadyExecuted.selector, _messageId) + ); - executor.executeCalls(nonce, sender, calls); + executor.executeMessage(_message.to, _message.data, _messageId, fromChainId, from); + } + + function testExecuteMessageFailure() public { + setDispatcher(); + pushMessages(address(this)); + + vm.startPrank(dispatcherAlias); + + MessageLib.Message memory _message = messages[0]; + bytes32 _messageId = MessageLib.computeMessageId( + nonce, + address(this), + _message.to, + _message.data + ); + + vm.expectRevert( + abi.encodeWithSelector(MessageLib.MessageFailure.selector, _messageId, bytes("")) + ); - assertTrue(executor.executed(nonce)); + executor.executeMessage(_message.to, _message.data, _messageId, fromChainId, from); } - function testExecuteCallsAlreadyExecuted() public { - setRelayer(); - pushCalls(address(greeter)); + function testExecuteMessageUnauthorized() public { + setDispatcher(); + pushMessages(address(greeter)); - vm.startPrank(relayerAlias); - executor.executeCalls(nonce, sender, calls); + MessageLib.Message memory _message = messages[0]; + bytes32 _messageId = MessageLib.computeMessageId( + nonce, + address(this), + _message.to, + _message.data + ); - vm.expectRevert(abi.encodeWithSelector(CallLib.CallsAlreadyExecuted.selector, nonce)); - executor.executeCalls(nonce, sender, calls); + vm.expectRevert(bytes("Executor/sender-unauthorized")); + executor.executeMessage(_message.to, _message.data, _messageId, fromChainId, from); } - function testExecuteCallsFailed() public { - setRelayer(); - pushCalls(address(this)); + function testExecuteMessageToNotZeroAddress() public { + setDispatcher(); + pushMessages(address(0)); + + vm.startPrank(dispatcherAlias); - vm.startPrank(relayerAlias); + MessageLib.Message memory _message = messages[0]; + bytes32 _messageId = MessageLib.computeMessageId( + nonce, + address(this), + _message.to, + _message.data + ); + + vm.expectRevert(bytes("MessageLib/no-contract-at-to")); + executor.executeMessage(_message.to, _message.data, _messageId, fromChainId, from); + } + + /* ============ ExecuteMessageBatch ============ */ + function testExecuteMessageBatch() public { + setDispatcher(); + pushMessages(address(greeter)); + + vm.startPrank(dispatcherAlias); + + bytes32 _messageId = MessageLib.computeMessageBatchId(nonce, msg.sender, messages); + + vm.expectEmit(true, true, true, true, address(greeter)); + emit SetGreeting(l1Greeting, _messageId, fromChainId, from, address(executor)); vm.expectEmit(true, true, true, true, address(executor)); - emit CallLib.CallFailure(1, 0, bytes("")); + emit MessageIdExecuted(fromChainId, _messageId); + + executor.executeMessageBatch(messages, _messageId, fromChainId, from); + + assertTrue(executor.executed(_messageId)); + } + + function testExecuteMessageBatchIdAlreadyExecuted() public { + setDispatcher(); + pushMessages(address(greeter)); + + bytes32 _messageId = MessageLib.computeMessageBatchId(nonce, msg.sender, messages); + + vm.startPrank(dispatcherAlias); + executor.executeMessageBatch(messages, _messageId, fromChainId, from); vm.expectRevert( - abi.encodeWithSelector( - CrossChainExecutorArbitrum.ExecuteCallsFailed.selector, - address(relayer), - nonce - ) + abi.encodeWithSelector(MessageLib.MessageIdAlreadyExecuted.selector, _messageId) ); - executor.executeCalls(nonce, sender, calls); + executor.executeMessageBatch(messages, _messageId, fromChainId, from); } - function testExecuteCallsUnauthorized() public { - setRelayer(); - pushCalls(address(greeter)); + function testExecuteMessageBatchFailure() public { + setDispatcher(); + pushMessages(address(this)); + + vm.startPrank(dispatcherAlias); + + bytes32 _messageId = MessageLib.computeMessageBatchId(nonce, msg.sender, messages); + + vm.expectRevert( + abi.encodeWithSelector(MessageLib.MessageBatchFailure.selector, _messageId, 0, bytes("")) + ); + + executor.executeMessageBatch(messages, _messageId, fromChainId, from); + } + + function testExecuteMessageBatchUnauthorized() public { + setDispatcher(); + pushMessages(address(greeter)); + + bytes32 _messageId = MessageLib.computeMessageBatchId(nonce, msg.sender, messages); vm.expectRevert(bytes("Executor/sender-unauthorized")); - executor.executeCalls(nonce, sender, calls); + executor.executeMessageBatch(messages, _messageId, fromChainId, from); } - function testExecuteCallsTargetNotZeroAddress() public { - setRelayer(); - pushCalls(address(0)); + function testExecuteMessageBatchToNotZeroAddress() public { + setDispatcher(); + pushMessages(address(0)); + + vm.startPrank(dispatcherAlias); - vm.startPrank(relayerAlias); + bytes32 _messageId = MessageLib.computeMessageBatchId(nonce, msg.sender, messages); - vm.expectRevert(bytes("CallLib/no-contract-at-target")); - executor.executeCalls(nonce, sender, calls); + vm.expectRevert(bytes("MessageLib/no-contract-at-to")); + executor.executeMessageBatch(messages, _messageId, fromChainId, from); } /* ============ Setters ============ */ - function testSetRelayer() public { - setRelayer(); - assertEq(address(relayer), address(executor.relayer())); + function testSetDispatcher() public { + setDispatcher(); + assertEq(address(dispatcher), address(executor.dispatcher())); } - function testSetRelayerFail() public { - setRelayer(); + function testSetDispatcherFail() public { + setDispatcher(); - vm.expectRevert(bytes("Executor/relayer-already-set")); - executor.setRelayer(CrossChainRelayerArbitrum(address(0))); + vm.expectRevert(bytes("Executor/dispatcher-already-set")); + executor.setDispatcher(MessageDispatcherArbitrum(address(0))); } } diff --git a/test/unit/ethereum-arbitrum/EthereumToArbitrumRelayer.t.sol b/test/unit/ethereum-arbitrum/EthereumToArbitrumRelayer.t.sol deleted file mode 100644 index 0db4b73..0000000 --- a/test/unit/ethereum-arbitrum/EthereumToArbitrumRelayer.t.sol +++ /dev/null @@ -1,193 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.16; - -import "forge-std/Test.sol"; - -import { IInbox } from "@arbitrum/nitro-contracts/src/bridge/IInbox.sol"; - -import { CrossChainRelayerArbitrum } from "../../../src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol"; -import { CrossChainExecutorArbitrum } from "../../../src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol"; -import { ICrossChainRelayer } from "../../../src/interfaces/ICrossChainRelayer.sol"; -import "../../../src/libraries/CallLib.sol"; - -import { Greeter } from "../../contracts/Greeter.sol"; -import { ArbInbox } from "../../contracts/mock/ArbInbox.sol"; - -contract CrossChainRelayerArbitrumUnitTest is Test { - ArbInbox public inbox = new ArbInbox(); - CrossChainExecutorArbitrum public executor = - CrossChainExecutorArbitrum(0x77E395E2bfCE67C718C8Ab812c86328EaE356f07); - Greeter public greeter = Greeter(0x720003dC4EA5aCDA0204823B98E014f095E667f8); - - address public sender = 0xa3a935315931A09A4e9B8A865517Cc18923497Ad; - address public attacker = 0xdBdDa361Db11Adf8A51dab8a511a8ee89128E89A; - - uint256 public gasLimit = 1000000; - - uint256 public maxSubmissionCost = 1 ether; - uint256 public gasPriceBid = 500; - uint256 public nonce = 1; - - string public l1Greeting = "Hello from L1"; - - CrossChainRelayerArbitrum public relayer; - CallLib.Call[] public calls; - - /* ============ Events to test ============ */ - event RelayedCalls( - uint256 indexed nonce, - address indexed sender, - CallLib.Call[] calls, - uint256 gasLimit - ); - - event ProcessedCalls(uint256 indexed nonce, address indexed sender, uint256 indexed ticketId); - - /* ============ Setup ============ */ - function setUp() public { - relayer = new CrossChainRelayerArbitrum(inbox); - - calls.push( - CallLib.Call({ - target: address(greeter), - data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) - }) - ); - } - - function setExecutor() public { - relayer.setExecutor(executor); - } - - /* ============ Constructor ============ */ - function testConstructor() public { - assertEq(address(relayer.inbox()), address(inbox)); - } - - function testConstructorInboxFail() public { - vm.expectRevert(bytes("Relayer/inbox-not-zero-address")); - relayer = new CrossChainRelayerArbitrum(IInbox(address(0))); - } - - /* ============ relayCalls ============ */ - function testRelayCalls() public { - setExecutor(); - - vm.expectEmit(true, true, true, true, address(relayer)); - emit RelayedCalls(nonce, address(this), calls, gasLimit); - - uint256 _nonce = relayer.relayCalls(calls, gasLimit); - assertEq(_nonce, nonce); - - bytes32 txHash = relayer.getTxHash(nonce, calls, address(this), gasLimit); - assertTrue(relayer.relayed(txHash)); - } - - /* ============ processCalls ============ */ - - function testProcessCalls() public { - setExecutor(); - - uint256 _nonce = relayer.relayCalls(calls, gasLimit); - assertEq(_nonce, nonce); - - vm.expectEmit(true, true, true, true, address(relayer)); - - uint256 _randomNumber = inbox.generateRandomNumber(); - emit ProcessedCalls(_nonce, address(this), _randomNumber); - - uint256 _ticketId = relayer.processCalls( - _nonce, - calls, - address(this), - msg.sender, - gasLimit, - maxSubmissionCost, - gasPriceBid - ); - - assertEq(_ticketId, _randomNumber); - } - - function testCallsNotRelayed() public { - setExecutor(); - - vm.expectRevert(bytes("Relayer/calls-not-relayed")); - relayer.processCalls( - nonce, - calls, - address(this), - msg.sender, - gasLimit, - maxSubmissionCost, - gasPriceBid - ); - } - - function testExecutorNotSet() public { - uint256 _nonce = relayer.relayCalls(calls, gasLimit); - - vm.expectRevert(bytes("Relayer/executor-not-set")); - - relayer.processCalls( - _nonce, - calls, - address(this), - msg.sender, - gasLimit, - maxSubmissionCost, - gasPriceBid - ); - } - - function testRefundAddressNotZero() public { - setExecutor(); - - uint256 _nonce = relayer.relayCalls(calls, gasLimit); - - vm.expectRevert(bytes("Relayer/refund-address-not-zero")); - - relayer.processCalls( - _nonce, - calls, - address(this), - address(0), - gasLimit, - maxSubmissionCost, - gasPriceBid - ); - } - - /* ============ Getters ============ */ - function testGetTxHash() public { - bytes32 txHash = relayer.getTxHash(nonce, calls, sender, gasLimit); - - bytes32 txHashMatch = keccak256(abi.encode(address(relayer), nonce, calls, sender, gasLimit)); - - assertEq(txHash, txHashMatch); - } - - function testGetTxHashFail() public { - bytes32 txHash = relayer.getTxHash(nonce, calls, sender, gasLimit); - - bytes32 txHashForged = keccak256( - abi.encode(address(relayer), nonce, calls, attacker, gasLimit) - ); - - assertTrue(txHash != txHashForged); - } - - /* ============ Setters ============ */ - function testSetExecutor() public { - setExecutor(); - assertEq(address(executor), address(relayer.executor())); - } - - function testSetExecutorFail() public { - setExecutor(); - - vm.expectRevert(bytes("Relayer/executor-already-set")); - relayer.setExecutor(CrossChainExecutorArbitrum(address(0))); - } -} diff --git a/yarn.lock b/yarn.lock index 42f8207..d8bf53c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1001,6 +1001,7 @@ __metadata: kill-port: 2.0.1 lint-staged: 13.0.3 npm-run-all: 4.1.5 + pinst: 3.0.0 postinstall-postinstall: 2.1.0 prettier: 2.7.1 prettier-plugin-solidity: 1.0.0-beta.24 @@ -6371,6 +6372,15 @@ __metadata: languageName: node linkType: hard +"pinst@npm:3.0.0": + version: 3.0.0 + resolution: "pinst@npm:3.0.0" + bin: + pinst: bin.js + checksum: 4ae48a6a60f79c37071233af51b4d91bfc85cfa3c12b66ccda60cdb642b4d14a4ab0cb3587afc55b1f6192cea1772a5e4822026a0d0d3528296edef00cc2d61f + languageName: node + linkType: hard + "postinstall-postinstall@npm:2.1.0": version: 2.1.0 resolution: "postinstall-postinstall@npm:2.1.0"