Files
smom-dbis-138/contracts/treasury/CcipBridgeAdapter138.sol
2026-03-02 12:14:09 -08:00

96 lines
3.3 KiB
Solidity

// 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;
}
}