Repository: ethereum-optimism/optimism
Pull request #14608: feat: update message passing for EIP7623
Author: login: smartcontracts
Created: 2025-03-04T02:16:00Z | Merged: 2025-03-05T16:11:54Z

Updates the way that gas limits are handled in the message passing system to account for EIP-7623. Most significant change is to make the baseGas calculation inside of CrossDomainMessenger more principled.


Diff:
diff --git a/packages/contracts-bedrock/interfaces/universal/ICrossDomainMessenger.sol b/packages/contracts-bedrock/interfaces/universal/ICrossDomainMessenger.sol
index 256b09fa56e..3f0b7774002 100644
--- a/packages/contracts-bedrock/interfaces/universal/ICrossDomainMessenger.sol
+++ b/packages/contracts-bedrock/interfaces/universal/ICrossDomainMessenger.sol
@@ -17,6 +17,9 @@ interface ICrossDomainMessenger {
     function RELAY_CONSTANT_OVERHEAD() external view returns (uint64);
     function RELAY_GAS_CHECK_BUFFER() external view returns (uint64);
     function RELAY_RESERVED_GAS() external view returns (uint64);
+    function TX_BASE_GAS() external view returns (uint64);
+    function FLOOR_CALLDATA_OVERHEAD() external view returns (uint64);
+    function ENCODING_OVERHEAD() external view returns (uint64);
     function baseGas(bytes memory _message, uint32 _minGasLimit) external pure returns (uint64);
     function failedMessages(bytes32) external view returns (bool);
     function messageNonce() external view returns (uint256);
diff --git a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh
index f400ab5cab5..77eec5660b8 100755
--- a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh
+++ b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh
@@ -54,6 +54,7 @@ ALLOWED_FILES=(
   "src/L1/L1StandardBridge.sol"
   "src/L1/OPContractsManager.sol"
   "src/L1/OPContractsManagerInterop.sol"
+  "src/L1/OPPrestateUpdater.sol"
   "src/L1/OptimismPortal2.sol"
   "src/L1/OptimismPortalInterop.sol"
   "src/L1/ProtocolVersions.sol"
diff --git a/packages/contracts-bedrock/snapshots/abi/L1CrossDomainMessenger.json b/packages/contracts-bedrock/snapshots/abi/L1CrossDomainMessenger.json
index ac33f91e24c..6cce51733d3 100644
--- a/packages/contracts-bedrock/snapshots/abi/L1CrossDomainMessenger.json
+++ b/packages/contracts-bedrock/snapshots/abi/L1CrossDomainMessenger.json
@@ -4,6 +4,32 @@
     "stateMutability": "nonpayable",
     "type": "constructor"
   },
+  {
+    "inputs": [],
+    "name": "ENCODING_OVERHEAD",
+    "outputs": [
+      {
+        "internalType": "uint64",
+        "name": "",
+        "type": "uint64"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "FLOOR_CALLDATA_OVERHEAD",
+    "outputs": [
+      {
+        "internalType": "uint64",
+        "name": "",
+        "type": "uint64"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
   {
     "inputs": [],
     "name": "MESSAGE_VERSION",
@@ -134,6 +160,19 @@
     "stateMutability": "view",
     "type": "function"
   },
+  {
+    "inputs": [],
+    "name": "TX_BASE_GAS",
+    "outputs": [
+      {
+        "internalType": "uint64",
+        "name": "",
+        "type": "uint64"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
   {
     "inputs": [
       {
diff --git a/packages/contracts-bedrock/snapshots/abi/L2CrossDomainMessenger.json b/packages/contracts-bedrock/snapshots/abi/L2CrossDomainMessenger.json
index 717bbb6eb6f..b9f04f127fb 100644
--- a/packages/contracts-bedrock/snapshots/abi/L2CrossDomainMessenger.json
+++ b/packages/contracts-bedrock/snapshots/abi/L2CrossDomainMessenger.json
@@ -4,6 +4,32 @@
     "stateMutability": "nonpayable",
     "type": "constructor"
   },
+  {
+    "inputs": [],
+    "name": "ENCODING_OVERHEAD",
+    "outputs": [
+      {
+        "internalType": "uint64",
+        "name": "",
+        "type": "uint64"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "FLOOR_CALLDATA_OVERHEAD",
+    "outputs": [
+      {
+        "internalType": "uint64",
+        "name": "",
+        "type": "uint64"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
   {
     "inputs": [],
     "name": "MESSAGE_VERSION",
@@ -121,6 +147,19 @@
     "stateMutability": "view",
     "type": "function"
   },
+  {
+    "inputs": [],
+    "name": "TX_BASE_GAS",
+    "outputs": [
+      {
+        "internalType": "uint64",
+        "name": "",
+        "type": "uint64"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
   {
     "inputs": [
       {
diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json
index 71146521dad..68d3e79e9fb 100644
--- a/packages/contracts-bedrock/snapshots/semver-lock.json
+++ b/packages/contracts-bedrock/snapshots/semver-lock.json
@@ -4,28 +4,28 @@
     "sourceCodeHash": "0xe772f7db8033e4a738850cb28ac4849d3a454c93732135a8a10d4f7cb498088e"
   },
   "src/L1/L1CrossDomainMessenger.sol": {
-    "initCodeHash": "0x03a3c0eb2418aba9f93bb89723ba2ee7cb9e1988ca00f380503c960149c85b7a",
-    "sourceCodeHash": "0x5907cdb82ec5f6bef2184558a511d049ab3ee65388cf44d0c20d0f234ef8ca44"
+    "initCodeHash": "0x5a6272f6bd4346da460b7ff2ebc426d158d7ffc65dc0f2c0651a9e6d545bfd03",
+    "sourceCodeHash": "0xf11fa72481dbe43dad4e7a48645fcf92d0feeefa0b98b282042bdda568508372"
   },
   "src/L1/L1ERC721Bridge.sol": {
-    "initCodeHash": "0xa54dbc482f557966a636158bc3a566c351cb04ee8b1cb13aa2de61b9a0ddb064",
-    "sourceCodeHash": "0xe9c85de57005fe964acb9c62f5009aa5a4ba0eacc24bec67a0e4a043d2db5757"
+    "initCodeHash": "0x819fcccb74bef53241d7d651f73949115e5706b61c7cb8cdb9b6d9d34ef39e89",
+    "sourceCodeHash": "0x5036f59ae3414d1106f148765bba9d1759844c9c2d3e18ab5c81bb49cf59eab8"
   },
   "src/L1/L1StandardBridge.sol": {
-    "initCodeHash": "0xe95886a6408f77fb433687049d85dd3c2200dfacc2dd6c5fd7f9554f6fc5b11b",
-    "sourceCodeHash": "0x7f614d0c5251365580c02dd898900a583ece2aa28579adea74a3808b575949b4"
+    "initCodeHash": "0xbd4a03a3611a0de9669c4c8556f90c5aaef666b899d9ded1c07abc60263da8d6",
+    "sourceCodeHash": "0x44797707aea8c63dec049a02d69ea056662a06e5cf320028ab8b388634bf1c67"
   },
   "src/L1/OPContractsManager.sol": {
-    "initCodeHash": "0x7ad29e286feabd0ef333f8969be8e766b3c68e97fe7b75e4c8a9699dd49e87f8",
-    "sourceCodeHash": "0xa412132a08809d03088690b7275c8118327d69d1e42b9d033c34cc283dea77a0"
+    "initCodeHash": "0xc50bce08abbfc66d7f902b5b1f5504c99cbdca17ca4ff98f67801e40c0751f6b",
+    "sourceCodeHash": "0xff40fd58a78fdfa90e8ac84bed278e6af8329fd826b478fb4fcd6641f62ae1b6"
   },
   "src/L1/OptimismPortal2.sol": {
-    "initCodeHash": "0x93c5e8ff52ff8b1cedd985b4a8890c12b56d5959832405c7622615c3541908f5",
-    "sourceCodeHash": "0x753465f9317d89f8ea64bcd3fc3fb1164d040b9be2d5ba3d401d951f2ceff023"
+    "initCodeHash": "0xd1651b8a6f4d25611a0105d5cc7c1da3921417bd44da870ec63bf5ccd1bc7c63",
+    "sourceCodeHash": "0xce7373d8c7df47caa8b090f3afb3d2539677f12cb3eff7fc0ab1fd85638f05c1"
   },
   "src/L1/OptimismPortalInterop.sol": {
-    "initCodeHash": "0x12ecac882d3a45901993f93661a5e8ada1fb964d25e7843cfb9f56b2a30aa7b4",
-    "sourceCodeHash": "0xa031a40aa6f722466a76a36871989942f34351a327227a62266bb9a26c439fe2"
+    "initCodeHash": "0xd59854648bf205dfbea96b483b2937441c32e9ef66b002468c2c14c0d6661728",
+    "sourceCodeHash": "0xd00b267dcf125e77c10b28c088be4378ec779927e3bcfeb6aa9a7f3d51370490"
   },
   "src/L1/ProtocolVersions.sol": {
     "initCodeHash": "0x5a76c8530cb24cf23d3baacc6eefaac226382af13f1e2a35535d2ec2b0573b29",
@@ -72,20 +72,20 @@
     "sourceCodeHash": "0xd0471c328c1d17c5863261322bf8d5aff2e7e9e3a1135631a993aa75667621df"
   },
   "src/L2/L2CrossDomainMessenger.sol": {
-    "initCodeHash": "0x6117d2ca80029c802b1e5cc36341f03ec48efd07df0251121d3faf5e93aa5901",
-    "sourceCodeHash": "0x48001529220d274c5cd2e84787239b6d2244780d23894e0a8e96550a40be18fe"
+    "initCodeHash": "0xe160be403df12709c371c33195d1b9c3b5e9499e902e86bdabc8eed749c3fd61",
+    "sourceCodeHash": "0x12ea125038b87e259a0d203e119faa6e9726ab2bdbc30430f820ccd48fe87e14"
   },
   "src/L2/L2ERC721Bridge.sol": {
-    "initCodeHash": "0xe0c4646e3372d53c294028ecbeff97b1ecb14cf7e44361b112c62b0c0d956ea2",
-    "sourceCodeHash": "0xc3c7fe397f91baa0b89567b8ecb2c0aff522000fd4889346e1dfec2a281486fe"
+    "initCodeHash": "0x863f0f5b410983f3e51cd97c60a3a42915141b7452864d0e176571d640002b81",
+    "sourceCodeHash": "0xc05bfcfadfd09a56cfea68e7c1853faa36d114d9a54cd307348be143e442c35a"
   },
   "src/L2/L2StandardBridge.sol": {
-    "initCodeHash": "0x0b5ec1511a7059f8eed938e68a2258485a3e24ac9ebef1f386e8dd6725b1a7c4",
-    "sourceCodeHash": "0xf59e693939236d00dbc5e21a5ba3e94eb442967e6a4533973d805c391e554465"
+    "initCodeHash": "0xba5b288a396b34488ba7be68473305529c7da7c43e5f1cfc48d6a4aecd014103",
+    "sourceCodeHash": "0x9dd26676cd1276c807ffd4747236783c5170d0919c70693e70b7e4c4c2675429"
   },
   "src/L2/L2StandardBridgeInterop.sol": {
-    "initCodeHash": "0xa34db426a412a36c06b7a172a107af4b075d1df6997e9bb5619e14f8e52de49c",
-    "sourceCodeHash": "0xc2c5c34bfe7bf2833c2b4dec73f403b4c3a57d26ef0b19a9b6e4958dc0908090"
+    "initCodeHash": "0xa7a2e7efe8116ebb21f47ee06c1e62d3b2f5a046478094611a2ab4b714154030",
+    "sourceCodeHash": "0xde724da82ecf3c96b330c2876a7285b6e2b933ac599241eaa3174c443ebbe33a"
   },
   "src/L2/L2ToL1MessagePasser.sol": {
     "initCodeHash": "0xf9d82084dcef31a3737a76d8ee4e5842ea190d0f77ed4678adb3bbb95217050f",
diff --git a/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol
index 5e475c715c8..c1cd2efc0ce 100644
--- a/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol
+++ b/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol
@@ -31,8 +31,8 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, ISemver {
     address private spacer_253_0_20;
 
     /// @notice Semantic version.
-    /// @custom:semver 2.5.0
-    string public constant version = "2.5.0";
+    /// @custom:semver 2.6.0
+    string public constant version = "2.6.0";
 
     /// @notice Constructs the L1CrossDomainMessenger contract.
     constructor() {
diff --git a/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol b/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol
index 78afeef759c..51f51c8ea10 100644
--- a/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol
+++ b/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol
@@ -28,8 +28,8 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver {
     ISuperchainConfig public superchainConfig;
 
     /// @notice Semantic version.
-    /// @custom:semver 2.3.1
-    string public constant version = "2.3.1";
+    /// @custom:semver 2.4.0
+    string public constant version = "2.4.0";
 
     /// @notice Constructs the L1ERC721Bridge contract.
     constructor() ERC721Bridge() {
diff --git a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol
index 3a22ef24adc..d893370a022 100644
--- a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol
+++ b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol
@@ -74,8 +74,8 @@ contract L1StandardBridge is StandardBridge, ISemver {
     );
 
     /// @notice Semantic version.
-    /// @custom:semver 2.2.2
-    string public constant version = "2.2.2";
+    /// @custom:semver 2.3.0
+    string public constant version = "2.3.0";
 
     /// @notice Address of the SuperchainConfig contract.
     ISuperchainConfig public superchainConfig;
diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol
index b70b4179a09..eb858c76709 100644
--- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol
+++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol
@@ -532,6 +532,20 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase {
             assertValidOpChainConfig(_opChainConfigs[i]);
             ISystemConfig.Addresses memory opChainAddrs = _opChainConfigs[i].systemConfigProxy.getAddresses();
 
+            // -------- Upgrade Contracts Stored in SystemConfig --------
+
+            // OptimismPortal and L1CrossDomainMessenger are being upgraded to include the fixes
+            // for EIP-7623 (minimum gas limits for L1 -> L2 messages).
+            upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.optimismPortal, impls.optimismPortalImpl);
+            upgradeTo(
+                _opChainConfigs[i].proxyAdmin, opChainAddrs.l1CrossDomainMessenger, impls.l1CrossDomainMessengerImpl
+            );
+
+            // L1ERC721Bridge and L1StandardBridge are being upgraded to include the tweaks to the
+            // EOA checking code for EIP-7702 (code length == 23).
+            upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.l1ERC721Bridge, impls.l1ERC721BridgeImpl);
+            upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.l1StandardBridge, impls.l1StandardBridgeImpl);
+
             // -------- Discover and Upgrade Proofs Contracts --------
 
             // All chains have the Permissioned Dispute Game.
@@ -1216,9 +1230,9 @@ contract OPContractsManager is ISemver {
 
     // -------- Constants and Variables --------
 
-    /// @custom:semver 1.7.0
+    /// @custom:semver 1.8.0
     function version() public pure virtual returns (string memory) {
-        return "1.7.0";
+        return "1.8.0";
     }
 
     OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder;
diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol
index 0573afdf46f..0b43241ea4f 100644
--- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol
+++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol
@@ -178,9 +178,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver {
     }
 
     /// @notice Semantic version.
-    /// @custom:semver 3.13.0
+    /// @custom:semver 3.14.0
     function version() public pure virtual returns (string memory) {
-        return "3.13.0";
+        return "3.14.0";
     }
 
     /// @notice Constructs the OptimismPortal contract.
@@ -255,7 +255,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver {
     /// @param _byteCount Number of bytes in the calldata.
     /// @return The minimum gas limit for a deposit.
     function minimumGasLimit(uint64 _byteCount) public pure returns (uint64) {
-        return _byteCount * 16 + 21000;
+        return _byteCount * 40 + 21000;
     }
 
     /// @notice Accepts value so that users can send ETH directly to this contract and have the
diff --git a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol
index 5993b7f27dc..7920a4931b1 100644
--- a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol
+++ b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol
@@ -25,9 +25,9 @@ contract OptimismPortalInterop is OptimismPortal2 {
         OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds)
     { }
 
-    /// @custom:semver +interop.2
+    /// @custom:semver +interop.3
     function version() public pure override returns (string memory) {
-        return string.concat(super.version(), "+interop.2");
+        return string.concat(super.version(), "+interop.3");
     }
 
     /// @notice Sets static configuration options for the L2 system.
diff --git a/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol
index 8c5af32f016..3e33c55dd0c 100644
--- a/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol
+++ b/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol
@@ -19,8 +19,8 @@ import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol";
 ///         L2 on the L2 side. Users are generally encouraged to use this contract instead of lower
 ///         level message passing contracts.
 contract L2CrossDomainMessenger is CrossDomainMessenger, ISemver {
-    /// @custom:semver 2.1.1-beta.8
-    string public constant version = "2.1.1-beta.8";
+    /// @custom:semver 2.2.0
+    string public constant version = "2.2.0";
 
     /// @notice Constructs the L2CrossDomainMessenger contract.
     constructor() {
diff --git a/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol b/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol
index 5f56ca1bf1f..e080f694751 100644
--- a/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol
+++ b/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol
@@ -24,8 +24,8 @@ import { ISemver } from "interfaces/universal/ISemver.sol";
 ///         **WARNING**: Do not bridge an ERC721 that was originally deployed on Optimism. This
 ///         bridge ONLY supports ERC721s originally deployed on Ethereum.
 contract L2ERC721Bridge is ERC721Bridge, ISemver {
-    /// @custom:semver 1.9.0
-    string public constant version = "1.9.0";
+    /// @custom:semver 1.10.0
+    string public constant version = "1.10.0";
 
     /// @notice Constructs the L2ERC721Bridge contract.
     constructor() ERC721Bridge() {
diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol
index 53418eec973..1e9c2ac15f3 100644
--- a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol
+++ b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol
@@ -57,9 +57,9 @@ contract L2StandardBridge is StandardBridge, ISemver {
     );
 
     /// @notice Semantic version.
-    /// @custom:semver 1.12.0
+    /// @custom:semver 1.13.0
     function version() public pure virtual returns (string memory) {
-        return "1.12.0";
+        return "1.13.0";
     }
 
     /// @notice Constructs the L2StandardBridge contract.
diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol
index 485d862a293..8dd1230e12e 100644
--- a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol
+++ b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol
@@ -39,9 +39,9 @@ contract L2StandardBridgeInterop is L2StandardBridge {
     event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount);
 
     /// @notice Semantic version.
-    /// @custom:semver +interop.9
+    /// @custom:semver +interop.10
     function version() public pure override returns (string memory) {
-        return string.concat(super.version(), "+interop.9");
+        return string.concat(super.version(), "+interop.10");
     }
 
     /// @notice Converts `amount` of `from` token to `to` token.
diff --git a/packages/contracts-bedrock/src/libraries/EOA.sol b/packages/contracts-bedrock/src/libraries/EOA.sol
index 7d9d5cb7197..254cf6eabe8 100644
--- a/packages/contracts-bedrock/src/libraries/EOA.sol
+++ b/packages/contracts-bedrock/src/libraries/EOA.sol
@@ -9,7 +9,7 @@ library EOA {
     function isSenderEOA() internal view returns (bool isEOA_) {
         if (msg.sender == tx.origin) {
             isEOA_ = true;
-        } else {
+        } else if (address(msg.sender).code.length == 23) {
             // If the sender is not the origin, check for 7702 delegated EOAs.
             assembly {
                 let ptr := mload(0x40)
@@ -17,6 +17,9 @@ library EOA {
                 extcodecopy(caller(), ptr, 0, 0x20)
                 isEOA_ := eq(shr(232, mload(ptr)), 0xEF0100)
             }
+        } else {
+            // If more or less than 23 bytes of code, not a 7702 delegated EOA.
+            isEOA_ = false;
         }
     }
 }
diff --git a/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol b/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
index 65b781707fe..ba21a0c7c51 100644
--- a/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
+++ b/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
@@ -3,6 +3,7 @@ pragma solidity 0.8.15;
 
 // Libraries
 import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
 import { SafeCall } from "src/libraries/SafeCall.sol";
 import { Hashing } from "src/libraries/Hashing.sol";
 import { Encoding } from "src/libraries/Encoding.sol";
@@ -115,6 +116,19 @@ abstract contract CrossDomainMessenger is
     ///         call in `relayMessage`.
     uint64 public constant RELAY_GAS_CHECK_BUFFER = 5_000;
 
+    /// @notice Base gas required for any transaction in the EVM.
+    uint64 public constant TX_BASE_GAS = 21_000;
+
+    /// @notice Floor overhead per byte of non-zero calldata in a message. Calldata floor was
+    ///         introduced in EIP-7623.
+    uint64 public constant FLOOR_CALLDATA_OVERHEAD = 40;
+
+    /// @notice Overhead added to the internal message data when the full call to relayMessage is
+    ///         ABI encoded. This is a constant value that is specific to the V1 message encoding
+    ///         scheme. 260 is an upper bound, actual overhead can be as low as 228 bytes for an
+    ///         empty message.
+    uint64 public constant ENCODING_OVERHEAD = 260;
+
     /// @notice Mapping of message hashes to boolean receipt values. Note that a message will only
     ///         be present in this mapping if it has successfully been relayed on this chain, and
     ///         can therefore not be relayed again.
@@ -340,23 +354,45 @@ abstract contract CrossDomainMessenger is
     /// @param _message     Message to compute the amount of required gas for.
     /// @param _minGasLimit Minimum desired gas limit when message goes to target.
     /// @return Amount of gas required to guarantee message receipt.
-    function baseGas(bytes calldata _message, uint32 _minGasLimit) public pure returns (uint64) {
-        return
-        // Constant overhead
-        RELAY_CONSTANT_OVERHEAD
-        // Calldata overhead
-        + (uint64(_message.length) * MIN_GAS_CALLDATA_OVERHEAD)
-        // Dynamic overhead (EIP-150)
-        + ((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) / MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR)
-        // Gas reserved for the worst-case cost of 3/5 of the `CALL` opcode's dynamic gas
-        // factors. (Conservative)
-        + RELAY_CALL_OVERHEAD
-        // Relay reserved gas (to ensure execution of `relayMessage` completes after the
-        // subcontext finishes executing) (Conservative)
-        + RELAY_RESERVED_GAS
-        // Gas reserved for the execution between the `hasMinGas` check and the `CALL`
-        // opcode. (Conservative)
-        + RELAY_GAS_CHECK_BUFFER;
+    function baseGas(bytes memory _message, uint32 _minGasLimit) public pure returns (uint64) {
+        // Base gas should really be computed on the fully encoded message but that would break the
+        // expected API, so we instead just add the encoding overhead to the message length inside
+        // of this function.
+
+        // We need a minimum amount of execution gas to ensure that the message will be received on
+        // the other side without running out of gas (stored within the failedMessages mapping).
+        // If we get beyond the hasMinGas check, then we *must* supply more than minGasLimit to
+        // the external call.
+        uint64 executionGas = uint64(
+            // Constant costs for relayMessage
+            RELAY_CONSTANT_OVERHEAD
+            // Covers dynamic parts of the CALL opcode
+            + RELAY_CALL_OVERHEAD
+            // Ensures execution of relayMessage completes after call
+            + RELAY_RESERVED_GAS
+            // Buffer between hasMinGas check and the CALL
+            + RELAY_GAS_CHECK_BUFFER
+            // Minimum gas limit, multiplied by 64/63 to account for EIP-150.
+            + ((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) / MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR)
+        );
+
+        // Total message size is the result of properly ABI encoding the call to relayMessage.
+        // Since we only get the message data and not the rest of the calldata, we use the
+        // ENCODING_OVERHEAD constant to conservatively account for the remaining bytes.
+        uint64 totalMessageSize = uint64(_message.length + ENCODING_OVERHEAD);
+
+        // Finally, replicate the transaction cost formula as defined after EIP-7623. This is
+        // mostly relevant in the L1 -> L2 case because we need to be able to cover the intrinsic
+        // cost of the message but it doesn't hurt in the L2 -> L1 case. After EIP-7623, the cost
+        // of a transaction is floored by its calldata size. We don't need to account for the
+        // contract creation case because this is always a call to relayMessage.
+        return TX_BASE_GAS
+            + uint64(
+                Math.max(
+                    executionGas + (totalMessageSize * MIN_GAS_CALLDATA_OVERHEAD),
+                    (totalMessageSize * FLOOR_CALLDATA_OVERHEAD)
+                )
+            );
     }
 
     /// @notice Initializer.
diff --git a/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol
index 762ac3ed190..39d8e8c86fa 100644
--- a/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol
+++ b/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol
@@ -3,7 +3,7 @@ pragma solidity 0.8.15;
 
 // Testing utilities
 import { CommonTest } from "test/setup/CommonTest.sol";
-import { Reverter } from "test/mocks/Callers.sol";
+import { Reverter, GasBurner } from "test/mocks/Callers.sol";
 import { stdError } from "forge-std/StdError.sol";
 
 // Libraries
@@ -18,6 +18,23 @@ import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.s
 import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
 import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
 
+contract Encoding_Harness {
+    function encodeCrossDomainMessage(
+        uint256 nonce,
+        address sender,
+        address target,
+        uint256 value,
+        uint256 gasLimit,
+        bytes memory data
+    )
+        external
+        pure
+        returns (bytes memory)
+    {
+        return Encoding.encodeCrossDomainMessage(nonce, sender, target, value, gasLimit, data);
+    }
+}
+
 contract L1CrossDomainMessenger_Test is CommonTest {
     /// @dev The receiver address
     address recipient = address(0xabbaacdc);
@@ -25,9 +42,13 @@ contract L1CrossDomainMessenger_Test is CommonTest {
     /// @dev The storage slot of the l2Sender
     uint256 senderSlotIndex;
 
+    /// @dev Encoding library harness.
+    Encoding_Harness encoding;
+
     function setUp() public override {
         super.setUp();
         senderSlotIndex = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot;
+        encoding = new Encoding_Harness();
     }
 
     /// @dev Tests that the implementation is initialized correctly.
@@ -275,6 +296,95 @@ contract L1CrossDomainMessenger_Test is CommonTest {
         );
     }
 
+    /// @notice Tests that the relayMessage function on L2 will always succeed for any potential
+    ///         message, regardless of the size of the message, the minimum gas limit, or the
+    ///         amount of gas used by the target contract.
+    function testFuzz_relayMessage_baseGasSufficient_succeeds(
+        uint24 _messageLength,
+        uint32 _minGasLimit,
+        uint32 _gasToUse
+    )
+        external
+    {
+        // TODO(#14609): Update this test to use default.isolate = true once a new stable Foundry
+        // release is available that includes #9904. That will allow us to use this test to check
+        // for changes to the EVM itself that might cause our gas formula to be incorrect.
+
+        // Skip if this is a fork test, won't work.
+        skipIfForkTest("L2CrossDomainMessenger doesn't exist on L1 in forked test");
+
+        // Using smaller uint so the fuzzer doesn't give as many massive values that get bounded.
+        // TODO: Known issue, messages above 34k can actually OOG on the receiving side if the
+        // target uses all available gas. Can be resolved by capping data sizes on the XDM or by
+        // increasing the amount of available relay gas to ~100k. If increasing relay gas, should
+        // have the relay gas only increase when the calldata size is large to avoid disrupting
+        // in-flight L2 -> L1 messages.
+        _messageLength = uint24(bound(_messageLength, 0, 34_000));
+
+        // Need more than 500 since GasBurner requires it.
+        // It's ok to try to use more than minGasLimit since the L2CrossDomainMessenger should
+        // catch the revert and store the message hash in the failedMessages mapping.
+        _gasToUse = uint32(bound(_gasToUse, 500, type(uint32).max));
+
+        // Generate random bytes, more useful than having a _message parameter.
+        bytes memory _message = vm.randomBytes(_messageLength);
+
+        // Compute the base gas.
+        // Base gas should really be computed on the fully encoded message but that would break the
+        // expected API, so we instead just add the encoding overhead to the message length inside
+        // of the baseGas function.
+        uint64 baseGas = l1CrossDomainMessenger.baseGas(_message, _minGasLimit);
+
+        // Deploy a gas burner.
+        address target = address(new GasBurner(_gasToUse));
+
+        // Encode the message.
+        bytes memory encoded = Encoding.encodeCrossDomainMessage(
+            Encoding.encodeVersionedNonce(0, 1), // nonce
+            alice, // Sender doesn't matter
+            target,
+            0, // Value doesn't matter
+            _minGasLimit,
+            _message
+        );
+
+        // Count the number of non-zero bytes in the message.
+        uint256 zeroBytesInCalldata = 0;
+        uint256 nonzeroBytesInCalldata = 0;
+        for (uint256 i = 0; i < encoded.length; i++) {
+            if (encoded[i] != bytes1(0)) {
+                nonzeroBytesInCalldata++;
+            } else {
+                zeroBytesInCalldata++;
+            }
+        }
+
+        // Base gas must always be sufficient to cover the floor cost from EIP-7623.
+        assertGt(baseGas, 21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 10));
+
+        // Actual gas on L2 will be the base gas minus the intrinsic gas cost. Note that even after
+        // EIP-7623, we still deduct 21k + 16 gas per calldata token from the gas limit before
+        // execution happens. After execution, if the message didn't spend enough in execution gas,
+        // the EVM will floor the cost of the transaction to 21k + 40 gas per calldata token.
+        uint256 gasSupplied = baseGas - (21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 4));
+
+        // We'll trigger the L2CrossDomainMessenger as if we're the L1CrossDomainMessenger
+        address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger));
+        vm.prank(caller);
+
+        // Trigger the L2CrossDomainMessenger.
+        // Should NOT fail.
+        (bool success,) = address(l2CrossDomainMessenger).call{ gas: gasSupplied }(encoded);
+        assertTrue(success, "L2CrossDomainMessenger call should not fail");
+
+        // Message should either be in the failed or successful messages mapping.
+        bool inFailedMessages = l2CrossDomainMessenger.failedMessages(keccak256(encoded));
+        bool inSuccessfulMessages = l2CrossDomainMessenger.successfulMessages(keccak256(encoded));
+        assertTrue(
+            inFailedMessages || inSuccessfulMessages, "message should be in either failed or successful messages"
+        );
+    }
+
     /// @dev Tests that the relayMessage function reverts if eth is
     ///      sent from a contract other than the standard bridge.
     function test_replayMessage_withValue_reverts() external {
@@ -288,6 +398,61 @@ contract L1CrossDomainMessenger_Test is CommonTest {
         );
     }
 
+    /// @notice Tests that the ENCODING_OVERHEAD is always greater than or equal to the number of
+    ///         bytes in an encoded message OTHER than the size of the data field.
+    ///         ENCODING_OVERHEAD needs to account for these other bytes so that the total message
+    ///         size used in the baseGas function is accurate. This test ensures that if the
+    ///         encoding ever changes, this test will fail and the developer will need to update
+    ///         the ENCODING_OVERHEAD constant.
+    /// @param _nonce The nonce to encode into the message.
+    /// @param _version The version to encode into the message.
+    /// @param _sender The sender to encode into the message.
+    /// @param _target The target to encode into the message.
+    /// @param _value The value to encode into the message.
+    /// @param _minGasLimit The minimum gas limit to encode into the message.
+    /// @param _message The message to encode into the message.
+    function testFuzz_encodingOverhead_sufficient_succeeds(
+        uint240 _nonce,
+        uint16 _version,
+        address _sender,
+        address _target,
+        uint256 _value,
+        uint256 _minGasLimit,
+        bytes memory _message
+    )
+        external
+    {
+        // Make sure that unexpected nonces aren't being used right now.
+        // Prevents people from forgetting to update this test if a new version is ever used.
+        if (_version > 2) {
+            vm.expectRevert("Encoding: unknown cross domain message version");
+            encoding.encodeCrossDomainMessage(
+                Encoding.encodeVersionedNonce({ _nonce: 0, _version: _version }),
+                _sender,
+                _target,
+                _value,
+                _minGasLimit,
+                _message
+            );
+        }
+
+        // Clamp the version to 0 or 1.
+        _version = _version % 2;
+
+        // Encode the message.
+        bytes memory encoded = encoding.encodeCrossDomainMessage(
+            Encoding.encodeVersionedNonce({ _nonce: _nonce, _version: _version }),
+            _sender,
+            _target,
+            _value,
+            _minGasLimit,
+            _message
+        );
+
+        // Total size should always be greater than or equal to the overhead bytes.
+        assertGe(l1CrossDomainMessenger.ENCODING_OVERHEAD(), encoded.length - _message.length);
+    }
+
     /// @dev Tests that the xDomainMessageSender is reset to the original value
     ///      after a message is relayed.
     function test_xDomainMessageSender_reset_succeeds() external {
diff --git a/packages/contracts-bedrock/test/libraries/EOA.t.sol b/packages/contracts-bedrock/test/libraries/EOA.t.sol
index ddead83ffe3..845fb540e6c 100644
--- a/packages/contracts-bedrock/test/libraries/EOA.t.sol
+++ b/packages/contracts-bedrock/test/libraries/EOA.t.sol
@@ -81,4 +81,27 @@ contract EOA_isEOA_Test is Test {
         vm.prank(sender);
         assertEq(harness.isSenderEOA(), false);
     }
+
+    /// @notice Tests that a contract with 23 bytes of code is not detected as an EOA.
+    /// @param _privateKey The private key of the sender.
+    function testFuzz_isEOA_isContract23BytesNot7702_succeeds(uint256 _privateKey) external {
+        // Make sure that the private key is in the range of a valid secp256k1 private key.
+        _privateKey = boundPrivateKey(_privateKey);
+
+        // Generate a random 23 byte code.
+        bytes memory code = vm.randomBytes(23);
+
+        // If the code happens to be EOF code, change it!
+        if (code[0] == 0xEF) {
+            code[0] = 0xFE; // Anything but EF!
+        }
+
+        // Make sure that the sender is a contract.
+        address sender = vm.addr(_privateKey);
+        vm.etch(sender, code);
+
+        // Should not be considered an EOA.
+        vm.prank(sender);
+        assertEq(harness.isSenderEOA(), false);
+    }
 }
diff --git a/packages/contracts-bedrock/test/mocks/Callers.sol b/packages/contracts-bedrock/test/mocks/Callers.sol
index c64eb423520..965d1eaa1e4 100644
--- a/packages/contracts-bedrock/test/mocks/Callers.sol
+++ b/packages/contracts-bedrock/test/mocks/Callers.sol
@@ -1,6 +1,9 @@
 // SPDX-License-Identifier: MIT
 pragma solidity ^0.8.0;
 
+// Libraries
+import { Burn } from "src/libraries/Burn.sol";
+
 contract CallRecorder {
     struct CallInfo {
         address sender;
@@ -50,3 +53,32 @@ contract DelegateCaller {
         }
     }
 }
+
+/// @title GasBurner
+/// @notice Contract that burns a specified amount of gas on receive or fallback.
+contract GasBurner {
+    /// @notice The amount of gas to burn on receive or fallback.
+    uint256 immutable GAS_TO_BURN;
+
+    /// @notice Constructor.
+    /// @param _gas The amount of gas to burn on receive or fallback.
+    constructor(uint256 _gas) {
+        // 500 gas buffer for Solidity overhead.
+        GAS_TO_BURN = _gas - 500;
+    }
+
+    /// @notice Receive function that burns the specified amount of gas.
+    receive() external payable {
+        _burn();
+    }
+
+    /// @notice Fallback function that burns the specified amount of gas.
+    fallback() external payable {
+        _burn();
+    }
+
+    /// @notice Internal function that burns the specified amount of gas.
+    function _burn() internal view {
+        Burn.gas(GAS_TO_BURN);
+    }
+}
diff --git a/packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
index 88c48dfed2c..01a7ccdb9ba 100644
--- a/packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
+++ b/packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
@@ -6,6 +6,7 @@ import { Test } from "forge-std/Test.sol";
 import { CommonTest } from "test/setup/CommonTest.sol";
 
 // Libraries
+import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
 import { Predeploys } from "src/libraries/Predeploys.sol";
 import { Hashing } from "src/libraries/Hashing.sol";
 import { Encoding } from "src/libraries/Encoding.sol";
@@ -39,6 +40,100 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest {
         uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length));
         assertTrue(baseGas >= minGasLimit);
     }
+
+    /// @notice Test that baseGas returns at least the floor cost for calldata
+    function test_baseGas_floor_succeeds() external view {
+        // Create a message large enough that the floor cost would be higher than the execution gas
+        bytes memory largeMessage = new bytes(100_000);
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(largeMessage, 0);
+
+        // Calculate the expected floor cost
+        uint64 expectedFloorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (
+                uint64(largeMessage.length + l1CrossDomainMessenger.ENCODING_OVERHEAD())
+                    * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD()
+            );
+
+        // Verify that the result is at least the floor cost
+        assertTrue(baseGasResult >= expectedFloorCost, "baseGas should return at least the floor cost");
+    }
+
+    /// @notice Test that baseGas returns the execution gas when it's higher than the floor cost
+    function test_baseGas_executionGas_succeeds() external view {
+        // Create a small message where execution gas would be higher than floor cost
+        bytes memory smallMessage = new bytes(10);
+        uint32 highGasLimit = 1_000_000;
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(smallMessage, highGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (
+                uint64(smallMessage.length + l1CrossDomainMessenger.ENCODING_OVERHEAD())
+                    * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD()
+            );
+
+        // Calculate the expected execution gas (simplified version of what's in the contract)
+        uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
+            + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS()
+            + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
+            + (
+                (highGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR())
+                    / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
+            );
+
+        uint64 expectedExecutionGasWithOverhead = l1CrossDomainMessenger.TX_BASE_GAS() + executionGas
+            + (
+                uint64(smallMessage.length + l1CrossDomainMessenger.ENCODING_OVERHEAD())
+                    * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD()
+            );
+
+        // Verify that the result is the execution gas (which should be higher than floor cost)
+        assertTrue(
+            baseGasResult >= expectedExecutionGasWithOverhead, "baseGas should return at least the execution gas"
+        );
+        assertTrue(
+            expectedExecutionGasWithOverhead > floorCost, "Execution gas should be higher than floor cost for this test"
+        );
+    }
+
+    /// @notice Fuzz test to verify the baseGas function correctly implements the Math.max logic
+    /// @param _message The message to test
+    /// @param _minGasLimit The minimum gas limit to test
+    function testFuzz_baseGas_maxLogic_succeeds(bytes calldata _message, uint32 _minGasLimit) external view {
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(_message, _minGasLimit);
+
+        // Calculate the expected execution gas
+        uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
+            + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS()
+            + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
+            + (
+                (_minGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR())
+                    / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
+            );
+
+        uint64 executionGasWithOverhead = executionGas
+            + (
+                uint64(_message.length + l1CrossDomainMessenger.ENCODING_OVERHEAD())
+                    * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD()
+            );
+
+        // The result should be at least the maximum of the two calculations
+        uint64 expectedMinimum = uint64(
+            Math.max(
+                executionGasWithOverhead,
+                uint64(_message.length + l1CrossDomainMessenger.ENCODING_OVERHEAD())
+                    * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD()
+            )
+        );
+        expectedMinimum += l1CrossDomainMessenger.TX_BASE_GAS();
+
+        assertTrue(
+            baseGasResult >= expectedMinimum,
+            "baseGas should return at least the maximum of execution gas and floor cost"
+        );
+    }
 }
 
 /// @title ExternalRelay
diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol
index 63a0b733c0e..90ac8ae5373 100644
--- a/packages/contracts-bedrock/test/universal/Specs.t.sol
+++ b/packages/contracts-bedrock/test/universal/Specs.t.sol
@@ -116,6 +116,9 @@ contract Specification_Test is CommonTest {
         _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("RELAY_CONSTANT_OVERHEAD()") });
         _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("RELAY_GAS_CHECK_BUFFER()") });
         _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("RELAY_RESERVED_GAS()") });
+        _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("TX_BASE_GAS()") });
+        _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("FLOOR_CALLDATA_OVERHEAD()") });
+        _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("ENCODING_OVERHEAD()") });
         _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("baseGas(bytes,uint32)") });
         _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("failedMessages(bytes32)") });
         _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("initialize(address,address)") });


Reviews:
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:21:20Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:26:11Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:27:52Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:28:36Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:30:12Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:34:05Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:42:15Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:42:59Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:44:04Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:44:09Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:44:20Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:45:19Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:46:31Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:50:33Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:51:19Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:52:33Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:52:33Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:52:57Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:54:06Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:54:16Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:54:41Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:54:48Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:54:51Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:54:57Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:55:01Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:55:17Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T02:55:52Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T21:54:22Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T21:54:41Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T22:02:55Z
user: login: smartcontracts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T22:03:00Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T22:08:42Z
user: login: mds1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T22:09:51Z
user: login: mds1
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T22:11:31Z

Review comments:
diff_hunk: @@ -17,6 +17,9 @@ interface ICrossDomainMessenger {
     function RELAY_CONSTANT_OVERHEAD() external view returns (uint64);
     function RELAY_GAS_CHECK_BUFFER() external view returns (uint64);
     function RELAY_RESERVED_GAS() external view returns (uint64);
+    function TX_BASE_GAS() external view returns (uint64);
+    function FLOOR_CALLDATA_OVERHEAD() external view returns (uint64);
+    function ENCODING_OVERHEAD() external view returns (uint64);
path: packages/contracts-bedrock/interfaces/universal/ICrossDomainMessenger.sol
user: login: mds1
body: These constants should be internal with camel case getters exposed, unless we are intentionally doing this for compatibility with the other related constants?
created_at: 2025-03-04T02:21:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -115,6 +116,18 @@ abstract contract CrossDomainMessenger is
     ///         call in `relayMessage`.
     uint64 public constant RELAY_GAS_CHECK_BUFFER = 5_000;
 
+    /// @notice Base gas required for any transaction in the EVM.
+    uint64 public constant TX_BASE_GAS = 21_000;
+
+    /// @notice Floor overhead per byte of non-zero calldata in a message. Calldata floor was
+    ///         introduced in EIP-7623.
+    uint64 public constant FLOOR_CALLDATA_OVERHEAD = 40;
+
+    /// @notice Overhead bytes regardless of the size of the message. Value is specific to the V1
+    ///         message encoding scheme. 260 is an upper bound, actual overhead can be as low as
+    ///         228 bytes for an empty message.
+    uint64 public constant ENCODING_OVERHEAD = 260;
path: packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
user: login: mds1
body: From this comment I don't fully follow where the 260 comes from or what this value is for
created_at: 2025-03-04T02:26:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,6 +17,9 @@ interface ICrossDomainMessenger {
     function RELAY_CONSTANT_OVERHEAD() external view returns (uint64);
     function RELAY_GAS_CHECK_BUFFER() external view returns (uint64);
     function RELAY_RESERVED_GAS() external view returns (uint64);
+    function TX_BASE_GAS() external view returns (uint64);
+    function FLOOR_CALLDATA_OVERHEAD() external view returns (uint64);
+    function ENCODING_OVERHEAD() external view returns (uint64);
path: packages/contracts-bedrock/interfaces/universal/ICrossDomainMessenger.sol
user: login: smartcontracts
body: Yeah, doing it intentionally.
created_at: 2025-03-04T02:27:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -115,6 +116,18 @@ abstract contract CrossDomainMessenger is
     ///         call in `relayMessage`.
     uint64 public constant RELAY_GAS_CHECK_BUFFER = 5_000;
 
+    /// @notice Base gas required for any transaction in the EVM.
+    uint64 public constant TX_BASE_GAS = 21_000;
+
+    /// @notice Floor overhead per byte of non-zero calldata in a message. Calldata floor was
+    ///         introduced in EIP-7623.
+    uint64 public constant FLOOR_CALLDATA_OVERHEAD = 40;
+
+    /// @notice Overhead bytes regardless of the size of the message. Value is specific to the V1
+    ///         message encoding scheme. 260 is an upper bound, actual overhead can be as low as
+    ///         228 bytes for an empty message.
+    uint64 public constant ENCODING_OVERHEAD = 260;
path: packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
user: login: smartcontracts
body: Will clarify. 260 is basically all of the bytes when you do `abi.encodeCall(relayMessage, ...)` that are NOT the actual _message itself.
created_at: 2025-03-04T02:28:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -115,6 +116,18 @@ abstract contract CrossDomainMessenger is
     ///         call in `relayMessage`.
     uint64 public constant RELAY_GAS_CHECK_BUFFER = 5_000;
 
+    /// @notice Base gas required for any transaction in the EVM.
+    uint64 public constant TX_BASE_GAS = 21_000;
+
+    /// @notice Floor overhead per byte of non-zero calldata in a message. Calldata floor was
+    ///         introduced in EIP-7623.
+    uint64 public constant FLOOR_CALLDATA_OVERHEAD = 40;
+
+    /// @notice Overhead bytes regardless of the size of the message. Value is specific to the V1
+    ///         message encoding scheme. 260 is an upper bound, actual overhead can be as low as
+    ///         228 bytes for an empty message.
+    uint64 public constant ENCODING_OVERHEAD = 260;
path: packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
user: login: smartcontracts
body: The point of this is that you really need to be computing the calldata overhead based on the fully encoded message (`abi.encodeCall(relayMessage, ....)`) but this interface is already used all over the place so we only get the internal `_message` data and not the abi encoded thing. As a result, we want to figure out how much overhead ABI encoding adds to the `_message`.
created_at: 2025-03-04T02:30:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -340,23 +353,44 @@ abstract contract CrossDomainMessenger is
     /// @param _message     Message to compute the amount of required gas for.
     /// @param _minGasLimit Minimum desired gas limit when message goes to target.
     /// @return Amount of gas required to guarantee message receipt.
-    function baseGas(bytes calldata _message, uint32 _minGasLimit) public pure returns (uint64) {
-        return
-        // Constant overhead
-        RELAY_CONSTANT_OVERHEAD
-        // Calldata overhead
-        + (uint64(_message.length) * MIN_GAS_CALLDATA_OVERHEAD)
-        // Dynamic overhead (EIP-150)
-        + ((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) / MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR)
-        // Gas reserved for the worst-case cost of 3/5 of the `CALL` opcode's dynamic gas
-        // factors. (Conservative)
-        + RELAY_CALL_OVERHEAD
-        // Relay reserved gas (to ensure execution of `relayMessage` completes after the
-        // subcontext finishes executing) (Conservative)
-        + RELAY_RESERVED_GAS
-        // Gas reserved for the execution between the `hasMinGas` check and the `CALL`
-        // opcode. (Conservative)
-        + RELAY_GAS_CHECK_BUFFER;
+    function baseGas(bytes memory _message, uint32 _minGasLimit) public pure returns (uint64) {
+        // Base gas should really be computed on the fully encoded message but that would break the
+        // expected API, so we instead just add the encoding overhead to the message length inside
+        // of this function.
+
+        // We need a minimum amount of execution gas to ensure that the message will be received on
+        // the other side without running out of gas (stored within the failedMessages mapping).
+        // If we get beyond the hasMinGas check, then we *must* supply more than minGasLimit to
+        // the external call.
+        uint64 executionGas = uint64(
+            // Constant costs for relayMessage
+            RELAY_CONSTANT_OVERHEAD
+            // Covers dynamic parts of the CALL opcode
+            + RELAY_CALL_OVERHEAD
+            // Ensures execution of relayMessage completes after call
+            + RELAY_RESERVED_GAS
+            // Buffer between hasMinGas check and the CALL
+            + RELAY_GAS_CHECK_BUFFER
+            // Minimum gas limit, multiplied by 64/63 to account for EIP-150.
+            + ((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) / MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR)
+        );
+
+        // Total message size is the result of properly ABI encoding the call to relayMessage.
+        // Since we only get the message data and not the rest of the calldata, we use the
+        // ENCODING_OVERHEAD constant to conservatively account for the remaining bytes.
+        uint64 totalMessageSize = uint64(_message.length + ENCODING_OVERHEAD);
+
+        // Finally, replicate the transaction cost formula as defined after EIP-7623. This is
+        // mostly relevant in the L1 -> L2 case because we need to be able to cover the intrinsic
+        // cost of the message but it doesn't hurt in the L2 -> L1 case. After EIP-7623, the cost
+        // of a transaction is floored by its calldata size.
+        return TX_BASE_GAS
+            + uint64(
+                Math.max(
+                    executionGas + (totalMessageSize * MIN_GAS_CALLDATA_OVERHEAD),
+                    (totalMessageSize * FLOOR_CALLDATA_OVERHEAD)
+                )
+            );
path: packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
user: login: mds1
body: Let's also add a code comment for explaining why we don't need the `isContractCreation` portion of the formula, for completeness
created_at: 2025-03-04T02:34:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -275,6 +296,88 @@ contract L1CrossDomainMessenger_Test is CommonTest {
         );
     }
 
+    /// @notice Tests that the relayMessage function on L2 will always succeed for any potential
+    ///         message, regardless of the size of the message, the minimum gas limit, or the
+    ///         amount of gas used by the target contract.
+    function testFuzz_relayMessage_baseGasSufficient_succeeds(
+        uint24 _messageLength,
+        uint32 _minGasLimit,
+        uint32 _gasToUse
+    )
+        external
+    {
+        // Skip if this is a fork test, won't work.
+        skipIfForkTest("L2CrossDomainMessenger doesn't exist on L1 in forked test");
+
+        // Using smaller uint so the fuzzer doesn't give as many massive values that get bounded.
+        // TODO: Known issue, messages above 34k can actually OOG on the receiving side if the
+        // target uses all available gas. Can be resolved by capping data sizes on the XDM or by
+        // increasing the amount of available relay gas to ~100k. If increasing relay gas, should
+        // have the relay gas only increase when the calldata size is large to avoid disrupting
+        // in-flight L2 -> L1 messages.
+        _messageLength = uint24(bound(_messageLength, 0, 34_000));
+
+        // Need more than 500 since GasBurner requires it.
+        // It's ok to try to use more than minGasLimit since the L2CrossDomainMessenger should
+        // catch the revert and store the message hash in the failedMessages mapping.
+        _gasToUse = uint32(bound(_gasToUse, 500, type(uint32).max));
+
+        // Generate random bytes, more useful than having a _message parameter.
+        bytes memory _message = vm.randomBytes(_messageLength);
path: packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol
user: login: mds1
body: This is fine, just curious why this way is more useful?
created_at: 2025-03-04T02:42:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -275,6 +296,88 @@ contract L1CrossDomainMessenger_Test is CommonTest {
         );
     }
 
+    /// @notice Tests that the relayMessage function on L2 will always succeed for any potential
+    ///         message, regardless of the size of the message, the minimum gas limit, or the
+    ///         amount of gas used by the target contract.
+    function testFuzz_relayMessage_baseGasSufficient_succeeds(
+        uint24 _messageLength,
+        uint32 _minGasLimit,
+        uint32 _gasToUse
+    )
+        external
+    {
+        // Skip if this is a fork test, won't work.
+        skipIfForkTest("L2CrossDomainMessenger doesn't exist on L1 in forked test");
+
+        // Using smaller uint so the fuzzer doesn't give as many massive values that get bounded.
+        // TODO: Known issue, messages above 34k can actually OOG on the receiving side if the
+        // target uses all available gas. Can be resolved by capping data sizes on the XDM or by
+        // increasing the amount of available relay gas to ~100k. If increasing relay gas, should
+        // have the relay gas only increase when the calldata size is large to avoid disrupting
+        // in-flight L2 -> L1 messages.
+        _messageLength = uint24(bound(_messageLength, 0, 34_000));
+
+        // Need more than 500 since GasBurner requires it.
+        // It's ok to try to use more than minGasLimit since the L2CrossDomainMessenger should
+        // catch the revert and store the message hash in the failedMessages mapping.
+        _gasToUse = uint32(bound(_gasToUse, 500, type(uint32).max));
+
+        // Generate random bytes, more useful than having a _message parameter.
+        bytes memory _message = vm.randomBytes(_messageLength);
path: packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol
user: login: mds1
body: Easier to bound the length this way I'm guessing than to get super large calldata params, which might not even be that long?
created_at: 2025-03-04T02:42:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -275,6 +296,88 @@ contract L1CrossDomainMessenger_Test is CommonTest {
         );
     }
 
+    /// @notice Tests that the relayMessage function on L2 will always succeed for any potential
+    ///         message, regardless of the size of the message, the minimum gas limit, or the
+    ///         amount of gas used by the target contract.
+    function testFuzz_relayMessage_baseGasSufficient_succeeds(
+        uint24 _messageLength,
+        uint32 _minGasLimit,
+        uint32 _gasToUse
+    )
+        external
+    {
+        // Skip if this is a fork test, won't work.
+        skipIfForkTest("L2CrossDomainMessenger doesn't exist on L1 in forked test");
+
+        // Using smaller uint so the fuzzer doesn't give as many massive values that get bounded.
+        // TODO: Known issue, messages above 34k can actually OOG on the receiving side if the
+        // target uses all available gas. Can be resolved by capping data sizes on the XDM or by
+        // increasing the amount of available relay gas to ~100k. If increasing relay gas, should
+        // have the relay gas only increase when the calldata size is large to avoid disrupting
+        // in-flight L2 -> L1 messages.
+        _messageLength = uint24(bound(_messageLength, 0, 34_000));
+
+        // Need more than 500 since GasBurner requires it.
+        // It's ok to try to use more than minGasLimit since the L2CrossDomainMessenger should
+        // catch the revert and store the message hash in the failedMessages mapping.
+        _gasToUse = uint32(bound(_gasToUse, 500, type(uint32).max));
+
+        // Generate random bytes, more useful than having a _message parameter.
+        bytes memory _message = vm.randomBytes(_messageLength);
path: packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol
user: login: smartcontracts
body: Yeah, the super large calldata parameters end up not actually being long enough to test this.
created_at: 2025-03-04T02:44:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -340,23 +353,44 @@ abstract contract CrossDomainMessenger is
     /// @param _message     Message to compute the amount of required gas for.
     /// @param _minGasLimit Minimum desired gas limit when message goes to target.
     /// @return Amount of gas required to guarantee message receipt.
-    function baseGas(bytes calldata _message, uint32 _minGasLimit) public pure returns (uint64) {
-        return
-        // Constant overhead
-        RELAY_CONSTANT_OVERHEAD
-        // Calldata overhead
-        + (uint64(_message.length) * MIN_GAS_CALLDATA_OVERHEAD)
-        // Dynamic overhead (EIP-150)
-        + ((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) / MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR)
-        // Gas reserved for the worst-case cost of 3/5 of the `CALL` opcode's dynamic gas
-        // factors. (Conservative)
-        + RELAY_CALL_OVERHEAD
-        // Relay reserved gas (to ensure execution of `relayMessage` completes after the
-        // subcontext finishes executing) (Conservative)
-        + RELAY_RESERVED_GAS
-        // Gas reserved for the execution between the `hasMinGas` check and the `CALL`
-        // opcode. (Conservative)
-        + RELAY_GAS_CHECK_BUFFER;
+    function baseGas(bytes memory _message, uint32 _minGasLimit) public pure returns (uint64) {
+        // Base gas should really be computed on the fully encoded message but that would break the
+        // expected API, so we instead just add the encoding overhead to the message length inside
+        // of this function.
+
+        // We need a minimum amount of execution gas to ensure that the message will be received on
+        // the other side without running out of gas (stored within the failedMessages mapping).
+        // If we get beyond the hasMinGas check, then we *must* supply more than minGasLimit to
+        // the external call.
+        uint64 executionGas = uint64(
+            // Constant costs for relayMessage
+            RELAY_CONSTANT_OVERHEAD
+            // Covers dynamic parts of the CALL opcode
+            + RELAY_CALL_OVERHEAD
+            // Ensures execution of relayMessage completes after call
+            + RELAY_RESERVED_GAS
+            // Buffer between hasMinGas check and the CALL
+            + RELAY_GAS_CHECK_BUFFER
+            // Minimum gas limit, multiplied by 64/63 to account for EIP-150.
+            + ((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) / MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR)
+        );
+
+        // Total message size is the result of properly ABI encoding the call to relayMessage.
+        // Since we only get the message data and not the rest of the calldata, we use the
+        // ENCODING_OVERHEAD constant to conservatively account for the remaining bytes.
+        uint64 totalMessageSize = uint64(_message.length + ENCODING_OVERHEAD);
+
+        // Finally, replicate the transaction cost formula as defined after EIP-7623. This is
+        // mostly relevant in the L1 -> L2 case because we need to be able to cover the intrinsic
+        // cost of the message but it doesn't hurt in the L2 -> L1 case. After EIP-7623, the cost
+        // of a transaction is floored by its calldata size.
+        return TX_BASE_GAS
+            + uint64(
+                Math.max(
+                    executionGas + (totalMessageSize * MIN_GAS_CALLDATA_OVERHEAD),
+                    (totalMessageSize * FLOOR_CALLDATA_OVERHEAD)
+                )
+            );
path: packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
user: login: smartcontracts
body: Done
created_at: 2025-03-04T02:44:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -115,6 +116,18 @@ abstract contract CrossDomainMessenger is
     ///         call in `relayMessage`.
     uint64 public constant RELAY_GAS_CHECK_BUFFER = 5_000;
 
+    /// @notice Base gas required for any transaction in the EVM.
+    uint64 public constant TX_BASE_GAS = 21_000;
+
+    /// @notice Floor overhead per byte of non-zero calldata in a message. Calldata floor was
+    ///         introduced in EIP-7623.
+    uint64 public constant FLOOR_CALLDATA_OVERHEAD = 40;
+
+    /// @notice Overhead bytes regardless of the size of the message. Value is specific to the V1
+    ///         message encoding scheme. 260 is an upper bound, actual overhead can be as low as
+    ///         228 bytes for an empty message.
+    uint64 public constant ENCODING_OVERHEAD = 260;
path: packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
user: login: smartcontracts
body: Updated the message to clarify this a bit
created_at: 2025-03-04T02:44:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -275,6 +296,92 @@ contract L1CrossDomainMessenger_Test is CommonTest {
         );
     }
 
+    /// @notice Tests that the relayMessage function on L2 will always succeed for any potential
+    ///         message, regardless of the size of the message, the minimum gas limit, or the
+    ///         amount of gas used by the target contract.
+    function testFuzz_relayMessage_baseGasSufficient_succeeds(
+        uint24 _messageLength,
+        uint32 _minGasLimit,
+        uint32 _gasToUse
+    )
+        external
+    {
+        // TODO(#14609): Update this test to use default.isolate = true once a new stable Foundry
+        // release is available that includes #9904. That will allow us to use this test to check
+        // for changes to the EVM itself that might cause our gas formula to be incorrect.
+
+        // Skip if this is a fork test, won't work.
+        skipIfForkTest("L2CrossDomainMessenger doesn't exist on L1 in forked test");
+
+        // Using smaller uint so the fuzzer doesn't give as many massive values that get bounded.
+        // TODO: Known issue, messages above 34k can actually OOG on the receiving side if the
+        // target uses all available gas. Can be resolved by capping data sizes on the XDM or by
+        // increasing the amount of available relay gas to ~100k. If increasing relay gas, should
+        // have the relay gas only increase when the calldata size is large to avoid disrupting
+        // in-flight L2 -> L1 messages.
+        _messageLength = uint24(bound(_messageLength, 0, 34_000));
+
+        // Need more than 500 since GasBurner requires it.
+        // It's ok to try to use more than minGasLimit since the L2CrossDomainMessenger should
+        // catch the revert and store the message hash in the failedMessages mapping.
+        _gasToUse = uint32(bound(_gasToUse, 500, type(uint32).max));
+
+        // Generate random bytes, more useful than having a _message parameter.
+        bytes memory _message = vm.randomBytes(_messageLength);
+
+        // Compute the base gas.
+        // Base gas should really be computed on the fully encoded message but that would break the
+        // expected API, so we instead just add the encoding overhead to the message length inside
+        // of the baseGas function.
+        uint64 baseGas = l1CrossDomainMessenger.baseGas(_message, _minGasLimit);
+
+        // Deploy a gas burner.
+        address target = address(new GasBurner(_gasToUse));
+
+        // Encode the message.
+        bytes memory encoded = Encoding.encodeCrossDomainMessage(
+            Encoding.encodeVersionedNonce(0, 1), // nonce
+            alice, // Sender doesn't matter
+            target,
+            0, // Value doesn't matter
+            _minGasLimit,
+            _message
+        );
+
+        // Count the number of non-zero bytes in the message.
+        uint256 zeroBytesInCalldata = 0;
+        uint256 nonzeroBytesInCalldata = 0;
+        for (uint256 i = 0; i < encoded.length; i++) {
+            if (encoded[i] != bytes1(0)) {
+                nonzeroBytesInCalldata++;
+            } else {
+                zeroBytesInCalldata++;
+            }
+        }
+
+        // Base gas must always be sufficient to cover the floor cost from EIP-7623.
+        assertGt(baseGas, 21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 10));
path: packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol
user: login: mds1
body: nit, can this really be `assertGte` for greater than or equal?
created_at: 2025-03-04T02:45:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -275,6 +296,92 @@ contract L1CrossDomainMessenger_Test is CommonTest {
         );
     }
 
+    /// @notice Tests that the relayMessage function on L2 will always succeed for any potential
+    ///         message, regardless of the size of the message, the minimum gas limit, or the
+    ///         amount of gas used by the target contract.
+    function testFuzz_relayMessage_baseGasSufficient_succeeds(
+        uint24 _messageLength,
+        uint32 _minGasLimit,
+        uint32 _gasToUse
+    )
+        external
+    {
+        // TODO(#14609): Update this test to use default.isolate = true once a new stable Foundry
+        // release is available that includes #9904. That will allow us to use this test to check
+        // for changes to the EVM itself that might cause our gas formula to be incorrect.
+
+        // Skip if this is a fork test, won't work.
+        skipIfForkTest("L2CrossDomainMessenger doesn't exist on L1 in forked test");
+
+        // Using smaller uint so the fuzzer doesn't give as many massive values that get bounded.
+        // TODO: Known issue, messages above 34k can actually OOG on the receiving side if the
+        // target uses all available gas. Can be resolved by capping data sizes on the XDM or by
+        // increasing the amount of available relay gas to ~100k. If increasing relay gas, should
+        // have the relay gas only increase when the calldata size is large to avoid disrupting
+        // in-flight L2 -> L1 messages.
+        _messageLength = uint24(bound(_messageLength, 0, 34_000));
+
+        // Need more than 500 since GasBurner requires it.
+        // It's ok to try to use more than minGasLimit since the L2CrossDomainMessenger should
+        // catch the revert and store the message hash in the failedMessages mapping.
+        _gasToUse = uint32(bound(_gasToUse, 500, type(uint32).max));
+
+        // Generate random bytes, more useful than having a _message parameter.
+        bytes memory _message = vm.randomBytes(_messageLength);
+
+        // Compute the base gas.
+        // Base gas should really be computed on the fully encoded message but that would break the
+        // expected API, so we instead just add the encoding overhead to the message length inside
+        // of the baseGas function.
+        uint64 baseGas = l1CrossDomainMessenger.baseGas(_message, _minGasLimit);
+
+        // Deploy a gas burner.
+        address target = address(new GasBurner(_gasToUse));
+
+        // Encode the message.
+        bytes memory encoded = Encoding.encodeCrossDomainMessage(
+            Encoding.encodeVersionedNonce(0, 1), // nonce
+            alice, // Sender doesn't matter
+            target,
+            0, // Value doesn't matter
+            _minGasLimit,
+            _message
+        );
+
+        // Count the number of non-zero bytes in the message.
+        uint256 zeroBytesInCalldata = 0;
+        uint256 nonzeroBytesInCalldata = 0;
+        for (uint256 i = 0; i < encoded.length; i++) {
+            if (encoded[i] != bytes1(0)) {
+                nonzeroBytesInCalldata++;
+            } else {
+                zeroBytesInCalldata++;
+            }
+        }
+
+        // Base gas must always be sufficient to cover the floor cost from EIP-7623.
+        assertGt(baseGas, 21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 10));
+
+        // Actual gas on L2 will be the base gas minus the intrinsic gas cost.
+        uint256 gasSupplied = baseGas - (21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 4));
path: packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol
user: login: mds1
body: Why is this not
```suggestion
        uint256 gasSupplied = baseGas - (21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 10));
```
created_at: 2025-03-04T02:46:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -275,6 +296,92 @@ contract L1CrossDomainMessenger_Test is CommonTest {
         );
     }
 
+    /// @notice Tests that the relayMessage function on L2 will always succeed for any potential
+    ///         message, regardless of the size of the message, the minimum gas limit, or the
+    ///         amount of gas used by the target contract.
+    function testFuzz_relayMessage_baseGasSufficient_succeeds(
+        uint24 _messageLength,
+        uint32 _minGasLimit,
+        uint32 _gasToUse
+    )
+        external
+    {
+        // TODO(#14609): Update this test to use default.isolate = true once a new stable Foundry
+        // release is available that includes #9904. That will allow us to use this test to check
+        // for changes to the EVM itself that might cause our gas formula to be incorrect.
+
+        // Skip if this is a fork test, won't work.
+        skipIfForkTest("L2CrossDomainMessenger doesn't exist on L1 in forked test");
+
+        // Using smaller uint so the fuzzer doesn't give as many massive values that get bounded.
+        // TODO: Known issue, messages above 34k can actually OOG on the receiving side if the
+        // target uses all available gas. Can be resolved by capping data sizes on the XDM or by
+        // increasing the amount of available relay gas to ~100k. If increasing relay gas, should
+        // have the relay gas only increase when the calldata size is large to avoid disrupting
+        // in-flight L2 -> L1 messages.
+        _messageLength = uint24(bound(_messageLength, 0, 34_000));
+
+        // Need more than 500 since GasBurner requires it.
+        // It's ok to try to use more than minGasLimit since the L2CrossDomainMessenger should
+        // catch the revert and store the message hash in the failedMessages mapping.
+        _gasToUse = uint32(bound(_gasToUse, 500, type(uint32).max));
+
+        // Generate random bytes, more useful than having a _message parameter.
+        bytes memory _message = vm.randomBytes(_messageLength);
+
+        // Compute the base gas.
+        // Base gas should really be computed on the fully encoded message but that would break the
+        // expected API, so we instead just add the encoding overhead to the message length inside
+        // of the baseGas function.
+        uint64 baseGas = l1CrossDomainMessenger.baseGas(_message, _minGasLimit);
+
+        // Deploy a gas burner.
+        address target = address(new GasBurner(_gasToUse));
+
+        // Encode the message.
+        bytes memory encoded = Encoding.encodeCrossDomainMessage(
+            Encoding.encodeVersionedNonce(0, 1), // nonce
+            alice, // Sender doesn't matter
+            target,
+            0, // Value doesn't matter
+            _minGasLimit,
+            _message
+        );
+
+        // Count the number of non-zero bytes in the message.
+        uint256 zeroBytesInCalldata = 0;
+        uint256 nonzeroBytesInCalldata = 0;
+        for (uint256 i = 0; i < encoded.length; i++) {
+            if (encoded[i] != bytes1(0)) {
+                nonzeroBytesInCalldata++;
+            } else {
+                zeroBytesInCalldata++;
+            }
+        }
+
+        // Base gas must always be sufficient to cover the floor cost from EIP-7623.
+        assertGt(baseGas, 21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 10));
path: packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol
user: login: smartcontracts
body: Yes, I'll probably just leave as-is, doesn't really hurt.
created_at: 2025-03-04T02:50:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,6 +3,7 @@ pragma solidity 0.8.15;
 
 // Libraries
 import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
path: packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
user: login: mds1
body: Should we just add the max function locally to avoid needing this dep here?
```solidity
function max(uint256 a, uint256 b) internal pure returns (uint256) {
    return a >= b ? a : b;
}
```
created_at: 2025-03-04T02:51:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,6 +40,90 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest {
         uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length));
         assertTrue(baseGas >= minGasLimit);
     }
+
+    /// @notice Test that baseGas returns at least the floor cost for calldata
path: packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
user: login: mds1
body: I need to more carefully review the new tests in this file tomorrow
created_at: 2025-03-04T02:52:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -275,6 +296,92 @@ contract L1CrossDomainMessenger_Test is CommonTest {
         );
     }
 
+    /// @notice Tests that the relayMessage function on L2 will always succeed for any potential
+    ///         message, regardless of the size of the message, the minimum gas limit, or the
+    ///         amount of gas used by the target contract.
+    function testFuzz_relayMessage_baseGasSufficient_succeeds(
+        uint24 _messageLength,
+        uint32 _minGasLimit,
+        uint32 _gasToUse
+    )
+        external
+    {
+        // TODO(#14609): Update this test to use default.isolate = true once a new stable Foundry
+        // release is available that includes #9904. That will allow us to use this test to check
+        // for changes to the EVM itself that might cause our gas formula to be incorrect.
+
+        // Skip if this is a fork test, won't work.
+        skipIfForkTest("L2CrossDomainMessenger doesn't exist on L1 in forked test");
+
+        // Using smaller uint so the fuzzer doesn't give as many massive values that get bounded.
+        // TODO: Known issue, messages above 34k can actually OOG on the receiving side if the
+        // target uses all available gas. Can be resolved by capping data sizes on the XDM or by
+        // increasing the amount of available relay gas to ~100k. If increasing relay gas, should
+        // have the relay gas only increase when the calldata size is large to avoid disrupting
+        // in-flight L2 -> L1 messages.
+        _messageLength = uint24(bound(_messageLength, 0, 34_000));
+
+        // Need more than 500 since GasBurner requires it.
+        // It's ok to try to use more than minGasLimit since the L2CrossDomainMessenger should
+        // catch the revert and store the message hash in the failedMessages mapping.
+        _gasToUse = uint32(bound(_gasToUse, 500, type(uint32).max));
+
+        // Generate random bytes, more useful than having a _message parameter.
+        bytes memory _message = vm.randomBytes(_messageLength);
+
+        // Compute the base gas.
+        // Base gas should really be computed on the fully encoded message but that would break the
+        // expected API, so we instead just add the encoding overhead to the message length inside
+        // of the baseGas function.
+        uint64 baseGas = l1CrossDomainMessenger.baseGas(_message, _minGasLimit);
+
+        // Deploy a gas burner.
+        address target = address(new GasBurner(_gasToUse));
+
+        // Encode the message.
+        bytes memory encoded = Encoding.encodeCrossDomainMessage(
+            Encoding.encodeVersionedNonce(0, 1), // nonce
+            alice, // Sender doesn't matter
+            target,
+            0, // Value doesn't matter
+            _minGasLimit,
+            _message
+        );
+
+        // Count the number of non-zero bytes in the message.
+        uint256 zeroBytesInCalldata = 0;
+        uint256 nonzeroBytesInCalldata = 0;
+        for (uint256 i = 0; i < encoded.length; i++) {
+            if (encoded[i] != bytes1(0)) {
+                nonzeroBytesInCalldata++;
+            } else {
+                zeroBytesInCalldata++;
+            }
+        }
+
+        // Base gas must always be sufficient to cover the floor cost from EIP-7623.
+        assertGt(baseGas, 21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 10));
+
+        // Actual gas on L2 will be the base gas minus the intrinsic gas cost.
+        uint256 gasSupplied = baseGas - (21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 4));
path: packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol
user: login: smartcontracts
body: Yeah it's confusing. You must have at least the floor cost available (which uses the `10` value) but the rest of the logic actually proceeds as it used to. So your execution gas is cut down by 21000 + the calldata cost at `4` gas per token. After execution you check how much you spent on execution gas. If that amount is less than the floor, you get charged the floor.
created_at: 2025-03-04T02:52:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,6 +3,7 @@ pragma solidity 0.8.15;
 
 // Libraries
 import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
path: packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
user: login: smartcontracts
body: Eh, I guess. It seemed easier to include the dep since we have it elsewhere already and it's well tested. 
created_at: 2025-03-04T02:52:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,6 +40,90 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest {
         uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length));
         assertTrue(baseGas >= minGasLimit);
     }
+
+    /// @notice Test that baseGas returns at least the floor cost for calldata
+    function test_baseGas_floor_succeeds() external view {
+        // Create a message large enough that the floor cost would be higher than the execution gas
+        bytes memory largeMessage = new bytes(100_000);
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(largeMessage, 0);
+
+        // Calculate the expected floor cost
+        uint64 expectedFloorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(largeMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
path: packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
user: login: smartcontracts
body: Must update this to include the encoding overhead
created_at: 2025-03-04T02:54:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,6 +40,90 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest {
         uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length));
         assertTrue(baseGas >= minGasLimit);
     }
+
+    /// @notice Test that baseGas returns at least the floor cost for calldata
+    function test_baseGas_floor_succeeds() external view {
+        // Create a message large enough that the floor cost would be higher than the execution gas
+        bytes memory largeMessage = new bytes(100_000);
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(largeMessage, 0);
+
+        // Calculate the expected floor cost
+        uint64 expectedFloorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(largeMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Verify that the result is at least the floor cost
+        assertTrue(baseGasResult >= expectedFloorCost, "baseGas should return at least the floor cost");
+    }
+
+    /// @notice Test that baseGas returns the execution gas when it's higher than the floor cost
+    function test_baseGas_executionGas_succeeds() external view {
+        // Create a small message where execution gas would be higher than floor cost
+        bytes memory smallMessage = new bytes(10);
+        uint32 highGasLimit = 1_000_000;
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(smallMessage, highGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
path: packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
user: login: smartcontracts
body: Must update to include the encoding overhead
created_at: 2025-03-04T02:54:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,6 +40,90 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest {
         uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length));
         assertTrue(baseGas >= minGasLimit);
     }
+
+    /// @notice Test that baseGas returns at least the floor cost for calldata
+    function test_baseGas_floor_succeeds() external view {
+        // Create a message large enough that the floor cost would be higher than the execution gas
+        bytes memory largeMessage = new bytes(100_000);
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(largeMessage, 0);
+
+        // Calculate the expected floor cost
+        uint64 expectedFloorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(largeMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Verify that the result is at least the floor cost
+        assertTrue(baseGasResult >= expectedFloorCost, "baseGas should return at least the floor cost");
+    }
+
+    /// @notice Test that baseGas returns the execution gas when it's higher than the floor cost
+    function test_baseGas_executionGas_succeeds() external view {
+        // Create a small message where execution gas would be higher than floor cost
+        bytes memory smallMessage = new bytes(10);
+        uint32 highGasLimit = 1_000_000;
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(smallMessage, highGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Calculate the expected execution gas (simplified version of what's in the contract)
+        uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
+            + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS()
+            + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
+            + (
+                (highGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR())
+                    / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
+            );
+
+        uint64 expectedExecutionGasWithOverhead = l1CrossDomainMessenger.TX_BASE_GAS() + executionGas
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD());
path: packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
user: login: smartcontracts
body: Update to include encoding overhead
created_at: 2025-03-04T02:54:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -275,6 +296,92 @@ contract L1CrossDomainMessenger_Test is CommonTest {
         );
     }
 
+    /// @notice Tests that the relayMessage function on L2 will always succeed for any potential
+    ///         message, regardless of the size of the message, the minimum gas limit, or the
+    ///         amount of gas used by the target contract.
+    function testFuzz_relayMessage_baseGasSufficient_succeeds(
+        uint24 _messageLength,
+        uint32 _minGasLimit,
+        uint32 _gasToUse
+    )
+        external
+    {
+        // TODO(#14609): Update this test to use default.isolate = true once a new stable Foundry
+        // release is available that includes #9904. That will allow us to use this test to check
+        // for changes to the EVM itself that might cause our gas formula to be incorrect.
+
+        // Skip if this is a fork test, won't work.
+        skipIfForkTest("L2CrossDomainMessenger doesn't exist on L1 in forked test");
+
+        // Using smaller uint so the fuzzer doesn't give as many massive values that get bounded.
+        // TODO: Known issue, messages above 34k can actually OOG on the receiving side if the
+        // target uses all available gas. Can be resolved by capping data sizes on the XDM or by
+        // increasing the amount of available relay gas to ~100k. If increasing relay gas, should
+        // have the relay gas only increase when the calldata size is large to avoid disrupting
+        // in-flight L2 -> L1 messages.
+        _messageLength = uint24(bound(_messageLength, 0, 34_000));
+
+        // Need more than 500 since GasBurner requires it.
+        // It's ok to try to use more than minGasLimit since the L2CrossDomainMessenger should
+        // catch the revert and store the message hash in the failedMessages mapping.
+        _gasToUse = uint32(bound(_gasToUse, 500, type(uint32).max));
+
+        // Generate random bytes, more useful than having a _message parameter.
+        bytes memory _message = vm.randomBytes(_messageLength);
+
+        // Compute the base gas.
+        // Base gas should really be computed on the fully encoded message but that would break the
+        // expected API, so we instead just add the encoding overhead to the message length inside
+        // of the baseGas function.
+        uint64 baseGas = l1CrossDomainMessenger.baseGas(_message, _minGasLimit);
+
+        // Deploy a gas burner.
+        address target = address(new GasBurner(_gasToUse));
+
+        // Encode the message.
+        bytes memory encoded = Encoding.encodeCrossDomainMessage(
+            Encoding.encodeVersionedNonce(0, 1), // nonce
+            alice, // Sender doesn't matter
+            target,
+            0, // Value doesn't matter
+            _minGasLimit,
+            _message
+        );
+
+        // Count the number of non-zero bytes in the message.
+        uint256 zeroBytesInCalldata = 0;
+        uint256 nonzeroBytesInCalldata = 0;
+        for (uint256 i = 0; i < encoded.length; i++) {
+            if (encoded[i] != bytes1(0)) {
+                nonzeroBytesInCalldata++;
+            } else {
+                zeroBytesInCalldata++;
+            }
+        }
+
+        // Base gas must always be sufficient to cover the floor cost from EIP-7623.
+        assertGt(baseGas, 21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 10));
+
+        // Actual gas on L2 will be the base gas minus the intrinsic gas cost.
+        uint256 gasSupplied = baseGas - (21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 4));
path: packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol
user: login: mds1
body: Whew, ok, maybe worth another code comment lol. I do appreciate your heavy code commenting in this PR
created_at: 2025-03-04T02:54:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,6 +40,90 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest {
         uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length));
         assertTrue(baseGas >= minGasLimit);
     }
+
+    /// @notice Test that baseGas returns at least the floor cost for calldata
+    function test_baseGas_floor_succeeds() external view {
+        // Create a message large enough that the floor cost would be higher than the execution gas
+        bytes memory largeMessage = new bytes(100_000);
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(largeMessage, 0);
+
+        // Calculate the expected floor cost
+        uint64 expectedFloorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(largeMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Verify that the result is at least the floor cost
+        assertTrue(baseGasResult >= expectedFloorCost, "baseGas should return at least the floor cost");
+    }
+
+    /// @notice Test that baseGas returns the execution gas when it's higher than the floor cost
+    function test_baseGas_executionGas_succeeds() external view {
+        // Create a small message where execution gas would be higher than floor cost
+        bytes memory smallMessage = new bytes(10);
+        uint32 highGasLimit = 1_000_000;
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(smallMessage, highGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Calculate the expected execution gas (simplified version of what's in the contract)
+        uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
+            + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS()
+            + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
+            + (
+                (highGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR())
+                    / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
+            );
+
+        uint64 expectedExecutionGasWithOverhead = l1CrossDomainMessenger.TX_BASE_GAS() + executionGas
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD());
+
+        // Verify that the result is the execution gas (which should be higher than floor cost)
+        assertTrue(
+            baseGasResult >= expectedExecutionGasWithOverhead, "baseGas should return at least the execution gas"
+        );
+        assertTrue(
+            expectedExecutionGasWithOverhead > floorCost, "Execution gas should be higher than floor cost for this test"
+        );
+    }
+
+    /// @notice Fuzz test to verify the baseGas function correctly implements the Math.max logic
+    /// @param _message The message to test
+    /// @param _minGasLimit The minimum gas limit to test
+    function testFuzz_baseGas_maxLogic_succeeds(bytes calldata _message, uint32 _minGasLimit) external view {
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(_message, _minGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(_message.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
path: packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
user: login: smartcontracts
body: Update to include encoding overhead
created_at: 2025-03-04T02:54:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,6 +40,90 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest {
         uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length));
         assertTrue(baseGas >= minGasLimit);
     }
+
+    /// @notice Test that baseGas returns at least the floor cost for calldata
+    function test_baseGas_floor_succeeds() external view {
+        // Create a message large enough that the floor cost would be higher than the execution gas
+        bytes memory largeMessage = new bytes(100_000);
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(largeMessage, 0);
+
+        // Calculate the expected floor cost
+        uint64 expectedFloorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(largeMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Verify that the result is at least the floor cost
+        assertTrue(baseGasResult >= expectedFloorCost, "baseGas should return at least the floor cost");
+    }
+
+    /// @notice Test that baseGas returns the execution gas when it's higher than the floor cost
+    function test_baseGas_executionGas_succeeds() external view {
+        // Create a small message where execution gas would be higher than floor cost
+        bytes memory smallMessage = new bytes(10);
+        uint32 highGasLimit = 1_000_000;
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(smallMessage, highGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Calculate the expected execution gas (simplified version of what's in the contract)
+        uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
+            + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS()
+            + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
+            + (
+                (highGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR())
+                    / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
+            );
+
+        uint64 expectedExecutionGasWithOverhead = l1CrossDomainMessenger.TX_BASE_GAS() + executionGas
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD());
+
+        // Verify that the result is the execution gas (which should be higher than floor cost)
+        assertTrue(
+            baseGasResult >= expectedExecutionGasWithOverhead, "baseGas should return at least the execution gas"
+        );
+        assertTrue(
+            expectedExecutionGasWithOverhead > floorCost, "Execution gas should be higher than floor cost for this test"
+        );
+    }
+
+    /// @notice Fuzz test to verify the baseGas function correctly implements the Math.max logic
+    /// @param _message The message to test
+    /// @param _minGasLimit The minimum gas limit to test
+    function testFuzz_baseGas_maxLogic_succeeds(bytes calldata _message, uint32 _minGasLimit) external view {
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(_message, _minGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(_message.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Calculate the expected execution gas
+        uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
+            + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS()
+            + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
+            + (
+                (_minGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR())
+                    / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
+            );
+
+        uint64 executionGasWithOverhead =
+            executionGas + (uint64(_message.length) * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD());
path: packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
user: login: smartcontracts
body: Update to include encoding overhead
created_at: 2025-03-04T02:54:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,6 +40,90 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest {
         uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length));
         assertTrue(baseGas >= minGasLimit);
     }
+
+    /// @notice Test that baseGas returns at least the floor cost for calldata
+    function test_baseGas_floor_succeeds() external view {
+        // Create a message large enough that the floor cost would be higher than the execution gas
+        bytes memory largeMessage = new bytes(100_000);
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(largeMessage, 0);
+
+        // Calculate the expected floor cost
+        uint64 expectedFloorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(largeMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Verify that the result is at least the floor cost
+        assertTrue(baseGasResult >= expectedFloorCost, "baseGas should return at least the floor cost");
+    }
+
+    /// @notice Test that baseGas returns the execution gas when it's higher than the floor cost
+    function test_baseGas_executionGas_succeeds() external view {
+        // Create a small message where execution gas would be higher than floor cost
+        bytes memory smallMessage = new bytes(10);
+        uint32 highGasLimit = 1_000_000;
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(smallMessage, highGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Calculate the expected execution gas (simplified version of what's in the contract)
+        uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
+            + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS()
+            + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
+            + (
+                (highGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR())
+                    / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
+            );
+
+        uint64 expectedExecutionGasWithOverhead = l1CrossDomainMessenger.TX_BASE_GAS() + executionGas
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD());
+
+        // Verify that the result is the execution gas (which should be higher than floor cost)
+        assertTrue(
+            baseGasResult >= expectedExecutionGasWithOverhead, "baseGas should return at least the execution gas"
+        );
+        assertTrue(
+            expectedExecutionGasWithOverhead > floorCost, "Execution gas should be higher than floor cost for this test"
+        );
+    }
+
+    /// @notice Fuzz test to verify the baseGas function correctly implements the Math.max logic
+    /// @param _message The message to test
+    /// @param _minGasLimit The minimum gas limit to test
+    function testFuzz_baseGas_maxLogic_succeeds(bytes calldata _message, uint32 _minGasLimit) external view {
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(_message, _minGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(_message.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Calculate the expected execution gas
+        uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
+            + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS()
+            + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
+            + (
+                (_minGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR())
+                    / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
+            );
+
+        uint64 executionGasWithOverhead =
+            executionGas + (uint64(_message.length) * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD());
+
+        // The result should be at least the maximum of the two calculations
+        uint64 expectedMinimum = uint64(
+            Math.max(
+                executionGasWithOverhead, uint64(_message.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD()
path: packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
user: login: smartcontracts
body: Update to include encoding overhead
created_at: 2025-03-04T02:55:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,6 +3,7 @@ pragma solidity 0.8.15;
 
 // Libraries
 import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
path: packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
user: login: mds1
body: OZ is generally a heavy dep but in this case it does seem lightweight so I'm indifferent
created_at: 2025-03-04T02:55:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,6 +40,90 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest {
         uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length));
         assertTrue(baseGas >= minGasLimit);
     }
+
+    /// @notice Test that baseGas returns at least the floor cost for calldata
+    function test_baseGas_floor_succeeds() external view {
+        // Create a message large enough that the floor cost would be higher than the execution gas
+        bytes memory largeMessage = new bytes(100_000);
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(largeMessage, 0);
+
+        // Calculate the expected floor cost
+        uint64 expectedFloorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(largeMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Verify that the result is at least the floor cost
+        assertTrue(baseGasResult >= expectedFloorCost, "baseGas should return at least the floor cost");
+    }
+
+    /// @notice Test that baseGas returns the execution gas when it's higher than the floor cost
+    function test_baseGas_executionGas_succeeds() external view {
+        // Create a small message where execution gas would be higher than floor cost
+        bytes memory smallMessage = new bytes(10);
+        uint32 highGasLimit = 1_000_000;
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(smallMessage, highGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Calculate the expected execution gas (simplified version of what's in the contract)
+        uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
+            + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS()
+            + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
+            + (
+                (highGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR())
+                    / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
+            );
+
+        uint64 expectedExecutionGasWithOverhead = l1CrossDomainMessenger.TX_BASE_GAS() + executionGas
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD());
+
+        // Verify that the result is the execution gas (which should be higher than floor cost)
+        assertTrue(
+            baseGasResult >= expectedExecutionGasWithOverhead, "baseGas should return at least the execution gas"
+        );
+        assertTrue(
+            expectedExecutionGasWithOverhead > floorCost, "Execution gas should be higher than floor cost for this test"
+        );
+    }
+
+    /// @notice Fuzz test to verify the baseGas function correctly implements the Math.max logic
+    /// @param _message The message to test
+    /// @param _minGasLimit The minimum gas limit to test
+    function testFuzz_baseGas_maxLogic_succeeds(bytes calldata _message, uint32 _minGasLimit) external view {
path: packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
user: login: smartcontracts
body: At this point we're basically just re-implementing the function. I don't know how useful tests like this really are. `testFuzz_relayMessage_baseGasSufficient_succeeds` is the only one that really matters.
created_at: 2025-03-04T02:55:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,8 +28,8 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver {
     ISuperchainConfig public superchainConfig;
 
     /// @notice Semantic version.
-    /// @custom:semver 2.3.1
-    string public constant version = "2.3.1";
+    /// @custom:semver 2.4.0
+    string public constant version = "2.4.0";
 
path: packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol
user: login: mds1
body: Why did the L1/L2ERC721Bridge's change? I don't see a change to the base ERC721Bridge
created_at: 2025-03-04T21:54:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,8 +74,8 @@ contract L1StandardBridge is StandardBridge, ISemver {
     );
 
     /// @notice Semantic version.
-    /// @custom:semver 2.2.2
-    string public constant version = "2.2.2";
+    /// @custom:semver 2.3.0
+    string public constant version = "2.3.0";
path: packages/contracts-bedrock/src/L1/L1StandardBridge.sol
user: login: mds1
body: same question as above
created_at: 2025-03-04T21:54:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,8 +28,8 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver {
     ISuperchainConfig public superchainConfig;
 
     /// @notice Semantic version.
-    /// @custom:semver 2.3.1
-    string public constant version = "2.3.1";
+    /// @custom:semver 2.4.0
+    string public constant version = "2.4.0";
 
path: packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol
user: login: smartcontracts
body: Ah because EOA.sol changed and they both use it
created_at: 2025-03-04T22:02:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,8 +74,8 @@ contract L1StandardBridge is StandardBridge, ISemver {
     );
 
     /// @notice Semantic version.
-    /// @custom:semver 2.2.2
-    string public constant version = "2.2.2";
+    /// @custom:semver 2.3.0
+    string public constant version = "2.3.0";
path: packages/contracts-bedrock/src/L1/L1StandardBridge.sol
user: login: smartcontracts
body: Same as above
created_at: 2025-03-04T22:03:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,6 +40,90 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest {
         uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length));
         assertTrue(baseGas >= minGasLimit);
     }
+
+    /// @notice Test that baseGas returns at least the floor cost for calldata
+    function test_baseGas_floor_succeeds() external view {
+        // Create a message large enough that the floor cost would be higher than the execution gas
+        bytes memory largeMessage = new bytes(100_000);
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(largeMessage, 0);
+
+        // Calculate the expected floor cost
+        uint64 expectedFloorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(largeMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Verify that the result is at least the floor cost
+        assertTrue(baseGasResult >= expectedFloorCost, "baseGas should return at least the floor cost");
+    }
+
+    /// @notice Test that baseGas returns the execution gas when it's higher than the floor cost
+    function test_baseGas_executionGas_succeeds() external view {
+        // Create a small message where execution gas would be higher than floor cost
+        bytes memory smallMessage = new bytes(10);
+        uint32 highGasLimit = 1_000_000;
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(smallMessage, highGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Calculate the expected execution gas (simplified version of what's in the contract)
+        uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
+            + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS()
+            + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
+            + (
+                (highGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR())
+                    / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
+            );
+
+        uint64 expectedExecutionGasWithOverhead = l1CrossDomainMessenger.TX_BASE_GAS() + executionGas
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD());
+
+        // Verify that the result is the execution gas (which should be higher than floor cost)
+        assertTrue(
+            baseGasResult >= expectedExecutionGasWithOverhead, "baseGas should return at least the execution gas"
+        );
+        assertTrue(
+            expectedExecutionGasWithOverhead > floorCost, "Execution gas should be higher than floor cost for this test"
+        );
+    }
+
+    /// @notice Fuzz test to verify the baseGas function correctly implements the Math.max logic
+    /// @param _message The message to test
+    /// @param _minGasLimit The minimum gas limit to test
+    function testFuzz_baseGas_maxLogic_succeeds(bytes calldata _message, uint32 _minGasLimit) external view {
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(_message, _minGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(_message.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
path: packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
user: login: mds1
body: This `floorCost` variable is unused
created_at: 2025-03-04T22:08:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,6 +40,90 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest {
         uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length));
         assertTrue(baseGas >= minGasLimit);
     }
+
+    /// @notice Test that baseGas returns at least the floor cost for calldata
+    function test_baseGas_floor_succeeds() external view {
+        // Create a message large enough that the floor cost would be higher than the execution gas
+        bytes memory largeMessage = new bytes(100_000);
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(largeMessage, 0);
+
+        // Calculate the expected floor cost
+        uint64 expectedFloorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(largeMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Verify that the result is at least the floor cost
+        assertTrue(baseGasResult >= expectedFloorCost, "baseGas should return at least the floor cost");
+    }
+
+    /// @notice Test that baseGas returns the execution gas when it's higher than the floor cost
+    function test_baseGas_executionGas_succeeds() external view {
+        // Create a small message where execution gas would be higher than floor cost
+        bytes memory smallMessage = new bytes(10);
+        uint32 highGasLimit = 1_000_000;
+
+        uint64 baseGasResult = l1CrossDomainMessenger.baseGas(smallMessage, highGasLimit);
+
+        // Calculate the expected floor cost
+        uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS()
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD());
+
+        // Calculate the expected execution gas (simplified version of what's in the contract)
+        uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
+            + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS()
+            + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
+            + (
+                (highGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR())
+                    / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
+            );
+
+        uint64 expectedExecutionGasWithOverhead = l1CrossDomainMessenger.TX_BASE_GAS() + executionGas
+            + (uint64(smallMessage.length) * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD());
+
+        // Verify that the result is the execution gas (which should be higher than floor cost)
+        assertTrue(
+            baseGasResult >= expectedExecutionGasWithOverhead, "baseGas should return at least the execution gas"
+        );
+        assertTrue(
+            expectedExecutionGasWithOverhead > floorCost, "Execution gas should be higher than floor cost for this test"
+        );
+    }
+
+    /// @notice Fuzz test to verify the baseGas function correctly implements the Math.max logic
+    /// @param _message The message to test
+    /// @param _minGasLimit The minimum gas limit to test
+    function testFuzz_baseGas_maxLogic_succeeds(bytes calldata _message, uint32 _minGasLimit) external view {
path: packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol
user: login: mds1
body: may as well just keep it, but agreed that it's not very useful since it just reimplements the function
created_at: 2025-03-04T22:09:50Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-03-04T02:19:43Z
body: ## [Codecov](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) Report
Attention: Patch coverage is `93.75000%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 45.56%. Comparing base [(`2123532`)](https://app.codecov.io/gh/ethereum-optimism/optimism/commit/2123532c3be498199db3bc73c9b518a15ff57355?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) to head [(`88419ad`)](https://app.codecov.io/gh/ethereum-optimism/optimism/commit/88419adbf689336913a3db7d4c3691b2b0102e75?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism).
> Report is 2 commits behind head on develop.

| [Files with missing lines](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | Patch % | Lines |
|---|---|---|
| [...ntracts-bedrock/src/L2/L2StandardBridgeInterop.sol](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree&filepath=packages%2Fcontracts-bedrock%2Fsrc%2FL2%2FL2StandardBridgeInterop.sol&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#diff-cGFja2FnZXMvY29udHJhY3RzLWJlZHJvY2svc3JjL0wyL0wyU3RhbmRhcmRCcmlkZ2VJbnRlcm9wLnNvbA==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) |

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           develop   #14608      +/-   ##
===========================================
- Coverage    46.67%   45.56%   -1.11%     
===========================================
  Files         1041      983      -58     
  Lines        89639    84944    -4695     
===========================================
- Hits         41839    38708    -3131     
+ Misses       44715    43321    -1394     
+ Partials      3085     2915     -170     
```

| [Flag](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | Coverage Δ | |
|---|---|---|
| [cannon-go-tests-32](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | `?` | |
| [cannon-go-tests-64](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | `?` | |
| [contracts-bedrock-tests](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | `94.39% <93.75%> (+0.08%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files with missing lines](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | Coverage Δ | |
|---|---|---|
| [...ontracts-bedrock/src/L1/L1CrossDomainMessenger.sol](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree&filepath=packages%2Fcontracts-bedrock%2Fsrc%2FL1%2FL1CrossDomainMessenger.sol&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#diff-cGFja2FnZXMvY29udHJhY3RzLWJlZHJvY2svc3JjL0wxL0wxQ3Jvc3NEb21haW5NZXNzZW5nZXIuc29s) | `100.00% <ø> (ø)` | |
| [...ckages/contracts-bedrock/src/L1/L1ERC721Bridge.sol](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree&filepath=packages%2Fcontracts-bedrock%2Fsrc%2FL1%2FL1ERC721Bridge.sol&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#diff-cGFja2FnZXMvY29udHJhY3RzLWJlZHJvY2svc3JjL0wxL0wxRVJDNzIxQnJpZGdlLnNvbA==) | `100.00% <ø> (ø)` | |
| [...ages/contracts-bedrock/src/L1/L1StandardBridge.sol](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree&filepath=packages%2Fcontracts-bedrock%2Fsrc%2FL1%2FL1StandardBridge.sol&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#diff-cGFja2FnZXMvY29udHJhY3RzLWJlZHJvY2svc3JjL0wxL0wxU3RhbmRhcmRCcmlkZ2Uuc29s) | `100.00% <ø> (ø)` | |
| [...es/contracts-bedrock/src/L1/OPContractsManager.sol](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree&filepath=packages%2Fcontracts-bedrock%2Fsrc%2FL1%2FOPContractsManager.sol&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#diff-cGFja2FnZXMvY29udHJhY3RzLWJlZHJvY2svc3JjL0wxL09QQ29udHJhY3RzTWFuYWdlci5zb2w=) | `97.35% <100.00%> (+1.04%)` | :arrow_up: |
| [...kages/contracts-bedrock/src/L1/OptimismPortal2.sol](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree&filepath=packages%2Fcontracts-bedrock%2Fsrc%2FL1%2FOptimismPortal2.sol&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#diff-cGFja2FnZXMvY29udHJhY3RzLWJlZHJvY2svc3JjL0wxL09wdGltaXNtUG9ydGFsMi5zb2w=) | `98.05% <100.00%> (ø)` | |
| [...contracts-bedrock/src/L1/OptimismPortalInterop.sol](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree&filepath=packages%2Fcontracts-bedrock%2Fsrc%2FL1%2FOptimismPortalInterop.sol&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#diff-cGFja2FnZXMvY29udHJhY3RzLWJlZHJvY2svc3JjL0wxL09wdGltaXNtUG9ydGFsSW50ZXJvcC5zb2w=) | `100.00% <100.00%> (ø)` | |
| [...ontracts-bedrock/src/L2/L2CrossDomainMessenger.sol](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree&filepath=packages%2Fcontracts-bedrock%2Fsrc%2FL2%2FL2CrossDomainMessenger.sol&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#diff-cGFja2FnZXMvY29udHJhY3RzLWJlZHJvY2svc3JjL0wyL0wyQ3Jvc3NEb21haW5NZXNzZW5nZXIuc29s) | `83.33% <ø> (ø)` | |
| [...ckages/contracts-bedrock/src/L2/L2ERC721Bridge.sol](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree&filepath=packages%2Fcontracts-bedrock%2Fsrc%2FL2%2FL2ERC721Bridge.sol&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#diff-cGFja2FnZXMvY29udHJhY3RzLWJlZHJvY2svc3JjL0wyL0wyRVJDNzIxQnJpZGdlLnNvbA==) | `89.47% <ø> (ø)` | |
| [...ages/contracts-bedrock/src/L2/L2StandardBridge.sol](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree&filepath=packages%2Fcontracts-bedrock%2Fsrc%2FL2%2FL2StandardBridge.sol&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#diff-cGFja2FnZXMvY29udHJhY3RzLWJlZHJvY2svc3JjL0wyL0wyU3RhbmRhcmRCcmlkZ2Uuc29s) | `93.54% <100.00%> (ø)` | |
| [packages/contracts-bedrock/src/libraries/EOA.sol](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree&filepath=packages%2Fcontracts-bedrock%2Fsrc%2Flibraries%2FEOA.sol&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#diff-cGFja2FnZXMvY29udHJhY3RzLWJlZHJvY2svc3JjL2xpYnJhcmllcy9FT0Euc29s) | `100.00% <100.00%> (ø)` | |
| ... and [2 more](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | |

... and [64 files with indirect coverage changes](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/14608/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism)

</details>
author_association: NONE
user: login: tynes
created_at: 2025-03-04T15:03:12Z
body: This is definitely a hack on top of a hack. Given the short timeframe it may make sense to do this, but ideally we think from first principles and are able to delete a lot of this gas introspection. We really only need a gaslimit on deposits so that we can  meter force include gas, if we removed the concept of a gaslimit on withdrawals (which does not have product market fit) then we could likely simplify a ton of things and remove the majority of our bridge bugs
author_association: CONTRIBUTOR
user: login: smartcontracts
created_at: 2025-03-04T19:49:29Z
body: > This is definitely a hack on top of a hack. Given the short timeframe it may make sense to do this, but ideally we think from first principles and are able to delete a lot of this gas introspection. We really only need a gaslimit on deposits so that we can meter force include gas, if we removed the concept of a gaslimit on withdrawals (which does not have product market fit) then we could likely simplify a ton of things and remove the majority of our bridge bugs

We need to fix this problem before Pectra activates on L2, so making the codepath clear with strong tests is the best possible approach atm. Trying to fundamentally change the deposit/withdrawal gas system would be far too much work. I agree that the current deposit/withdrawal gas system is broken and we should fix it. Need to figure out how to prioritize that work with other things that we're doing. 
author_association: CONTRIBUTOR
user: login: mds1
created_at: 2025-03-04T21:48:55Z
body: Agreed with the above convo: we unfortunately need this fix in the short term, in the medium/long term we need to significantly simplify these paths or we'll be doing fixes like this (and risk introducing bugs) every time gas pricing changes
author_association: CONTRIBUTOR