diff --git a/Constants.ts b/Constants.ts index c4f7ad8..50f0b9d 100644 --- a/Constants.ts +++ b/Constants.ts @@ -11,13 +11,6 @@ export const USDC_TOKEN_DECIMALS = 6; export const DELAYED_INBOX = '0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f'; -/** - * Retrieved by calling the `getGasAccountingParams` function on the `ArbGasInfo` contract - * Address: https://arbiscan.io/address/0x000000000000000000000000000000000000006C - * Contract: https://github.com/OffchainLabs/nitro/blob/master/contracts/src/precompiles/ArbGasInfo.sol#L80 - */ -export const ARBITRUM_MAX_TX_GAS_LIMIT = 32000000; - export const MAINNET_CHAIN_ID = 1; export const ARBITRUM_CHAIN_ID = 42161; export const OPTIMISM_CHAIN_ID = 10; diff --git a/README.md b/README.md index 707f04f..c44d2c3 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ 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. -*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.* +_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** @@ -205,9 +205,9 @@ function _nonce() internal pure returns (uint256 _callDataNonce); | Network | Contract | Address | | --------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| Ethereum Goerli | [EthereumToArbitrumRelayer.sol](./src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol) | [0x7460fDb4db23C7287c67122A31661b753081e80a](https://goerli.etherscan.io/address/0x7460fDb4db23C7287c67122A31661b753081e80a) | -| Arbitrum Goerli | [EthereumToArbitrumExecutor](./src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol) | [0x18771cC0bbcA24d3B28C040669DCc7b5Ffba30FB](https://goerli.arbiscan.io/address/0x18771cC0bbcA24d3B28C040669DCc7b5Ffba30FB) | -| Arbitrum Goerli | [Greeter](./test/contracts/Greeter.sol) | [0xa1d913940B8dbb7bDB1F68D8E9C54484D575FefC](https://goerli.arbiscan.io/address/0xa1d913940B8dbb7bDB1F68D8E9C54484D575FefC) | +| 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) | ### Ethereum Goerli -> Optimism Goerli @@ -342,9 +342,9 @@ It takes about 15 minutes for the message to be bridged to Arbitrum Goerli. | Network | Call | Transaction hash | | --------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Ethereum Goerli | relayCalls | [0x102ae324d996fdeadf666d1f6f00db00c73be632c8b115cf6f2ab901fd1ca7f7](https://goerli.etherscan.io/tx/0x102ae324d996fdeadf666d1f6f00db00c73be632c8b115cf6f2ab901fd1ca7f7) | -| Ethereum Goerli | processCalls | [0x34876c7553b4618170e1c95aaa30daf79e4ddb6436cb7e317215ea0e3593dbcc](https://goerli.etherscan.io/tx/0x34876c7553b4618170e1c95aaa30daf79e4ddb6436cb7e317215ea0e3593dbcc) | -| Arbitrum Goerli | executeCalls | [0xc74b9570949b941ec1f1a020c1d988614448947e6f2de691e2031304bb76bd0c](https://goerli.arbiscan.io/tx/0xc74b9570949b941ec1f1a020c1d988614448947e6f2de691e2031304bb76bd0c) | +| 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) | #### Ethereum Goerli to Optimism Goerli diff --git a/lib/contracts b/lib/contracts index dc41712..eb370b6 160000 --- a/lib/contracts +++ b/lib/contracts @@ -1 +1 @@ -Subproject commit dc41712b802a65a0cc2d00ec0833da741dd5ba7c +Subproject commit eb370b655923162c442077246855affe56809263 diff --git a/lib/forge-std b/lib/forge-std index 17656a2..cd7d533 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 17656a2fa5453f495d8c1302a0cedded912457eb +Subproject commit cd7d533f9a0ee0ec02ad81e0a8f262bc4203c653 diff --git a/lib/nitro b/lib/nitro index 1f32bec..48fda6c 160000 --- a/lib/nitro +++ b/lib/nitro @@ -1 +1 @@ -Subproject commit 1f32bec6b9b228bb2fab4bfa02867716f65d0c5c +Subproject commit 48fda6c75955c73a95b8a6ac394768d8dacefa91 diff --git a/lib/optimism b/lib/optimism index f7dbad0..764a927 160000 --- a/lib/optimism +++ b/lib/optimism @@ -1 +1 @@ -Subproject commit f7dbad0287fd97488e62a3bee53fb3353a6c56b9 +Subproject commit 764a9271eb68f70bf4c791662261ad0ba4e8e73f diff --git a/script/bridge/BridgeToArbitrumGoerli.ts b/script/bridge/BridgeToArbitrumGoerli.ts index 96ee910..8120e84 100644 --- a/script/bridge/BridgeToArbitrumGoerli.ts +++ b/script/bridge/BridgeToArbitrumGoerli.ts @@ -1,13 +1,14 @@ import { L1TransactionReceipt, L1ToL2MessageGasEstimator } from '@arbitrum/sdk/'; -import { hexDataLength } from '@ethersproject/bytes'; +import { getBaseFee } from '@arbitrum/sdk/dist/lib/utils/lib'; import { BigNumber, providers } from 'ethers'; 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, ICrossChainRelayer } from '../../types'; -import CrossChainRelayerArbitrumArtifact from '../../out/CrossChainRelayerArbitrum.sol/CrossChainRelayerArbitrum.json'; +import { CrossChainRelayerArbitrum } from '../../types'; +import { CallLib } from '../../types/ICrossChainRelayer'; +import CrossChainRelayerArbitrumArtifact from '../../out/EthereumToArbitrumRelayer.sol/CrossChainRelayerArbitrum.json'; const main = async () => { action('Relay calls from Ethereum to Arbitrum...'); @@ -16,7 +17,7 @@ const main = async () => { ethers: { getContractAt, provider: l1Provider, - utils: { defaultAbiCoder, Interface }, + utils: { Interface }, }, getNamedAccounts, } = hre; @@ -52,33 +53,44 @@ const main = async () => { [greeting], ); - const calls: ICrossChainRelayer.CallStruct[] = [ + const calls: CallLib.CallStruct[] = [ { target: greeterAddress, data: callData, }, ]; + const nextNonce = (await crossChainRelayerArbitrum.nonce()).add(1); + const executeCallsData = new Interface([ 'function executeCalls(uint256,address,(address,bytes)[])', - ]).encodeFunctionData('executeCalls', [ - BigNumber.from(1), - deployer, - [[greeterAddress, callData]], - ]); + ]).encodeFunctionData('executeCalls', [nextNonce, deployer, [[greeterAddress, callData]]]); const l1ToL2MessageGasEstimate = new L1ToL2MessageGasEstimator(l2Provider); + const baseFee = await getBaseFee(l1Provider); + + /** + * 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 + */ + const { deposit, gasLimit, maxSubmissionCost } = await l1ToL2MessageGasEstimate.estimateAll( + { + from: crossChainRelayerArbitrumAddress, + to: crossChainExecutorAddress, + l2CallValue: BigNumber.from(0), + excessFeeRefundAddress: deployer, + callValueRefundAddress: deployer, + data: executeCallsData, + }, + baseFee, + l1Provider, + ); - const maxGas = await l1ToL2MessageGasEstimate.estimateRetryableTicketGasLimit({ - from: crossChainRelayerArbitrumAddress, - to: crossChainExecutorAddress, - l2CallValue: BigNumber.from(0), - excessFeeRefundAddress: deployer, - callValueRefundAddress: deployer, - data: executeCallsData, - }); + info(`Current retryable base submission price is: ${maxSubmissionCost.toString()}`); - const relayCallsTransaction = await crossChainRelayerArbitrum.relayCalls(calls, maxGas); + const relayCallsTransaction = await crossChainRelayerArbitrum.relayCalls(calls, gasLimit); const relayCallsTransactionReceipt = await relayCallsTransaction.wait(); const relayedCallsEventInterface = new Interface([ @@ -96,33 +108,22 @@ const main = async () => { action('Process calls from Ethereum to Arbitrum...'); - const greetingBytes = defaultAbiCoder.encode(['string'], [greeting]); - const greetingBytesLength = hexDataLength(greetingBytes) + 4; // 4 bytes func identifier - - const submissionPriceWei = await l1ToL2MessageGasEstimate.estimateSubmissionFee( - l1Provider, - await l1Provider.getGasPrice(), - greetingBytesLength, - ); - - info(`Current retryable base submission price: ${submissionPriceWei.toString()}`); - - const maxSubmissionCost = submissionPriceWei.mul(5); const gasPriceBid = await l2Provider.getGasPrice(); info(`L2 gas price: ${gasPriceBid.toString()}`); - const callValue = maxSubmissionCost.add(gasPriceBid.mul(maxGas)); + info(`Sending greeting to L2 with ${deposit.toString()} callValue for L2 fees:`); const processCallsTransaction = await crossChainRelayerArbitrum.processCalls( relayCallsNonce, calls, deployer, - maxGas, + deployer, + gasLimit, maxSubmissionCost, gasPriceBid, { - value: callValue, + value: deposit, }, ); diff --git a/script/deploy/DeployToArbitrumGoerli.s.sol b/script/deploy/DeployToArbitrumGoerli.s.sol index e5293ac..e9f01e2 100644 --- a/script/deploy/DeployToArbitrumGoerli.s.sol +++ b/script/deploy/DeployToArbitrumGoerli.s.sol @@ -16,7 +16,7 @@ contract DeployCrossChainRelayerToGoerli is Script { function run() public { vm.broadcast(); - new CrossChainRelayerArbitrum(IInbox(delayedInbox), 32000000); + new CrossChainRelayerArbitrum(IInbox(delayedInbox)); vm.stopBroadcast(); } diff --git a/script/deploy/DeployToMumbai.s.sol b/script/deploy/DeployToMumbai.s.sol index e1d1927..d360928 100644 --- a/script/deploy/DeployToMumbai.s.sol +++ b/script/deploy/DeployToMumbai.s.sol @@ -17,7 +17,7 @@ contract DeployCrossChainRelayerToGoerli is Script { function run() public { vm.broadcast(); - new CrossChainRelayerPolygon(checkpointManager, fxRoot, 30000000); + new CrossChainRelayerPolygon(checkpointManager, fxRoot); vm.stopBroadcast(); } diff --git a/script/deploy/DeployToOptimismGoerli.s.sol b/script/deploy/DeployToOptimismGoerli.s.sol index 50589f4..ed2c82a 100644 --- a/script/deploy/DeployToOptimismGoerli.s.sol +++ b/script/deploy/DeployToOptimismGoerli.s.sol @@ -16,7 +16,7 @@ contract DeployCrossChainRelayerToGoerli is Script { function run() public { vm.broadcast(); - new CrossChainRelayerOptimism(ICrossDomainMessenger(proxyOVML1CrossDomainMessenger), 1920000); + new CrossChainRelayerOptimism(ICrossDomainMessenger(proxyOVML1CrossDomainMessenger)); vm.stopBroadcast(); } diff --git a/script/fork/bridge/BridgeToArbitrum.ts b/script/fork/bridge/BridgeToArbitrum.ts index 318feeb..874c1de 100644 --- a/script/fork/bridge/BridgeToArbitrum.ts +++ b/script/fork/bridge/BridgeToArbitrum.ts @@ -1,7 +1,7 @@ import { task } from 'hardhat/config'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { L1TransactionReceipt, L1ToL2MessageGasEstimator } from '@arbitrum/sdk/'; -import { hexDataLength } from '@ethersproject/bytes'; +import { getBaseFee } from '@arbitrum/sdk/dist/lib/utils/lib'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { BigNumber, providers } from 'ethers'; import kill from 'kill-port'; @@ -68,58 +68,70 @@ export const relayCalls = task( }, ]; + const nextNonce = (await crossChainRelayerArbitrum.nonce()).add(1); + const executeCallsData = new Interface([ 'function executeCalls(uint256,address,(address,bytes)[])', - ]).encodeFunctionData('executeCalls', [ - BigNumber.from(1), - deployer, - [[greeterAddress, callData]], - ]); + ]).encodeFunctionData('executeCalls', [nextNonce, deployer, [[greeterAddress, callData]]]); const l1ToL2MessageGasEstimate = new L1ToL2MessageGasEstimator(l2Provider); + const baseFee = await getBaseFee(l1Provider); + + /** + * 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 + */ + const { deposit, gasLimit, maxSubmissionCost } = await l1ToL2MessageGasEstimate.estimateAll( + { + from: crossChainRelayerArbitrum.address, + to: crossChainExecutorAddress, + l2CallValue: BigNumber.from(0), + excessFeeRefundAddress: deployer, + callValueRefundAddress: deployer, + data: executeCallsData, + }, + baseFee, + l1Provider, + ); - const maxGas = await l1ToL2MessageGasEstimate.estimateRetryableTicketGasLimit({ - from: crossChainRelayerArbitrum.address, - to: crossChainExecutorAddress, - l2CallValue: BigNumber.from(0), - excessFeeRefundAddress: deployer, - callValueRefundAddress: deployer, - data: executeCallsData, - }); + info(`Current retryable base submission price is: ${maxSubmissionCost.toString()}`); - await crossChainRelayerArbitrum.relayCalls(calls, maxGas); + const relayCallsTransaction = await crossChainRelayerArbitrum.relayCalls(calls, gasLimit); + const relayCallsTransactionReceipt = await relayCallsTransaction.wait(); - success('Successfully relayed calls from Ethereum to Arbitrum!'); + const relayedCallsEventInterface = new Interface([ + 'event RelayedCalls(uint256 indexed nonce,address indexed sender, (address target,bytes data)[], uint256 gasLimit)', + ]); - action('Process calls from Ethereum to Arbitrum...'); + const relayedCallsEventLogs = relayedCallsEventInterface.parseLog( + relayCallsTransactionReceipt.logs[0], + ); - const greetingBytes = defaultAbiCoder.encode(['string'], [greeting]); - const greetingBytesLength = hexDataLength(greetingBytes) + 4; // 4 bytes func identifier + const [relayCallsNonce] = relayedCallsEventLogs.args; - const submissionPriceWei = await l1ToL2MessageGasEstimate.estimateSubmissionFee( - l1Provider, - await l1Provider.getGasPrice(), - greetingBytesLength, - ); + success('Successfully relayed calls from Ethereum to Arbitrum!'); + info(`Nonce: ${relayCallsNonce}`); - info(`Current retryable base submission price: ${submissionPriceWei.toString()}`); + action('Process calls from Ethereum to Arbitrum...'); - const maxSubmissionCost = submissionPriceWei.mul(5); const gasPriceBid = await l2Provider.getGasPrice(); info(`L2 gas price: ${gasPriceBid.toString()}`); - const callValue = maxSubmissionCost.add(gasPriceBid.mul(maxGas)); + info(`Sending greeting to L2 with ${deposit.toString()} callValue for L2 fees:`); const processCallsTransaction = await crossChainRelayerArbitrum.processCalls( - BigNumber.from(1), + relayCallsNonce, calls, deployer, - maxGas, + deployer, + gasLimit, maxSubmissionCost, gasPriceBid, { - value: callValue, + value: deposit, }, ); @@ -133,7 +145,7 @@ export const relayCalls = task( processCallsTransactionReceipt.logs[2], ); - const [sender, nonce, ticketId] = processedCallsEventLogs.args; + const [nonce, sender, ticketId] = processedCallsEventLogs.args; const receipt = await l1Provider.getTransactionReceipt(processCallsTransaction.hash); const l1Receipt = new L1TransactionReceipt(receipt); diff --git a/script/fork/deploy/DeployToArbitrum.ts b/script/fork/deploy/DeployToArbitrum.ts index 1d34a66..db0f789 100644 --- a/script/fork/deploy/DeployToArbitrum.ts +++ b/script/fork/deploy/DeployToArbitrum.ts @@ -1,12 +1,7 @@ import { task } from 'hardhat/config'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { - ARBITRUM_CHAIN_ID, - MAINNET_CHAIN_ID, - DELAYED_INBOX, - ARBITRUM_MAX_TX_GAS_LIMIT, -} from '../../../Constants'; +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'; @@ -28,7 +23,7 @@ export const deployRelayer = task( const { address } = await deploy('CrossChainRelayerArbitrum', { from: deployer, - args: [DELAYED_INBOX, ARBITRUM_MAX_TX_GAS_LIMIT], + args: [DELAYED_INBOX], }); success(`Arbitrum Relayer deployed on Ethereum at address: ${address}`); diff --git a/src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol b/src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol index bfee411..eb4b89f 100644 --- a/src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol +++ b/src/ethereum-arbitrum/EthereumToArbitrumExecutor.sol @@ -13,6 +13,15 @@ import "../libraries/CallLib.sol"; * These calls are sent by the `CrossChainRelayerArbitrum` 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); + /* ============ Variables ============ */ /// @notice Address of the relayer contract on the Ethereum chain. @@ -39,7 +48,11 @@ contract CrossChainExecutorArbitrum is ICrossChainExecutor { bool _executedNonce = executed[_nonce]; executed[_nonce] = true; - CallLib.executeCalls(_nonce, _sender, _calls, _executedNonce); + bool _callsExecuted = CallLib.executeCalls(_nonce, _sender, _calls, _executedNonce); + + if (!_callsExecuted) { + revert ExecuteCallsFailed(_relayer, _nonce); + } emit ExecutedCalls(_relayer, _nonce); } diff --git a/src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol b/src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol index f88674d..5326af6 100644 --- a/src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol +++ b/src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol @@ -33,11 +33,8 @@ contract CrossChainRelayerArbitrum is ICrossChainRelayer { /// @notice Address of the executor contract on the Arbitrum chain. ICrossChainExecutor public executor; - /// @notice Gas limit provided for free on Arbitrum. - uint256 public immutable maxGasLimit; - /// @notice Nonce to uniquely idenfity each batch of calls. - uint256 internal nonce; + uint256 public nonce; /** * @notice Hash of transactions that were relayed in `relayCalls`. @@ -51,14 +48,10 @@ contract CrossChainRelayerArbitrum is ICrossChainRelayer { /** * @notice CrossChainRelayer constructor. * @param _inbox Address of the Arbitrum inbox on Ethereum - * @param _maxGasLimit Gas limit provided for free on Arbitrum */ - constructor(IInbox _inbox, uint256 _maxGasLimit) { + constructor(IInbox _inbox) { require(address(_inbox) != address(0), "Relayer/inbox-not-zero-address"); - require(_maxGasLimit > 0, "Relayer/max-gas-limit-gt-zero"); - inbox = _inbox; - maxGasLimit = _maxGasLimit; } /* ============ External Functions ============ */ @@ -66,17 +59,12 @@ contract CrossChainRelayerArbitrum is ICrossChainRelayer { /// @inheritdoc ICrossChainRelayer function relayCalls(CallLib.Call[] calldata _calls, uint256 _gasLimit) external - payable returns (uint256) { - uint256 _maxGasLimit = maxGasLimit; - - if (_gasLimit > _maxGasLimit) { - revert GasLimitTooHigh(_gasLimit, _maxGasLimit); + unchecked { + nonce++; } - nonce++; - uint256 _nonce = nonce; relayed[_getTxHash(_nonce, _calls, msg.sender, _gasLimit)] = true; @@ -89,10 +77,12 @@ contract CrossChainRelayerArbitrum is ICrossChainRelayer { /** * @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 @@ -102,25 +92,31 @@ contract CrossChainRelayerArbitrum is ICrossChainRelayer { 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"); - bytes memory _data = abi.encodeWithSignature( - "executeCalls(uint256,address,(address,bytes)[])", + 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 }( - address(executor), + _executorAddress, 0, _maxSubmissionCost, - msg.sender, - msg.sender, + _refundAddress, + _sender, _gasLimit, _gasPriceBid, _data diff --git a/src/ethereum-optimism/EthereumToOptimismExecutor.sol b/src/ethereum-optimism/EthereumToOptimismExecutor.sol index 1aba9c1..9a11cc3 100644 --- a/src/ethereum-optimism/EthereumToOptimismExecutor.sol +++ b/src/ethereum-optimism/EthereumToOptimismExecutor.sol @@ -13,6 +13,15 @@ import "../libraries/CallLib.sol"; * These calls are sent by the `CrossChainRelayerOptimism` 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); + /* ============ Variables ============ */ /// @notice Address of the Optimism cross domain messenger on the Optimism chain. @@ -53,7 +62,11 @@ contract CrossChainExecutorOptimism is ICrossChainExecutor { bool _executedNonce = executed[_nonce]; executed[_nonce] = true; - CallLib.executeCalls(_nonce, _sender, _calls, _executedNonce); + bool _callsExecuted = CallLib.executeCalls(_nonce, _sender, _calls, _executedNonce); + + if (!_callsExecuted) { + revert ExecuteCallsFailed(_relayer, _nonce); + } emit ExecutedCalls(_relayer, _nonce); } diff --git a/src/ethereum-optimism/EthereumToOptimismRelayer.sol b/src/ethereum-optimism/EthereumToOptimismRelayer.sol index ac70501..2ea326c 100644 --- a/src/ethereum-optimism/EthereumToOptimismRelayer.sol +++ b/src/ethereum-optimism/EthereumToOptimismRelayer.sol @@ -22,9 +22,6 @@ contract CrossChainRelayerOptimism is ICrossChainRelayer { /// @notice Address of the executor contract on the Optimism chain. ICrossChainExecutor public executor; - /// @notice Gas limit provided for free on Optimism. - uint256 public immutable maxGasLimit; - /// @notice Nonce to uniquely idenfity each batch of calls. uint256 internal nonce; @@ -33,14 +30,10 @@ contract CrossChainRelayerOptimism is ICrossChainRelayer { /** * @notice CrossChainRelayerOptimism constructor. * @param _crossDomainMessenger Address of the Optimism cross domain messenger - * @param _maxGasLimit Gas limit provided for free on Optimism */ - constructor(ICrossDomainMessenger _crossDomainMessenger, uint256 _maxGasLimit) { + constructor(ICrossDomainMessenger _crossDomainMessenger) { require(address(_crossDomainMessenger) != address(0), "Relayer/CDM-not-zero-address"); - require(_maxGasLimit > 0, "Relayer/max-gas-limit-gt-zero"); - crossDomainMessenger = _crossDomainMessenger; - maxGasLimit = _maxGasLimit; } /* ============ External Functions ============ */ @@ -48,27 +41,20 @@ contract CrossChainRelayerOptimism is ICrossChainRelayer { /// @inheritdoc ICrossChainRelayer function relayCalls(CallLib.Call[] calldata _calls, uint256 _gasLimit) external - payable returns (uint256) { - uint256 _maxGasLimit = maxGasLimit; + address _executorAddress = address(executor); + require(_executorAddress != address(0), "Relayer/executor-not-set"); - if (_gasLimit > _maxGasLimit) { - revert GasLimitTooHigh(_gasLimit, _maxGasLimit); + unchecked { + nonce++; } - nonce++; - uint256 _nonce = nonce; crossDomainMessenger.sendMessage( - address(executor), - abi.encodeWithSignature( - "executeCalls(uint256,address,(address,bytes)[])", - _nonce, - msg.sender, - _calls - ), + _executorAddress, + abi.encodeWithSelector(ICrossChainExecutor.executeCalls.selector, _nonce, msg.sender, _calls), uint32(_gasLimit) ); diff --git a/src/ethereum-polygon/EthereumToPolygonExecutor.sol b/src/ethereum-polygon/EthereumToPolygonExecutor.sol index 29bc54f..d651327 100644 --- a/src/ethereum-polygon/EthereumToPolygonExecutor.sol +++ b/src/ethereum-polygon/EthereumToPolygonExecutor.sol @@ -12,6 +12,15 @@ import "../libraries/CallLib.sol"; * These calls are sent by the `CrossChainRelayerPolygon` 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); + /* ============ Events ============ */ /** @@ -54,7 +63,11 @@ contract CrossChainExecutorPolygon is FxBaseChildTunnel { bool _executedNonce = executed[_nonce]; executed[_nonce] = true; - CallLib.executeCalls(_nonce, _callsSender, _calls, _executedNonce); + bool _callsExecuted = CallLib.executeCalls(_nonce, _callsSender, _calls, _executedNonce); + + if (!_callsExecuted) { + revert ExecuteCallsFailed(_sender, _nonce); + } emit ExecutedCalls(_sender, _nonce); } diff --git a/src/ethereum-polygon/EthereumToPolygonRelayer.sol b/src/ethereum-polygon/EthereumToPolygonRelayer.sol index 2867314..560e193 100644 --- a/src/ethereum-polygon/EthereumToPolygonRelayer.sol +++ b/src/ethereum-polygon/EthereumToPolygonRelayer.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.16; import { FxBaseRootTunnel } from "@maticnetwork/fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; -import { ICrossChainExecutor } from "../interfaces/ICrossChainExecutor.sol"; import { ICrossChainRelayer } from "../interfaces/ICrossChainRelayer.sol"; import "../libraries/CallLib.sol"; @@ -16,9 +15,6 @@ import "../libraries/CallLib.sol"; contract CrossChainRelayerPolygon is ICrossChainRelayer, FxBaseRootTunnel { /* ============ Variables ============ */ - /// @notice Gas limit provided for free on Polygon. - uint256 public immutable maxGasLimit; - /// @notice Nonce to uniquely idenfity each batch of calls. uint256 internal nonce; @@ -28,33 +24,24 @@ contract CrossChainRelayerPolygon is ICrossChainRelayer, FxBaseRootTunnel { * @notice CrossChainRelayerPolygon constructor. * @param _checkpointManager Address of the root chain manager contract on Ethereum * @param _fxRoot Address of the state sender contract on Ethereum - * @param _maxGasLimit Gas limit provided for free on Polygon */ - constructor( - address _checkpointManager, - address _fxRoot, - uint256 _maxGasLimit - ) FxBaseRootTunnel(_checkpointManager, _fxRoot) { - require(_maxGasLimit > 0, "Relayer/max-gas-limit-gt-zero"); - maxGasLimit = _maxGasLimit; - } + constructor(address _checkpointManager, address _fxRoot) + FxBaseRootTunnel(_checkpointManager, _fxRoot) + {} /* ============ External Functions ============ */ /// @inheritdoc ICrossChainRelayer function relayCalls(CallLib.Call[] calldata _calls, uint256 _gasLimit) external - payable returns (uint256) { - uint256 _maxGasLimit = maxGasLimit; + require(address(fxChildTunnel) != address(0), "Relayer/fxChildTunnel-not-set"); - if (_gasLimit > _maxGasLimit) { - revert GasLimitTooHigh(_gasLimit, _maxGasLimit); + unchecked { + nonce++; } - nonce++; - uint256 _nonce = nonce; _sendMessageToChild(abi.encode(_nonce, msg.sender, _calls)); diff --git a/src/interfaces/ICrossChainExecutor.sol b/src/interfaces/ICrossChainExecutor.sol index 227f5cf..08cd1e9 100644 --- a/src/interfaces/ICrossChainExecutor.sol +++ b/src/interfaces/ICrossChainExecutor.sol @@ -21,6 +21,7 @@ interface ICrossChainExecutor { /** * @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 diff --git a/src/interfaces/ICrossChainRelayer.sol b/src/interfaces/ICrossChainRelayer.sol index a29769b..bb5056a 100644 --- a/src/interfaces/ICrossChainRelayer.sol +++ b/src/interfaces/ICrossChainRelayer.sol @@ -7,6 +7,7 @@ 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 { /** @@ -35,13 +36,9 @@ interface ICrossChainRelayer { * @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 */ - function relayCalls(CallLib.Call[] calldata calls, uint256 gasLimit) - external - payable - returns (uint256); + function relayCalls(CallLib.Call[] calldata calls, uint256 gasLimit) external returns (uint256); } diff --git a/src/interfaces/ICrossChainRelayerPayable.sol b/src/interfaces/ICrossChainRelayerPayable.sol new file mode 100644 index 0000000..83d7a96 --- /dev/null +++ b/src/interfaces/ICrossChainRelayerPayable.sol @@ -0,0 +1,47 @@ +// 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/libraries/CallLib.sol b/src/libraries/CallLib.sol index 18d8cdc..a364106 100644 --- a/src/libraries/CallLib.sol +++ b/src/libraries/CallLib.sol @@ -19,14 +19,25 @@ library CallLib { bytes data; } - /* ============ Custom Errors ============ */ + /* ============ 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 Custom error emitted if a call to a target contract fails. - * @param callIndex Index of the failed call - * @param errorData Error data returned by the failed call + * @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 */ - error CallFailure(uint256 callIndex, bytes errorData); + event CallSuccess(uint256 nonce, uint256 callIndex, bytes successData); + + /* ============ Custom Errors ============ */ /** * @notice Emitted when a batch of calls has already been executed. @@ -39,35 +50,45 @@ library CallLib { /** * @notice Execute calls from the origin chain. * @dev Will revert if `_calls` have already been executed. - * @dev Will 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 * @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 { + ) internal returns (bool) { if (_executedNonce) { revert CallsAlreadyExecuted(_nonce); } uint256 _callsLength = _calls.length; - for (uint256 _callIndex; _callIndex < _callsLength; _callIndex++) { + 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) { - revert CallFailure(_callIndex, _returnData); + emit CallFailure(_nonce, _callIndex, _returnData); + return false; + } + + emit CallSuccess(_nonce, _callIndex, _returnData); + + unchecked { + _callIndex++; } } + + return true; } } diff --git a/test/fork/EthereumToOptimismFork.t.sol b/test/fork/EthereumToOptimismFork.t.sol index be84235..dd7b19a 100644 --- a/test/fork/EthereumToOptimismFork.t.sol +++ b/test/fork/EthereumToOptimismFork.t.sol @@ -31,7 +31,6 @@ contract EthereumToOptimismForkTest is Test { string public l1Greeting = "Hello from L1"; string public l2Greeting = "Hello from L2"; - uint256 public maxGasLimit = 1920000; uint256 public nonce = 1; /* ============ Events to test ============ */ @@ -57,10 +56,7 @@ contract EthereumToOptimismForkTest is Test { function deployRelayer() public { vm.selectFork(mainnetFork); - relayer = new CrossChainRelayerOptimism( - ICrossDomainMessenger(proxyOVML1CrossDomainMessenger), - maxGasLimit - ); + relayer = new CrossChainRelayerOptimism(ICrossDomainMessenger(proxyOVML1CrossDomainMessenger)); vm.makePersistent(address(relayer)); } @@ -152,6 +148,23 @@ contract EthereumToOptimismForkTest is Test { assertEq(_nonce, nonce); } + function testExecutorNotSet() public { + deployAll(); + + vm.selectFork(mainnetFork); + + CallLib.Call[] memory _calls = new CallLib.Call[](1); + + _calls[0] = CallLib.Call({ + target: address(greeter), + data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) + }); + + vm.expectRevert(bytes("Relayer/executor-not-set")); + + relayer.relayCalls(_calls, 200000); + } + /* ============ executeCalls ============ */ function testExecuteCalls() public { @@ -194,26 +207,6 @@ contract EthereumToOptimismForkTest is Test { assertEq(greeter.greet(), l1Greeting); } - function testGasLimitTooHigh() public { - deployAll(); - setAll(); - - vm.selectFork(mainnetFork); - - CallLib.Call[] memory _calls = new CallLib.Call[](1); - - _calls[0] = CallLib.Call({ - target: address(greeter), - data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) - }); - - vm.expectRevert( - abi.encodeWithSelector(ICrossChainRelayer.GasLimitTooHigh.selector, 2000000, maxGasLimit) - ); - - relayer.relayCalls(_calls, 2000000); - } - function testIsAuthorized() public { deployAll(); setAll(); diff --git a/test/fork/EthereumToPolygonFork.t.sol b/test/fork/EthereumToPolygonFork.t.sol index 01913e6..94fe803 100644 --- a/test/fork/EthereumToPolygonFork.t.sol +++ b/test/fork/EthereumToPolygonFork.t.sol @@ -28,7 +28,6 @@ contract EthereumToPolygonForkTest is Test { string public l1Greeting = "Hello from L1"; string public l2Greeting = "Hello from L2"; - uint256 public maxGasLimit = 1920000; uint256 public nonce = 1; /* ============ Events to test ============ */ @@ -58,7 +57,7 @@ contract EthereumToPolygonForkTest is Test { function deployRelayer() public { vm.selectFork(mainnetFork); - relayer = new CrossChainRelayerPolygon(checkpointManager, fxRoot, maxGasLimit); + relayer = new CrossChainRelayerPolygon(checkpointManager, fxRoot); vm.makePersistent(address(relayer)); } @@ -109,7 +108,6 @@ contract EthereumToPolygonForkTest is Test { assertEq(address(relayer.checkpointManager()), checkpointManager); assertEq(address(relayer.fxRoot()), fxRoot); - assertEq(relayer.maxGasLimit(), maxGasLimit); assertEq(relayer.fxChildTunnel(), address(executor)); } @@ -132,6 +130,7 @@ contract EthereumToPolygonForkTest is Test { function testRelayCalls() public { deployAll(); + setAll(); vm.selectFork(mainnetFork); @@ -151,6 +150,23 @@ contract EthereumToPolygonForkTest is Test { assertEq(_nonce, nonce); } + function testFxChildTunnelNotSet() public { + deployAll(); + + vm.selectFork(mainnetFork); + + CallLib.Call[] memory _calls = new CallLib.Call[](1); + + _calls[0] = CallLib.Call({ + target: address(greeter), + data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) + }); + + vm.expectRevert(bytes("Relayer/fxChildTunnel-not-set")); + + relayer.relayCalls(_calls, 200000); + } + function testExecuteCalls() public { deployAll(); setAll(); @@ -179,23 +195,25 @@ contract EthereumToPolygonForkTest is Test { assertEq(greeter.greet(), l1Greeting); } - function testGasLimitTooHigh() public { + function testExecuteCallsTargetNotZeroAddress() public { deployAll(); + setAll(); - vm.selectFork(mainnetFork); + vm.selectFork(polygonFork); + + assertEq(greeter.greet(), l2Greeting); CallLib.Call[] memory _calls = new CallLib.Call[](1); _calls[0] = CallLib.Call({ - target: address(greeter), + target: address(0), data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) }); - vm.expectRevert( - abi.encodeWithSelector(ICrossChainRelayer.GasLimitTooHigh.selector, 2000000, maxGasLimit) - ); + vm.startPrank(fxChild); - relayer.relayCalls(_calls, 2000000); + vm.expectRevert(bytes("CallLib/no-contract-at-target")); + executor.processMessageFromRoot(1, address(relayer), abi.encode(nonce, address(this), _calls)); } function testCallFailure() public { @@ -213,7 +231,13 @@ contract EthereumToPolygonForkTest is Test { vm.startPrank(fxChild); - vm.expectRevert(abi.encodeWithSelector(CallLib.CallFailure.selector, 0, bytes(""))); + vm.expectRevert( + abi.encodeWithSelector( + CrossChainExecutorPolygon.ExecuteCallsFailed.selector, + address(relayer), + 1 + ) + ); executor.processMessageFromRoot(1, address(relayer), abi.encode(nonce, address(this), _calls)); } diff --git a/test/unit/ethereum-arbitrum/EthereumToArbitrumExecutor.t.sol b/test/unit/ethereum-arbitrum/EthereumToArbitrumExecutor.t.sol index 942d92d..c192fe5 100644 --- a/test/unit/ethereum-arbitrum/EthereumToArbitrumExecutor.t.sol +++ b/test/unit/ethereum-arbitrum/EthereumToArbitrumExecutor.t.sol @@ -42,10 +42,12 @@ contract CrossChainExecutorArbitrumUnitTest is Test { function setUp() public { executor = new CrossChainExecutorArbitrum(); greeter = new Greeter(address(executor), "Hello from L2"); + } + function pushCalls(address _target) public { calls.push( CallLib.Call({ - target: address(greeter), + target: _target, data: abi.encodeWithSignature("setGreeting(string)", l1Greeting) }) ); @@ -59,12 +61,16 @@ contract CrossChainExecutorArbitrumUnitTest is Test { function testExecuteCalls() public { setRelayer(); + pushCalls(address(greeter)); vm.startPrank(relayerAlias); vm.expectEmit(true, true, true, true, address(greeter)); emit SetGreeting(l1Greeting, nonce, sender, address(executor)); + vm.expectEmit(true, true, true, true, address(executor)); + emit CallLib.CallSuccess(1, 0, bytes("")); + vm.expectEmit(true, true, true, true, address(executor)); emit ExecutedCalls(relayer, nonce); @@ -75,6 +81,7 @@ contract CrossChainExecutorArbitrumUnitTest is Test { function testExecuteCallsAlreadyExecuted() public { setRelayer(); + pushCalls(address(greeter)); vm.startPrank(relayerAlias); executor.executeCalls(nonce, sender, calls); @@ -83,13 +90,44 @@ contract CrossChainExecutorArbitrumUnitTest is Test { executor.executeCalls(nonce, sender, calls); } + function testExecuteCallsFailed() public { + setRelayer(); + pushCalls(address(this)); + + vm.startPrank(relayerAlias); + + vm.expectEmit(true, true, true, true, address(executor)); + emit CallLib.CallFailure(1, 0, bytes("")); + + vm.expectRevert( + abi.encodeWithSelector( + CrossChainExecutorArbitrum.ExecuteCallsFailed.selector, + address(relayer), + nonce + ) + ); + + executor.executeCalls(nonce, sender, calls); + } + function testExecuteCallsUnauthorized() public { setRelayer(); + pushCalls(address(greeter)); vm.expectRevert(bytes("Executor/sender-unauthorized")); executor.executeCalls(nonce, sender, calls); } + function testExecuteCallsTargetNotZeroAddress() public { + setRelayer(); + pushCalls(address(0)); + + vm.startPrank(relayerAlias); + + vm.expectRevert(bytes("CallLib/no-contract-at-target")); + executor.executeCalls(nonce, sender, calls); + } + /* ============ Setters ============ */ function testSetRelayer() public { diff --git a/test/unit/ethereum-arbitrum/EthereumToArbitrumRelayer.t.sol b/test/unit/ethereum-arbitrum/EthereumToArbitrumRelayer.t.sol index 9c0cb79..0db4b73 100644 --- a/test/unit/ethereum-arbitrum/EthereumToArbitrumRelayer.t.sol +++ b/test/unit/ethereum-arbitrum/EthereumToArbitrumRelayer.t.sol @@ -23,9 +23,8 @@ contract CrossChainRelayerArbitrumUnitTest is Test { address public sender = 0xa3a935315931A09A4e9B8A865517Cc18923497Ad; address public attacker = 0xdBdDa361Db11Adf8A51dab8a511a8ee89128E89A; - uint256 public maxGasLimit = 32000000; uint256 public gasLimit = 1000000; - uint256 public gasLimitGTMax = maxGasLimit + 1; + uint256 public maxSubmissionCost = 1 ether; uint256 public gasPriceBid = 500; uint256 public nonce = 1; @@ -47,7 +46,7 @@ contract CrossChainRelayerArbitrumUnitTest is Test { /* ============ Setup ============ */ function setUp() public { - relayer = new CrossChainRelayerArbitrum(inbox, maxGasLimit); + relayer = new CrossChainRelayerArbitrum(inbox); calls.push( CallLib.Call({ @@ -64,17 +63,11 @@ contract CrossChainRelayerArbitrumUnitTest is Test { /* ============ Constructor ============ */ function testConstructor() public { assertEq(address(relayer.inbox()), address(inbox)); - assertEq(relayer.maxGasLimit(), maxGasLimit); } function testConstructorInboxFail() public { vm.expectRevert(bytes("Relayer/inbox-not-zero-address")); - relayer = new CrossChainRelayerArbitrum(IInbox(address(0)), maxGasLimit); - } - - function testConstructorMaxGasLimitFail() public { - vm.expectRevert(bytes("Relayer/max-gas-limit-gt-zero")); - relayer = new CrossChainRelayerArbitrum(inbox, 0); + relayer = new CrossChainRelayerArbitrum(IInbox(address(0))); } /* ============ relayCalls ============ */ @@ -91,24 +84,6 @@ contract CrossChainRelayerArbitrumUnitTest is Test { assertTrue(relayer.relayed(txHash)); } - function testRelayCallsFail() public { - setExecutor(); - - vm.expectRevert( - abi.encodeWithSelector( - ICrossChainRelayer.GasLimitTooHigh.selector, - gasLimitGTMax, - maxGasLimit - ) - ); - - relayer.relayCalls(calls, gasLimitGTMax); - - bytes32 txHash = relayer.getTxHash(nonce, calls, address(this), gasLimit); - - assertTrue(!relayer.relayed(txHash)); - } - /* ============ processCalls ============ */ function testProcessCalls() public { @@ -126,6 +101,7 @@ contract CrossChainRelayerArbitrumUnitTest is Test { _nonce, calls, address(this), + msg.sender, gasLimit, maxSubmissionCost, gasPriceBid @@ -134,11 +110,53 @@ contract CrossChainRelayerArbitrumUnitTest is Test { assertEq(_ticketId, _randomNumber); } - function testProcessCallsFail() public { + function testCallsNotRelayed() public { setExecutor(); vm.expectRevert(bytes("Relayer/calls-not-relayed")); - relayer.processCalls(nonce, calls, address(this), gasLimit, maxSubmissionCost, gasPriceBid); + 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 ============ */