diff --git a/contracts/L1Teleporter.sol b/contracts/L1Teleporter.sol index bdb27c9..414c682 100644 --- a/contracts/L1Teleporter.sol +++ b/contracts/L1Teleporter.sol @@ -118,7 +118,8 @@ contract L1Teleporter is Pausable, AccessControl, L2ForwarderPredictor, IL1Telep to: params.to, gasLimit: params.gasParams.l2l3TokenBridgeGasLimit, gasPriceBid: params.gasParams.l3GasPriceBid, - maxSubmissionCost: maxSubmissionCost + maxSubmissionCost: maxSubmissionCost, + l3Calldata: params.l3Calldata }); } @@ -142,6 +143,11 @@ contract L1Teleporter is Pausable, AccessControl, L2ForwarderPredictor, IL1Telep // all teleportation types require at least these 2 retryables to L2 ethAmount = costs.l1l2TokenBridgeCost + costs.l2ForwarderFactoryCost; + // OnlyCustomFee is the only type that supports L3 calldata + if (teleportationType != TeleportationType.OnlyCustomFee && params.l3Calldata.length > 0) { + revert L3CalldataNotAllowedForType(teleportationType); + } + // in addition to the above ETH amount, more fee token and/or ETH is required depending on the teleportation type if (teleportationType == TeleportationType.Standard) { // standard type requires 1 retryable to L3 paid for in ETH diff --git a/contracts/L2Forwarder.sol b/contracts/L2Forwarder.sol index ca5e3f5..dadd1e1 100644 --- a/contracts/L2Forwarder.sol +++ b/contracts/L2Forwarder.sol @@ -108,7 +108,7 @@ contract L2Forwarder is IL2Forwarder { gasLimit: params.gasLimit, maxFeePerGas: params.gasPriceBid, tokenTotalFeeAmount: tokenBalance, - data: "" + data: params.l3Calldata }); emit BridgedToL3(callValue, totalFeeAmount); diff --git a/contracts/interfaces/IL1Teleporter.sol b/contracts/interfaces/IL1Teleporter.sol index b386587..9861539 100644 --- a/contracts/interfaces/IL1Teleporter.sol +++ b/contracts/interfaces/IL1Teleporter.sol @@ -18,6 +18,7 @@ interface IL1Teleporter is IL2ForwarderPredictor { /// @param to L3 address that will receive the tokens /// @param amount Amount of tokens being teleported /// @param gasParams Gas parameters for each retryable ticket + /// @param l3Calldata Calldata for the L2ForwarderFactory call on L2 to L3 retryable struct TeleportParams { address l1Token; address l3FeeTokenL1Addr; @@ -26,6 +27,7 @@ interface IL1Teleporter is IL2ForwarderPredictor { address to; uint256 amount; RetryableGasParams gasParams; + bytes l3Calldata; } /// @notice Gas parameters for each retryable ticket. @@ -74,6 +76,8 @@ interface IL1Teleporter is IL2ForwarderPredictor { error InsufficientFeeToken(uint256 required, uint256 provided); /// @notice Thrown when the SKIP_FEE_TOKEN magic is passed for l3FeeTokenL1Addr and at least one fee token related gas parameter is nonzero error NonZeroFeeTokenAmount(); + /// @notice Thrown when there is non empty l3Calldata but the teleportation type is not OnlyCustomFee + error L3CalldataNotAllowedForType(TeleportationType teleportationType); /// @notice Start an L1 -> L3 transfer. msg.value sent must equal the total ETH cost of all retryables. /// Call `determineTypeAndFees` to calculate the total cost of retryables in ETH and the L3's fee token. diff --git a/contracts/interfaces/IL2Forwarder.sol b/contracts/interfaces/IL2Forwarder.sol index 4a304f2..a7f6fb5 100644 --- a/contracts/interfaces/IL2Forwarder.sol +++ b/contracts/interfaces/IL2Forwarder.sol @@ -17,6 +17,7 @@ interface IL2Forwarder { /// @param gasLimit Gas limit for the L2 -> L3 retryable /// @param gasPriceBid Gas price for the L2 -> L3 retryable /// @param maxSubmissionCost Max submission fee for the L2 -> L3 retryable. Is ignored for Standard and OnlyCustomFee teleportation types. + /// @param l3Calldata Calldata for the L2ForwarderFactory call on L2 to L3 retryable struct L2ForwarderParams { address owner; address l2Token; @@ -26,6 +27,7 @@ interface IL2Forwarder { uint256 gasLimit; uint256 gasPriceBid; uint256 maxSubmissionCost; + bytes l3Calldata; } /// @notice Emitted after a successful call to rescue diff --git a/test/L2Forwarder.t.sol b/test/L2Forwarder.t.sol index a69c397..456a1fb 100644 --- a/test/L2Forwarder.t.sol +++ b/test/L2Forwarder.t.sol @@ -86,7 +86,8 @@ contract L2ForwarderTest is BaseTest { to: l3Recipient, gasLimit: gasLimit, gasPriceBid: gasPriceBid, - maxSubmissionCost: 0 + maxSubmissionCost: 0, + l3Calldata: "" }); // simulate A1 and B1 (bridging TOKEN to L2) @@ -133,7 +134,8 @@ contract L2ForwarderTest is BaseTest { to: l3Recipient, gasLimit: gasLimit, gasPriceBid: gasPriceBid, - maxSubmissionCost: 0 + maxSubmissionCost: 0, + l3Calldata: "" }); // simulate A1 and B1 (bridging TOKEN to L2) @@ -202,7 +204,8 @@ contract L2ForwarderTest is BaseTest { to: l3Recipient, gasLimit: gasLimit, gasPriceBid: gasPriceBid, - maxSubmissionCost: expectedSubmissionCost + maxSubmissionCost: expectedSubmissionCost, + l3Calldata: "" }); // simulate ETH refunds from A1, A2, B1, B2 @@ -288,7 +291,8 @@ contract L2ForwarderTest is BaseTest { to: l3Recipient, gasLimit: gasLimitA, gasPriceBid: gasPriceBidA, - maxSubmissionCost: expectedSubmissionCostA + maxSubmissionCost: expectedSubmissionCostA, + l3Calldata: "" }); // skip simulating ETH refunds from A1, A2, B1 @@ -319,7 +323,8 @@ contract L2ForwarderTest is BaseTest { to: l3Recipient, gasLimit: gasLimitB, gasPriceBid: gasPriceBidB, - maxSubmissionCost: expectedSubmissionCostB + maxSubmissionCost: expectedSubmissionCostB, + l3Calldata: "" }); _expectNonFeeTokenToCustomFeeEvents(paramsB, tokenAmountB); vm.prank(aliasedL1Teleporter); diff --git a/test/Teleporter.t.sol b/test/Teleporter.t.sol index 65c8835..d461799 100644 --- a/test/Teleporter.t.sol +++ b/test/Teleporter.t.sol @@ -189,7 +189,8 @@ contract L1TeleporterTest is BaseTest { l2l3RouterOrInbox: l2l3RouterOrInbox, to: address(1), amount: 10, - gasParams: gasParams + gasParams: gasParams, + l3Calldata: "" }); ( uint256 standardEth, @@ -254,7 +255,8 @@ contract L1TeleporterTest is BaseTest { l1l2FeeTokenBridgeMaxSubmissionCost: 8, l1l2TokenBridgeMaxSubmissionCost: 9, l2l3TokenBridgeMaxSubmissionCost: 10 - }) + }), + l3Calldata: "" }); ( uint256 standardEth, @@ -281,7 +283,8 @@ contract L1TeleporterTest is BaseTest { l2l3RouterOrInbox: l2l3RouterOrInbox, to: address(1), amount: 10, - gasParams: gasParams + gasParams: gasParams, + l3Calldata: "" }); ( uint256 feeTokenEth, @@ -303,7 +306,8 @@ contract L1TeleporterTest is BaseTest { l2l3RouterOrInbox: l2l3RouterOrInbox, to: address(1), amount: 10, - gasParams: gasParams + gasParams: gasParams, + l3Calldata: "" }); ( uint256 feeTokenEth2, @@ -328,7 +332,8 @@ contract L1TeleporterTest is BaseTest { l2l3RouterOrInbox: l2l3RouterOrInbox, to: address(1), amount: 10, - gasParams: gasParams + gasParams: gasParams, + l3Calldata: "" }); skipFeeTokenParams.gasParams.l3GasPriceBid = 0; skipFeeTokenParams.gasParams.l2l3TokenBridgeGasLimit = 0; @@ -389,7 +394,8 @@ contract L1TeleporterTest is BaseTest { l2l3RouterOrInbox: l2l3RouterOrInbox, to: receiver, amount: amount, - gasParams: gasParams + gasParams: gasParams, + l3Calldata: "" }); (uint256 requiredEth,,, IL1Teleporter.RetryableGasCosts memory retryableCosts) = @@ -440,7 +446,8 @@ contract L1TeleporterTest is BaseTest { l2l3RouterOrInbox: l2l3RouterOrInbox, to: receiver, amount: amount, - gasParams: gasParams + gasParams: gasParams, + l3Calldata: "" }); (uint256 requiredEth, uint256 requiredFeeTokenAmount,, IL1Teleporter.RetryableGasCosts memory retryableCosts) = @@ -498,7 +505,8 @@ contract L1TeleporterTest is BaseTest { l2l3RouterOrInbox: l2l3RouterOrInbox, to: receiver, amount: amount, - gasParams: gasParams + gasParams: gasParams, + l3Calldata: "" }); (uint256 requiredEth, uint256 requiredFeeTokenAmount,, IL1Teleporter.RetryableGasCosts memory retryableCosts) = @@ -566,7 +574,8 @@ contract L1TeleporterTest is BaseTest { l2l3RouterOrInbox: l2l3RouterOrInbox, to: receiver, amount: amount, - gasParams: gasParams + gasParams: gasParams, + l3Calldata: "" }); (uint256 requiredEth,,, IL1Teleporter.RetryableGasCosts memory retryableCosts) =