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