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

229 lines
8.7 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 "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import "./interfaces/ICoupon.sol";
import "./interfaces/ICompliance.sol";
/// @title CouponBackedSecurity — CBS 资产证券化合约
/// @notice 券收益流打包为资产支持证券Securities Track + Broker-Dealer 牌照下运营
/// @dev 使用 Transparent Proxy 部署
contract CouponBackedSecurity is Initializable, AccessControlUpgradeable, ReentrancyGuardUpgradeable, ERC721Holder {
bytes32 public constant ISSUER_ROLE = keccak256("ISSUER_ROLE");
bytes32 public constant SETTLER_ROLE = keccak256("SETTLER_ROLE");
bytes32 public constant RATING_AGENCY_ROLE = keccak256("RATING_AGENCY_ROLE");
bytes32 public constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE");
ICompliance public compliance;
ICoupon public couponContract;
IERC20 public stablecoin;
struct Pool {
uint256[] couponIds; // 底层券资产
uint256 totalFaceValue; // 底层面值总额
uint256 totalShares; // 份额总数
uint256 soldShares; // 已售份额
uint256 maturityDate; // 到期日
string creditRating; // 信用评级
address issuer; // 池创建方
bool active; // 是否活跃
uint256 createdAt;
}
mapping(uint256 => Pool) private _pools;
// 份额持有poolId → holder → shares
mapping(uint256 => mapping(address => uint256)) public shares;
// 收益分配记录poolId → holder → claimed amount
mapping(uint256 => mapping(address => uint256)) public claimedYield;
// 池的总收益
mapping(uint256 => uint256) public poolYield;
uint256 public nextPoolId;
// --- Events ---
event PoolCreated(
uint256 indexed poolId,
address indexed issuer,
uint256 couponCount,
uint256 totalFaceValue,
uint256 totalShares
);
event SharesPurchased(uint256 indexed poolId, address indexed buyer, uint256 shareCount, uint256 amount);
event CreditRatingSet(uint256 indexed poolId, string rating);
event YieldDeposited(uint256 indexed poolId, uint256 totalYield);
event YieldClaimed(uint256 indexed poolId, address indexed holder, uint256 amount);
event PoolDeactivated(uint256 indexed poolId);
function initialize(
address _couponContract,
address _compliance,
address _stablecoin,
address admin
) external initializer {
__AccessControl_init();
__ReentrancyGuard_init();
couponContract = ICoupon(_couponContract);
compliance = ICompliance(_compliance);
stablecoin = IERC20(_stablecoin);
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(GOVERNANCE_ROLE, admin);
}
// ========================
// 池管理
// ========================
/// @notice 创建 CBS 池 — 将一组券的收益流打包
/// @param couponIds 底层券 token IDs
/// @param totalShares 份额总数
/// @param maturityDate 到期日
function createPool(
uint256[] calldata couponIds,
uint256 totalShares,
uint256 maturityDate
) external onlyRole(ISSUER_ROLE) nonReentrant returns (uint256 poolId) {
require(couponIds.length > 0, "CBS: empty pool");
require(totalShares > 0, "CBS: zero shares");
require(maturityDate > block.timestamp, "CBS: invalid maturity");
// 合规检查:创建者必须 KYC L3 + Broker-Dealer 资质
compliance.requireKycLevel(msg.sender, 3);
uint256 totalFace = 0;
for (uint256 i = 0; i < couponIds.length; i++) {
ICoupon.CouponConfig memory config = couponContract.getConfig(couponIds[i]);
require(
config.couponType == ICoupon.CouponType.Security,
"CBS: only Security coupons"
);
totalFace += couponContract.getFaceValue(couponIds[i]);
// 锁定底层券到池中
couponContract.safeTransferFrom(msg.sender, address(this), couponIds[i]);
}
poolId = nextPoolId++;
_pools[poolId].couponIds = couponIds;
_pools[poolId].totalFaceValue = totalFace;
_pools[poolId].totalShares = totalShares;
_pools[poolId].soldShares = 0;
_pools[poolId].maturityDate = maturityDate;
_pools[poolId].creditRating = "";
_pools[poolId].issuer = msg.sender;
_pools[poolId].active = true;
_pools[poolId].createdAt = block.timestamp;
emit PoolCreated(poolId, msg.sender, couponIds.length, totalFace, totalShares);
}
/// @notice 购买 CBS 份额 — 仅合格投资者KYC L2+
function purchaseShares(uint256 poolId, uint256 shareCount) external nonReentrant {
require(_pools[poolId].active, "CBS: pool not active");
require(shareCount > 0, "CBS: zero shares");
require(
_pools[poolId].soldShares + shareCount <= _pools[poolId].totalShares,
"CBS: insufficient shares"
);
// 合规检查
compliance.requireKycLevel(msg.sender, 2);
uint256 price = (_pools[poolId].totalFaceValue * shareCount) / _pools[poolId].totalShares;
require(price > 0, "CBS: price is zero");
stablecoin.transferFrom(msg.sender, address(this), price);
shares[poolId][msg.sender] += shareCount;
_pools[poolId].soldShares += shareCount;
emit SharesPurchased(poolId, msg.sender, shareCount, price);
}
// ========================
// 信用评级
// ========================
/// @notice 链下评级机构写入信用评级
function setCreditRating(uint256 poolId, string calldata rating)
external
onlyRole(RATING_AGENCY_ROLE)
{
require(_pools[poolId].active, "CBS: pool not active");
_pools[poolId].creditRating = rating;
emit CreditRatingSet(poolId, rating);
}
// ========================
// 收益分配
// ========================
/// @notice 存入收益(由 clearing-service 链下计算后调用)
function depositYield(uint256 poolId, uint256 totalYield) external onlyRole(SETTLER_ROLE) nonReentrant {
require(_pools[poolId].active, "CBS: pool not active");
require(totalYield > 0, "CBS: zero yield");
stablecoin.transferFrom(msg.sender, address(this), totalYield);
poolYield[poolId] += totalYield;
emit YieldDeposited(poolId, totalYield);
}
/// @notice 持有人领取收益 — 按份额比例
function claimYield(uint256 poolId) external nonReentrant {
uint256 holderShares = shares[poolId][msg.sender];
require(holderShares > 0, "CBS: no shares");
uint256 totalEntitled = (poolYield[poolId] * holderShares) / _pools[poolId].totalShares;
uint256 alreadyClaimed = claimedYield[poolId][msg.sender];
uint256 claimable = totalEntitled - alreadyClaimed;
require(claimable > 0, "CBS: nothing to claim");
claimedYield[poolId][msg.sender] = totalEntitled;
stablecoin.transfer(msg.sender, claimable);
emit YieldClaimed(poolId, msg.sender, claimable);
}
/// @notice 停用池
function deactivatePool(uint256 poolId) external onlyRole(GOVERNANCE_ROLE) {
_pools[poolId].active = false;
emit PoolDeactivated(poolId);
}
// ========================
// 查询
// ========================
/// @notice 获取池信息
function getPool(uint256 poolId) external view returns (
uint256 totalFaceValue,
uint256 totalShares,
uint256 soldShares,
uint256 maturityDate,
string memory creditRating,
address issuer,
bool active
) {
Pool storage p = _pools[poolId];
return (p.totalFaceValue, p.totalShares, p.soldShares, p.maturityDate, p.creditRating, p.issuer, p.active);
}
/// @notice 获取池中的券 IDs
function getPoolCouponIds(uint256 poolId) external view returns (uint256[] memory) {
return _pools[poolId].couponIds;
}
/// @notice 计算持有人可领取的收益
function getClaimableYield(uint256 poolId, address holder) external view returns (uint256) {
uint256 holderShares = shares[poolId][holder];
if (holderShares == 0 || _pools[poolId].totalShares == 0) return 0;
uint256 totalEntitled = (poolYield[poolId] * holderShares) / _pools[poolId].totalShares;
return totalEntitled - claimedYield[poolId][holder];
}
}