// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./interfaces/ICoupon.sol"; import "./interfaces/ICompliance.sol"; /// @title Settlement — 交易结算合约 /// @notice 原子交换:券 ↔ 稳定币 同时转移,支持多稳定币 /// @dev 使用 Transparent Proxy 部署 contract Settlement is Initializable, AccessControlUpgradeable, ReentrancyGuardUpgradeable { bytes32 public constant SETTLER_ROLE = keccak256("SETTLER_ROLE"); bytes32 public constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE"); ICoupon public couponContract; ICompliance public compliance; // 多稳定币支持 mapping(address => bool) public supportedStablecoins; address public defaultStablecoin; // USDC // --- Events --- event TradeSettled( uint256 indexed tokenId, address indexed buyer, address indexed seller, uint256 price, address stablecoin ); event RefundExecuted( uint256 indexed tokenId, address indexed buyer, address indexed seller, uint256 refundAmount, address stablecoin ); event StablecoinAdded(address indexed token); event StablecoinRemoved(address indexed token); function initialize( address _couponContract, address _compliance, address _defaultStablecoin, address admin ) external initializer { __AccessControl_init(); __ReentrancyGuard_init(); couponContract = ICoupon(_couponContract); compliance = ICompliance(_compliance); defaultStablecoin = _defaultStablecoin; supportedStablecoins[_defaultStablecoin] = true; _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(SETTLER_ROLE, admin); _grantRole(GOVERNANCE_ROLE, admin); } /// @notice 原子交换:券 ↔ 稳定币 同时转移 /// @param tokenId 券 token ID /// @param buyer 买方地址 /// @param seller 卖方地址 /// @param price 成交价格(USDC 精度) /// @param stablecoin 使用的稳定币地址 function executeSwap( uint256 tokenId, address buyer, address seller, uint256 price, address stablecoin ) external onlyRole(SETTLER_ROLE) nonReentrant { require(supportedStablecoins[stablecoin], "Settlement: unsupported stablecoin"); require(buyer != address(0) && seller != address(0), "Settlement: zero address"); require(price > 0, "Settlement: zero price"); // 合规检查 ICoupon.CouponConfig memory config = couponContract.getConfig(tokenId); compliance.preTradeCheck(buyer, seller, price, config.couponType); // Utility Track: 价格 ≤ 面值上限 if (config.couponType == ICoupon.CouponType.Utility) { require(price <= config.maxPrice, "Utility: price exceeds max price"); } // 转售次数检查 require( couponContract.getResaleCount(tokenId) < config.maxResaleCount, "Settlement: max resale count exceeded" ); // 到期检查 require(block.timestamp <= config.expiryDate, "Settlement: coupon expired"); // 原子交换(要么全部成功,要么全部回滚) IERC20(stablecoin).transferFrom(buyer, seller, price); couponContract.safeTransferFrom(seller, buyer, tokenId); couponContract.incrementResaleCount(tokenId); emit TradeSettled(tokenId, buyer, seller, price, stablecoin); } /// @notice 使用默认稳定币的原子交换(便捷方法) function executeSwap( uint256 tokenId, address buyer, address seller, uint256 price ) external onlyRole(SETTLER_ROLE) nonReentrant { _executeSwapInternal(tokenId, buyer, seller, price, defaultStablecoin); } /// @notice 退款:反向原子交换 function executeRefund( uint256 tokenId, address buyer, address seller, uint256 refundAmount, address stablecoin ) external onlyRole(SETTLER_ROLE) nonReentrant { require(supportedStablecoins[stablecoin], "Settlement: unsupported stablecoin"); require(refundAmount > 0, "Settlement: zero refund"); // 合规检查 require(!compliance.isBlacklisted(buyer), "Settlement: buyer blacklisted"); require(!compliance.isBlacklisted(seller), "Settlement: seller blacklisted"); // 反向原子交换 couponContract.safeTransferFrom(buyer, seller, tokenId); IERC20(stablecoin).transferFrom(seller, buyer, refundAmount); emit RefundExecuted(tokenId, buyer, seller, refundAmount, stablecoin); } /// @notice 添加支持的稳定币 function addStablecoin(address token) external onlyRole(GOVERNANCE_ROLE) { require(token != address(0), "Settlement: zero address"); supportedStablecoins[token] = true; emit StablecoinAdded(token); } /// @notice 移除稳定币支持 function removeStablecoin(address token) external onlyRole(GOVERNANCE_ROLE) { require(token != defaultStablecoin, "Settlement: cannot remove default"); supportedStablecoins[token] = false; emit StablecoinRemoved(token); } // --- Internal --- function _executeSwapInternal( uint256 tokenId, address buyer, address seller, uint256 price, address stablecoin ) internal { require(buyer != address(0) && seller != address(0), "Settlement: zero address"); require(price > 0, "Settlement: zero price"); ICoupon.CouponConfig memory config = couponContract.getConfig(tokenId); compliance.preTradeCheck(buyer, seller, price, config.couponType); if (config.couponType == ICoupon.CouponType.Utility) { require(price <= config.maxPrice, "Utility: price exceeds max price"); } require( couponContract.getResaleCount(tokenId) < config.maxResaleCount, "Settlement: max resale count exceeded" ); require(block.timestamp <= config.expiryDate, "Settlement: coupon expired"); IERC20(stablecoin).transferFrom(buyer, seller, price); couponContract.safeTransferFrom(seller, buyer, tokenId); couponContract.incrementResaleCount(tokenId); emit TradeSettled(tokenId, buyer, seller, price, stablecoin); } }