94 lines
3.2 KiB
Solidity
94 lines
3.2 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 "../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);
|
|
}
|
|
}
|