gcx/blockchain/genex-contracts/src/Settlement.sol

183 lines
6.6 KiB
Solidity
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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