// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "../ccip/CCIPWETH9Bridge.sol"; /** * @title CcipBridgeAdapter138 * @notice Export-only module: only WETH9 to Ethereum Mainnet; only callable by StrategyExecutor138. * @dev Destination selector fixed to 5009297550715157269. Gate: exportsEnabled must be true. * See docs/treasury/EXECUTOR_ALLOWLIST_MATRIX.md and EXPORT_STATE_MACHINE.md. */ contract CcipBridgeAdapter138 is AccessControl, ReentrancyGuard { using SafeERC20 for IERC20; uint64 public constant MAINNET_SELECTOR = 5009297550715157269; address public immutable weth9; address public immutable bridgeWeth9; address public immutable receiverMainnet; address public strategyExecutor; bool private _executorSet; bool public exportsEnabled; event ExportInitiated(bytes32 indexed messageId, uint256 amount); event ExportsEnabledSet(bool enabled); error ExportsDisabled(); error OnlyExecutor(); error ZeroAmount(); error DeadlineExpired(); error InsufficientOutput(); constructor( address _weth9, address _bridgeWeth9, address _receiverMainnet, address admin ) { if (_weth9 == address(0) || _bridgeWeth9 == address(0) || _receiverMainnet == address(0)) { revert("CcipBridgeAdapter138: zero address"); } weth9 = _weth9; bridgeWeth9 = _bridgeWeth9; receiverMainnet = _receiverMainnet; _grantRole(DEFAULT_ADMIN_ROLE, admin); } function setStrategyExecutor(address _executor) external onlyRole(DEFAULT_ADMIN_ROLE) { if (_executorSet) revert("CcipBridgeAdapter138: executor already set"); if (_executor == address(0)) revert("CcipBridgeAdapter138: zero executor"); _executorSet = true; strategyExecutor = _executor; } function setExportsEnabled(bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { exportsEnabled = enabled; emit ExportsEnabledSet(enabled); } /** * @notice Send WETH9 to Ethereum Mainnet. Only callable by StrategyExecutor138. * @param amount Amount of WETH9 to send. * @param minAmount Unused; reserved for future slippage/quote checks. * @param deadline Revert if block.timestamp > deadline. */ function sendWeth9ToMainnet(uint256 amount, uint256 minAmount, uint256 deadline) external payable nonReentrant returns (bytes32 messageId) { if (msg.sender != strategyExecutor) revert OnlyExecutor(); if (!exportsEnabled) revert ExportsDisabled(); if (amount == 0) revert ZeroAmount(); if (block.timestamp > deadline) revert DeadlineExpired(); if (minAmount > 0 && amount < minAmount) revert InsufficientOutput(); IERC20(weth9).safeTransferFrom(msg.sender, address(this), amount); IERC20(weth9).approve(bridgeWeth9, amount); messageId = CCIPWETH9Bridge(payable(bridgeWeth9)).sendCrossChain{value: msg.value}( MAINNET_SELECTOR, receiverMainnet, amount ); emit ExportInitiated(messageId, amount); return messageId; } }