Vulnerability: CrossDomainMessenger does not successfully guarantee replayability, can lose user funds
Severity: medium

Description:
While `SafeCall.callWithMinGas` successfully ensures that the called function will not revert, it does not ensure any remaining buffer for continued execution on the calling contract.\\nAs a result, there are situations where `OptimismPortal` can be called with an amount of gas such that the remaining gas after calling `L1CrossDomainMessenger` is sufficient to finalize the transaction, but such that the remaining gas after `L1CrossDomainMessenger` makes its call to target is insufficient to mark the transaction as successful or failed.\\nIn any of these valid scenarios, users who withdraw using the L1CrossDomainMessenger (expecting replayability) will have their withdrawals bricked, permanently losing their funds.\\nWhen a user performs a withdrawal with the `L1CrossDomainMessenger`, they include a `gasLimit` value, which specifies the amount of gas that is needed for the function to execute on L1.\\nThis value is translated into two separate values:\\nThe `OptimismPortal` sends at least baseGas(_message, _minGasLimit) = 64/63 * `_minGasLimit` + 16 * data.length + 200_000 to `L1CrossDomainMessenger`, which accounts for the additional overhead used by the Cross Domain Messenger.\\nThe `L1CrossDomainMessenger` sends at least `_minGasLimit` to the target contract.\\nThe core of this vulnerability is in the fact that, if:\\n`OptimismPortal` retains sufficient gas after its call to complete the transaction, and\\n`L1CrossDomainMessenger` runs out of gas after its transaction is complete (even if the tx succeeded)\\n...then the result will be that the transaction is marked as finalized in the Portal (disallowing it from being called again), while the Cross Domain Messenger transaction will revert, causing the target transaction to revert and not setting it in `failedMessages` (disallowing it from being replayed). The result is that the transaction will be permanently stuck.\\nCalcuations\\nLet's run through the math to see how this might unfold. We will put aside the additional gas allocated for calldata length, because this amount is used up in the call and doesn't materially impact the calculations.\\nWhen the `OptimismPortal` calls the `L1CrossDomainMessenger`, it is enforced that the gas sent will be greater than or equal to `_minGasLimit * 64/63 + 200_000`.\\nThis ensures that the remaining gas for the `OptimismPortal` to continue execution after the call is at least `_minGasLimit / 64 + 3125`. Even assuming that `_minGasLimit == 0`, this is sufficient for `OptimismPortal` to complete execution, so we can safely say that any time `OptimismPortal.finalizeWithdrawalTransaction()` is called with sufficient gas to pass the `SafeCall.callWithMinGas()` check, it will complete execution.\\nMoving over to `L1CrossDomainMessenger`, our call begins with at least `_minGasLimit * 64/63 + 200_000` gas. By the time we get to the external call, we have remaining gas of at least `_minGasLimit * 64/63 + 158_998`. This leaves us with the following guarantees:\\nGas available for the external call will be at least 63/64ths of that, which equals `_minGasLimit + 156_513`.\\nGas available for continued execution after the call will be at least 1/64th of that, which equals `_minGasLimit * 1/63 + 3125`.\\nThe additional gas required to mark the transaction as `failedMessages[versionedHash] = true` and complete the rest of the execution is `23,823`.\\nTherefore, in any situation where the external call uses all the available gas will revert if `_minGasLimit * 1/63 + 3125 < 23_823`, which simplifies to `_minGasLimit < 1_303_974`. In other words, in most cases.\\nHowever, it should be unusual for the external call to use all the available gas. In most cases, it should only use `_minGasLimit`, which would leave `156_513` available to resolve this issue.\\nSo, let's look at some examples of times when this may not be the case.\\nAt Risk Scenarios\\nThere are several valid scenarios where users might encounter this issue, and have their replayable transactions stuck:\\nUser Sends Too Little Gas\\nThe expectation when using the Cross Domain Messenger is that all transactions will be replayable. Even if the `_minGasLimit` is set incorrectly, there will always be the opportunity to correct this by replaying it yourself with a higher gas limit. In fact, it is a core tenet of the Cross Domain Messengers that they include replay protection for failed transactions.\\nHowever, if a user sets a gas limit that is too low for a transaction, this issue may result.\\nThe consequence is that, while users think that Cross Domain Messenger transactions are replayable and gas limits don't need to be set precisely, they can in fact lose their entire withdrawal if they set their gas limit too low, even when using the "safe" Standard Bridge or Cross Domain Messenger.\\nTarget Contract Uses More Than Minimum Gas\\nThe checks involved in this process ensure that sufficient gas is being sent to a contract, but there is no requirement that that is all the gas a contract uses.\\n`_minGasLimit` should be set sufficiently high for the contract to not revert, but that doesn't mean that `_minGasLimit` represents the total amount of gas the contract uses.\\nAs a silly example, let's look at a modified version of the `gas()` function in your `Burn.sol` contract:\\n```\\nfunction gas(uint256 _amountToLeave) internal view {\\n    uint256 i = 0;\\n    while (gasleft() > _amountToLeave) {\\n        ++i;\\n    }\\n}\\n```\\n\\nThis function runs until it leaves a specified amount of gas, and then returns. While the amount of gas sent to this contract could comfortably exceed the `_minGasLimit`, it would not be safe to assume that the amount leftover afterwards would equal `startingGas - _minGasLimit`.\\nWhile this is a contrived example, but the point is that there are many situations where it is not safe to assume that the minimum amount of gas a function needs will be greater than the amount it ends up using, if it is provided with extra gas.\\nIn these cases, the assumption that our leftover gas after the function runs will be greater than the required 1/64th does not hold, and the withdrawal can be bricked.

Impact:
In certain valid scenarios where users decide to use the "safe" Cross Domain Messenger or Standard Bridge with the expectation of replayability, their withdrawals from L2 to L1 can be bricked and permanently lost.

Recommended mitigation:
`L1CrossDomainMessenger` should only send `_minGasLimit` along with its call to the target (rather than gas()) to ensure it has sufficient leftover gas to ensure replayability.