diff --git a/contracts/chain-adapters/OP_Adapter.sol b/contracts/chain-adapters/OP_Adapter.sol index 0fcf1a975..e2f5c8818 100644 --- a/contracts/chain-adapters/OP_Adapter.sol +++ b/contracts/chain-adapters/OP_Adapter.sol @@ -35,31 +35,43 @@ contract OP_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapter { IL1StandardBridge public immutable L1_STANDARD_BRIDGE; IOpUSDCBridgeAdapter public immutable L1_OP_USDC_BRIDGE; + error InvalidBridgeConfig(); + /** * @notice Constructs new Adapter. * @param _l1Weth WETH address on L1. + * @param _l1Usdc USDC address on L1. * @param _crossDomainMessenger XDomainMessenger Destination chain system contract. * @param _l1StandardBridge Standard bridge contract. - * @param _l1Usdc USDC address on L1. + * @param _l1USDCBridge OP USDC bridge contract. + * @param _cctpTokenMessenger CCTP token messenger contract. + * @param _recipientCircleDomainId Circle domain ID of the destination chain. */ constructor( WETH9Interface _l1Weth, IERC20 _l1Usdc, address _crossDomainMessenger, IL1StandardBridge _l1StandardBridge, - IOpUSDCBridgeAdapter _l1USDCBridge + IOpUSDCBridgeAdapter _l1USDCBridge, + ITokenMessenger _cctpTokenMessenger, + uint32 _recipientCircleDomainId ) CrossDomainEnabled(_crossDomainMessenger) - CircleCCTPAdapter( - _l1Usdc, - // Hardcode cctp messenger to 0x0 to disable CCTP bridging. - ITokenMessenger(address(0)), - CircleDomainIds.UNINITIALIZED - ) + CircleCCTPAdapter(_l1Usdc, _cctpTokenMessenger, _recipientCircleDomainId) { L1_WETH = _l1Weth; L1_STANDARD_BRIDGE = _l1StandardBridge; L1_OP_USDC_BRIDGE = _l1USDCBridge; + + address zero = address(0); + if (address(_l1Usdc) != zero) { + bool opUSDCBridgeDisabled = address(_l1USDCBridge) == zero; + bool cctpUSDCBridgeDisabled = address(_cctpTokenMessenger) == zero; + // Bridged and Native USDC are mutually exclusive. + if (opUSDCBridgeDisabled == cctpUSDCBridgeDisabled) { + revert InvalidBridgeConfig(); + } + } } /** diff --git a/test/evm/foundry/local/OP_Adapter.t.sol b/test/evm/foundry/local/OP_Adapter.t.sol new file mode 100644 index 000000000..e9a5aa8fd --- /dev/null +++ b/test/evm/foundry/local/OP_Adapter.t.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { Test } from "forge-std/Test.sol"; + +import { ERC20, IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; +import { IL1StandardBridge } from "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; +import { IOpUSDCBridgeAdapter } from "../../../../contracts/external/interfaces/IOpUSDCBridgeAdapter.sol"; +import { ITokenMessenger } from "../../../../contracts/external/interfaces/CCTPInterfaces.sol"; + +import { OP_Adapter } from "../../../../contracts/chain-adapters/OP_Adapter.sol"; +import { WETH9Interface } from "../../../../contracts/external/interfaces/WETH9Interface.sol"; +import { WETH9 } from "../../../../contracts/external/WETH9.sol"; + +contract OP_AdapterTest is Test { + ERC20 l1Usdc; + WETH9 l1Weth; + + IL1StandardBridge standardBridge; + IOpUSDCBridgeAdapter opUSDCBridge; + ITokenMessenger cctpMessenger; + + uint32 constant RECIPIENT_CIRCLE_DOMAIN_ID = 1; + + function setUp() public { + l1Usdc = new ERC20("l1Usdc", "l1Usdc"); + l1Weth = new WETH9(); + + standardBridge = IL1StandardBridge(makeAddr("standardBridge")); + opUSDCBridge = IOpUSDCBridgeAdapter(makeAddr("opUSDCBridge")); + cctpMessenger = ITokenMessenger(makeAddr("cctpMessenger")); + } + + function testUSDCNotSet() public { + new OP_Adapter( + WETH9Interface(address(l1Weth)), + IERC20(address(0)), + address(0), + standardBridge, + IOpUSDCBridgeAdapter(address(0)), + ITokenMessenger(address(0)), + RECIPIENT_CIRCLE_DOMAIN_ID + ); + } + + function testL1UsdcBridgeSet() public { + new OP_Adapter( + WETH9Interface(address(l1Weth)), + IERC20(address(l1Usdc)), + address(0), + standardBridge, + opUSDCBridge, + ITokenMessenger(address(0)), + RECIPIENT_CIRCLE_DOMAIN_ID + ); + } + + function testCctpMessengerSet() public { + new OP_Adapter( + WETH9Interface(address(l1Weth)), + IERC20(address(0)), + address(0), + standardBridge, + IOpUSDCBridgeAdapter(address(0)), + cctpMessenger, + RECIPIENT_CIRCLE_DOMAIN_ID + ); + } + + function testNeitherSet() public { + vm.expectRevert(OP_Adapter.InvalidBridgeConfig.selector); + new OP_Adapter( + WETH9Interface(address(l1Weth)), + IERC20(address(l1Usdc)), + address(0), + standardBridge, + IOpUSDCBridgeAdapter(address(0)), + ITokenMessenger(address(0)), + RECIPIENT_CIRCLE_DOMAIN_ID + ); + } + + function testBothSet() public { + vm.expectRevert(OP_Adapter.InvalidBridgeConfig.selector); + new OP_Adapter( + WETH9Interface(address(l1Weth)), + IERC20(address(l1Usdc)), + address(0), + standardBridge, + opUSDCBridge, + cctpMessenger, + RECIPIENT_CIRCLE_DOMAIN_ID + ); + } +} diff --git a/test/evm/hardhat/chain-adapters/OP_Adapter.ts b/test/evm/hardhat/chain-adapters/OP_Adapter.ts index c5e2778bd..e3b612ed5 100644 --- a/test/evm/hardhat/chain-adapters/OP_Adapter.ts +++ b/test/evm/hardhat/chain-adapters/OP_Adapter.ts @@ -12,9 +12,11 @@ import { toWei, getContractFactory, seedWallet, + toBN, } from "../../../../utils/utils"; import { hubPoolFixture, enableTokensForLP } from "../fixtures/HubPool.Fixture"; import { constructSingleChainTree } from "../MerkleLib.utils"; +import { ZERO_ADDRESS } from "@uma/common"; let hubPool: Contract, adapter: Contract, weth: Contract, usdc: Contract, mockSpoke: Contract, timer: Contract; let l2Weth: string, l2Usdc: string; @@ -63,7 +65,9 @@ describe("OP Adapter", function () { usdc.address, l1CrossDomainMessenger.address, l1StandardBridge.address, - opUSDCBridge.address + opUSDCBridge.address, + ZERO_ADDRESS, + toBN("322") ); // Seed the HubPool some funds so it can send L1->L2 messages. await hubPool.connect(liquidityProvider).loadEthForL2Calls({ value: toWei("1") });