// 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 "../bridge/integration/ICWReserveVerifier.sol"; interface ICWBurnable { function burn(uint256 amount) external; } /** * @title CWBuybackExecutor * @notice Routes fee revenue into cW buybacks; burns purchased tokens or locks in treasury. */ contract CWBuybackExecutor is AccessControl, ReentrancyGuard { using SafeERC20 for IERC20; bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); ICWReserveVerifier public reserveVerifier; address public treasuryLock; bool public burnOnBuyback = true; event ReserveVerifierUpdated(address indexed verifier); event TreasuryLockUpdated(address indexed treasuryLock); event BurnModeUpdated(bool burnOnBuyback); event BuybackExecuted(address indexed paymentToken, address indexed cwToken, uint256 paid, uint256 received, bool burned); error ZeroAddress(); error ReserveCheckFailed(); constructor(address admin, address reserveVerifier_, address treasuryLock_) { if (admin == address(0)) { revert ZeroAddress(); } _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(EXECUTOR_ROLE, admin); reserveVerifier = ICWReserveVerifier(reserveVerifier_); treasuryLock = treasuryLock_; } function setReserveVerifier(address verifier_) external onlyRole(DEFAULT_ADMIN_ROLE) { reserveVerifier = ICWReserveVerifier(verifier_); emit ReserveVerifierUpdated(verifier_); } function setTreasuryLock(address treasuryLock_) external onlyRole(DEFAULT_ADMIN_ROLE) { treasuryLock = treasuryLock_; emit TreasuryLockUpdated(treasuryLock_); } function setBurnOnBuyback(bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) { burnOnBuyback = enabled; emit BurnModeUpdated(enabled); } /** * @notice Operator delivers pre-purchased cW tokens; contract burns or locks them after reserve check. */ function executeBuyback( address canonicalToken, uint64 destinationChainSelector, address cwToken, address paymentToken, uint256 paymentAmount, uint256 cwAmount ) external onlyRole(EXECUTOR_ROLE) nonReentrant { if (address(reserveVerifier) != address(0)) { if (!reserveVerifier.verifyLock(canonicalToken, destinationChainSelector, 0)) { revert ReserveCheckFailed(); } } if (paymentAmount > 0 && paymentToken != address(0)) { IERC20(paymentToken).safeTransferFrom(msg.sender, address(this), paymentAmount); } IERC20(cwToken).safeTransferFrom(msg.sender, address(this), cwAmount); bool burned = false; if (burnOnBuyback) { ICWBurnable(cwToken).burn(cwAmount); burned = true; } else if (treasuryLock != address(0)) { IERC20(cwToken).safeTransfer(treasuryLock, cwAmount); } emit BuybackExecuted(paymentToken, cwToken, paymentAmount, cwAmount, burned); } }