// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; /** * @title CWStabilityFund * @notice Insurance / surplus buffer for bridge, oracle, and temporary depeg events. */ contract CWStabilityFund is AccessControl, ReentrancyGuard { using SafeERC20 for IERC20; bytes32 public constant EMERGENCY_ROLE = keccak256("EMERGENCY_ROLE"); bytes32 public constant DEPOSITOR_ROLE = keccak256("DEPOSITOR_ROLE"); mapping(address => bool) public supportedAsset; event AssetSupported(address indexed asset, bool supported); event Deposited(address indexed asset, address indexed from, uint256 amount); event EmergencyWithdrawn(address indexed asset, address indexed to, uint256 amount, bytes32 reason); error ZeroAddress(); error UnsupportedAsset(); constructor(address admin) { if (admin == address(0)) { revert ZeroAddress(); } _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(EMERGENCY_ROLE, admin); _grantRole(DEPOSITOR_ROLE, admin); } function setSupportedAsset(address asset, bool supported) external onlyRole(DEFAULT_ADMIN_ROLE) { supportedAsset[asset] = supported; emit AssetSupported(asset, supported); } function deposit(address asset, uint256 amount) external onlyRole(DEPOSITOR_ROLE) nonReentrant { if (!supportedAsset[asset]) { revert UnsupportedAsset(); } IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); emit Deposited(asset, msg.sender, amount); } function emergencyWithdraw( address asset, address to, uint256 amount, bytes32 reason ) external onlyRole(EMERGENCY_ROLE) nonReentrant { if (to == address(0)) { revert ZeroAddress(); } IERC20(asset).safeTransfer(to, amount); emit EmergencyWithdrawn(asset, to, amount, reason); } function balanceOf(address asset) external view returns (uint256) { return IERC20(asset).balanceOf(address(this)); } }