187 lines
7.1 KiB
Solidity
187 lines
7.1 KiB
Solidity
// 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";
|
|
|
|
/// @title Treasury — 资金托管合约
|
|
/// @notice 保障资金锁定、交易 Escrow、资金释放与费用分割
|
|
/// @dev 使用 Transparent Proxy 部署
|
|
contract Treasury is Initializable, AccessControlUpgradeable, ReentrancyGuardUpgradeable {
|
|
bytes32 public constant SETTLER_ROLE = keccak256("SETTLER_ROLE");
|
|
bytes32 public constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE");
|
|
|
|
IERC20 public stablecoin;
|
|
address public platformFeeCollector;
|
|
|
|
// 发行方保障资金(自愿缴纳,提升信用评级)
|
|
mapping(address => uint256) public guaranteeFunds;
|
|
// 发行方冻结的销售款(自愿,作为兑付保障)
|
|
mapping(address => uint256) public frozenSalesRevenue;
|
|
// Escrow 托管
|
|
mapping(uint256 => EscrowInfo) public escrows;
|
|
|
|
struct EscrowInfo {
|
|
address buyer;
|
|
uint256 amount;
|
|
bool released;
|
|
bool refunded;
|
|
}
|
|
|
|
// --- Events ---
|
|
event GuaranteeFundDeposited(address indexed issuer, uint256 amount);
|
|
event GuaranteeFundWithdrawn(address indexed issuer, uint256 amount);
|
|
event GuaranteeFundActivated(address indexed issuer, uint256 totalClaim);
|
|
event SalesRevenueFrozen(address indexed issuer, uint256 amount);
|
|
event SalesRevenueReleased(address indexed issuer, uint256 amount);
|
|
event FundsEscrowed(uint256 indexed orderId, address indexed buyer, uint256 amount);
|
|
event FundsReleased(uint256 indexed orderId, address indexed seller, uint256 amount, uint256 platformFee);
|
|
event FundsRefunded(uint256 indexed orderId, address indexed buyer, uint256 amount);
|
|
|
|
function initialize(
|
|
address _stablecoin,
|
|
address _platformFeeCollector,
|
|
address admin
|
|
) external initializer {
|
|
__AccessControl_init();
|
|
__ReentrancyGuard_init();
|
|
|
|
stablecoin = IERC20(_stablecoin);
|
|
platformFeeCollector = _platformFeeCollector;
|
|
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
_grantRole(SETTLER_ROLE, admin);
|
|
_grantRole(GOVERNANCE_ROLE, admin);
|
|
}
|
|
|
|
// ========================
|
|
// 保障资金管理
|
|
// ========================
|
|
|
|
/// @notice 发行方缴纳保障资金
|
|
function depositGuaranteeFund(uint256 amount) external nonReentrant {
|
|
require(amount > 0, "Treasury: zero amount");
|
|
stablecoin.transferFrom(msg.sender, address(this), amount);
|
|
guaranteeFunds[msg.sender] += amount;
|
|
emit GuaranteeFundDeposited(msg.sender, amount);
|
|
}
|
|
|
|
/// @notice 发行方提取保障资金(需治理审批)
|
|
function withdrawGuaranteeFund(address issuer, uint256 amount) external onlyRole(GOVERNANCE_ROLE) nonReentrant {
|
|
require(guaranteeFunds[issuer] >= amount, "Treasury: insufficient guarantee");
|
|
guaranteeFunds[issuer] -= amount;
|
|
stablecoin.transfer(issuer, amount);
|
|
emit GuaranteeFundWithdrawn(issuer, amount);
|
|
}
|
|
|
|
/// @notice 发行方违约时启用保障资金赔付
|
|
function activateGuaranteeFund(
|
|
address issuer,
|
|
address[] calldata claimants,
|
|
uint256[] calldata amounts
|
|
) external onlyRole(GOVERNANCE_ROLE) nonReentrant {
|
|
require(claimants.length == amounts.length, "Treasury: length mismatch");
|
|
|
|
uint256 totalClaim = 0;
|
|
for (uint256 i = 0; i < claimants.length; i++) {
|
|
totalClaim += amounts[i];
|
|
}
|
|
require(guaranteeFunds[issuer] >= totalClaim, "Treasury: insufficient guarantee fund");
|
|
|
|
guaranteeFunds[issuer] -= totalClaim;
|
|
for (uint256 i = 0; i < claimants.length; i++) {
|
|
stablecoin.transfer(claimants[i], amounts[i]);
|
|
}
|
|
|
|
emit GuaranteeFundActivated(issuer, totalClaim);
|
|
}
|
|
|
|
// ========================
|
|
// 销售款冻结
|
|
// ========================
|
|
|
|
/// @notice 发行方冻结销售款(自愿,作为兑付保障)
|
|
function freezeSalesRevenue(uint256 amount) external nonReentrant {
|
|
require(amount > 0, "Treasury: zero amount");
|
|
stablecoin.transferFrom(msg.sender, address(this), amount);
|
|
frozenSalesRevenue[msg.sender] += amount;
|
|
emit SalesRevenueFrozen(msg.sender, amount);
|
|
}
|
|
|
|
/// @notice 释放冻结销售款(需治理审批)
|
|
function releaseSalesRevenue(address issuer, uint256 amount) external onlyRole(GOVERNANCE_ROLE) nonReentrant {
|
|
require(frozenSalesRevenue[issuer] >= amount, "Treasury: insufficient frozen revenue");
|
|
frozenSalesRevenue[issuer] -= amount;
|
|
stablecoin.transfer(issuer, amount);
|
|
emit SalesRevenueReleased(issuer, amount);
|
|
}
|
|
|
|
// ========================
|
|
// 交易 Escrow
|
|
// ========================
|
|
|
|
/// @notice 交易资金托管(原子交换中间态)
|
|
function escrow(uint256 orderId, address buyer, uint256 amount) external onlyRole(SETTLER_ROLE) nonReentrant {
|
|
require(amount > 0, "Treasury: zero escrow");
|
|
require(escrows[orderId].amount == 0, "Treasury: escrow exists");
|
|
|
|
stablecoin.transferFrom(buyer, address(this), amount);
|
|
escrows[orderId] = EscrowInfo({
|
|
buyer: buyer,
|
|
amount: amount,
|
|
released: false,
|
|
refunded: false
|
|
});
|
|
|
|
emit FundsEscrowed(orderId, buyer, amount);
|
|
}
|
|
|
|
/// @notice 释放托管资金给卖方
|
|
function release(
|
|
uint256 orderId,
|
|
address seller,
|
|
uint256 amount,
|
|
uint256 platformFee
|
|
) external onlyRole(SETTLER_ROLE) nonReentrant {
|
|
EscrowInfo storage info = escrows[orderId];
|
|
require(info.amount > 0, "Treasury: no escrow");
|
|
require(!info.released && !info.refunded, "Treasury: already settled");
|
|
require(amount + platformFee <= info.amount, "Treasury: exceeds escrow");
|
|
|
|
info.released = true;
|
|
|
|
// 卖方收款
|
|
stablecoin.transfer(seller, amount);
|
|
// 平台手续费归集
|
|
if (platformFee > 0) {
|
|
stablecoin.transfer(platformFeeCollector, platformFee);
|
|
}
|
|
|
|
emit FundsReleased(orderId, seller, amount, platformFee);
|
|
}
|
|
|
|
/// @notice 退回托管资金给买方
|
|
function refundEscrow(uint256 orderId) external onlyRole(SETTLER_ROLE) nonReentrant {
|
|
EscrowInfo storage info = escrows[orderId];
|
|
require(info.amount > 0, "Treasury: no escrow");
|
|
require(!info.released && !info.refunded, "Treasury: already settled");
|
|
|
|
info.refunded = true;
|
|
stablecoin.transfer(info.buyer, info.amount);
|
|
|
|
emit FundsRefunded(orderId, info.buyer, info.amount);
|
|
}
|
|
|
|
// ========================
|
|
// 管理
|
|
// ========================
|
|
|
|
/// @notice 更新平台手续费收集地址
|
|
function setPlatformFeeCollector(address _collector) external onlyRole(GOVERNANCE_ROLE) {
|
|
require(_collector != address(0), "Treasury: zero address");
|
|
platformFeeCollector = _collector;
|
|
}
|
|
}
|