diff --git a/contracts/src/bridge/arbitrum/AddressAliasHelper.sol b/contracts/src/bridge/arbitrum/AddressAliasHelper.sol new file mode 100644 index 000000000..5f79c1042 --- /dev/null +++ b/contracts/src/bridge/arbitrum/AddressAliasHelper.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2019-2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +pragma solidity >=0.7.0; + +library AddressAliasHelper { + uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + + /// @notice Utility function that converts the address in the L1 that submitted a tx to + /// the inbox to the msg.sender viewed in the L2 + /// @param l1Address the address in the L1 that triggered the tx to L2 + /// @return l2Address L2 address as viewed in msg.sender + function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { + l2Address = address(uint160(l1Address) + offset); + } + + /// @notice Utility function that converts the msg.sender viewed in the L2 to the + /// address in the L1 that submitted a tx to the inbox + /// @param l2Address L2 address as viewed in msg.sender + /// @return l1Address the address in the L1 that triggered the tx to L2 + function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) { + l1Address = address(uint160(l2Address) - offset); + } +} diff --git a/contracts/src/bridge/arbitrum/L1Bridge.sol b/contracts/src/bridge/arbitrum/L1Bridge.sol index c131dc349..ddc738cfc 100644 --- a/contracts/src/bridge/arbitrum/L1Bridge.sol +++ b/contracts/src/bridge/arbitrum/L1Bridge.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import "./interfaces/IInbox.sol"; +import "./interfaces/IOutbox.sol"; import "./interfaces/IArbRetryableTx.sol"; contract L1Bridge { @@ -60,4 +61,10 @@ contract L1Bridge { (uint256 submissionCost, ) = arbRetryableTx.getSubmissionPrice(_calldatasize); return submissionCost; } + + function onlyAuthorized(address _sender) external { + IOutbox outbox = IOutbox(inbox.bridge().activeOutbox()); + address l2Sender = outbox.l2ToL1Sender(); + require(l2Sender == l2Target, "Only L2 target"); + } } diff --git a/contracts/src/bridge/arbitrum/L2Bridge.sol b/contracts/src/bridge/arbitrum/L2Bridge.sol index e19de7163..0d7848820 100644 --- a/contracts/src/bridge/arbitrum/L2Bridge.sol +++ b/contracts/src/bridge/arbitrum/L2Bridge.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import "./interfaces/IArbSys.sol"; +import "./AddressAliasHelper.sol"; contract L2Bridge { address public l1Target; @@ -26,4 +27,8 @@ contract L2Bridge { emit L2ToL1TxCreated(withdrawalId); return withdrawalId; } + + function onlyAuthorized(address _sender) external { + require(_sender == AddressAliasHelper.applyL1ToL2Alias(l1Target), "Only L1 target"); + } } diff --git a/contracts/src/gateway/arbitrum/ArbitrumGateway.sol b/contracts/src/gateway/arbitrum/ArbitrumGateway.sol index 72d10ee78..ed6f9feed 100644 --- a/contracts/src/gateway/arbitrum/ArbitrumGateway.sol +++ b/contracts/src/gateway/arbitrum/ArbitrumGateway.sol @@ -10,9 +10,15 @@ import "../IForeignGateway.sol"; contract ArbitrumGateway is IHomeGateway { // L2 bridge with the ForeignGateway as the l1target + L2Bridge internal l2bridge; address public arbitrator; + modifier onlyFromL1() { + l2bridge.onlyAuthorized(msg.sender); + _; + } + constructor(address _arbitrator, L2Bridge _l2bridge) { arbitrator = _arbitrator; l2bridge = _l2bridge; @@ -35,7 +41,7 @@ contract ArbitrumGateway is IHomeGateway { * for it to maintain it's internal mapping. * @param _data The calldata to relay */ - function relayCreateDispute(bytes memory _data) external { + function relayCreateDispute(bytes memory _data) external onlyFromL1 { // solhint-disable-next-line avoid-low-level-calls (bool success, ) = arbitrator.call(_data); require(success, "Failed to call contract"); diff --git a/contracts/src/gateway/arbitrum/EthereumGateway.sol b/contracts/src/gateway/arbitrum/EthereumGateway.sol index 063373e89..2a7cf35f3 100644 --- a/contracts/src/gateway/arbitrum/EthereumGateway.sol +++ b/contracts/src/gateway/arbitrum/EthereumGateway.sol @@ -17,6 +17,11 @@ contract EthereumGateway is IForeignGateway { // in the V2 court. uint256 internal internalArbitrationCost; + modifier onlyFromL2() { + l1bridge.onlyAuthorized(msg.sender); + _; + } + constructor(uint256 _arbitrationCost, L1Bridge _l1bridge) { internalArbitrationCost = _arbitrationCost; l1bridge = _l1bridge; @@ -66,7 +71,7 @@ contract EthereumGateway is IForeignGateway { * * @param _data The calldata to relay */ - function relayRule(bytes memory _data) external { + function relayRule(bytes memory _data) external onlyFromL2 { address arbitrable = address(0); // see the TODO above about the disputeId // solhint-disable-next-line avoid-low-level-calls