Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m20s
CI/CD Pipeline / Security Scanning (push) Successful in 2m48s
CI/CD Pipeline / Lint and Format (push) Failing after 47s
CI/CD Pipeline / Terraform Validation (push) Failing after 28s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 30s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 44s
Validation / validate-genesis (push) Successful in 37s
Validation / validate-terraform (push) Failing after 42s
Validation / validate-kubernetes (push) Failing after 12s
Validation / validate-smart-contracts (push) Failing after 16m44s
Validation / validate-security (push) Failing after 20s
Validation / validate-documentation (push) Failing after 19s
Verify Deployment / Verify Deployment (push) Failing after 1m21s
Use dedicated cusdc_v2/cusdt_v2 SVG paths so GRU token alignment validation passes.
220 lines
8.1 KiB
Solidity
220 lines
8.1 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "@openzeppelin/contracts/access/AccessControl.sol";
|
|
import "./interfaces/IPolicyManager.sol";
|
|
import "./interfaces/IComplianceRegistry.sol";
|
|
import "./interfaces/IDebtRegistry.sol";
|
|
import "./libraries/ReasonCodes.sol";
|
|
import "./errors/RegistryErrors.sol";
|
|
|
|
/**
|
|
* @title PolicyManager
|
|
* @notice Central rule engine for transfer authorization across all eMoney tokens
|
|
* @dev Consults ComplianceRegistry and DebtRegistry to make transfer decisions.
|
|
* Manages per-token configuration including pause state, bridge-only mode, and lien modes.
|
|
* Lien enforcement is performed by eMoneyToken based on the lien mode returned here.
|
|
*/
|
|
contract PolicyManager is IPolicyManager, AccessControl {
|
|
bytes32 public constant POLICY_OPERATOR_ROLE = keccak256("POLICY_OPERATOR_ROLE");
|
|
|
|
struct TokenConfig {
|
|
bool paused;
|
|
bool bridgeOnly;
|
|
address bridge;
|
|
uint8 lienMode; // 0 = off, 1 = hard freeze, 2 = encumbered
|
|
}
|
|
|
|
IComplianceRegistry public immutable complianceRegistry;
|
|
IDebtRegistry public immutable debtRegistry;
|
|
|
|
mapping(address => TokenConfig) private _tokenConfigs;
|
|
mapping(address => mapping(address => bool)) private _tokenFreezes; // token => account => frozen
|
|
|
|
/**
|
|
* @notice Initializes PolicyManager with registry addresses
|
|
* @param admin Address that will receive DEFAULT_ADMIN_ROLE
|
|
* @param compliance Address of ComplianceRegistry contract
|
|
* @param debt Address of DebtRegistry contract
|
|
*/
|
|
constructor(address admin, address compliance, address debt) {
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
complianceRegistry = IComplianceRegistry(compliance);
|
|
debtRegistry = IDebtRegistry(debt);
|
|
}
|
|
|
|
/**
|
|
* @notice Returns whether a token is paused
|
|
* @param token Token address to check
|
|
* @return true if token is paused, false otherwise
|
|
*/
|
|
function isPaused(address token) external view override returns (bool) {
|
|
return _tokenConfigs[token].paused;
|
|
}
|
|
|
|
/**
|
|
* @notice Returns whether a token is in bridge-only mode
|
|
* @param token Token address to check
|
|
* @return true if token only allows transfers to/from bridge, false otherwise
|
|
*/
|
|
function bridgeOnly(address token) external view override returns (bool) {
|
|
return _tokenConfigs[token].bridgeOnly;
|
|
}
|
|
|
|
/**
|
|
* @notice Returns the bridge address for a token
|
|
* @param token Token address to check
|
|
* @return Bridge address (zero address if not set)
|
|
*/
|
|
function bridge(address token) external view override returns (address) {
|
|
return _tokenConfigs[token].bridge;
|
|
}
|
|
|
|
/**
|
|
* @notice Returns the lien mode for a token
|
|
* @param token Token address to check
|
|
* @return Lien mode: 0 = off, 1 = hard freeze, 2 = encumbered
|
|
*/
|
|
function lienMode(address token) external view override returns (uint8) {
|
|
return _tokenConfigs[token].lienMode;
|
|
}
|
|
|
|
/**
|
|
* @notice Returns whether an account is frozen for a specific token
|
|
* @param token Token address to check
|
|
* @param account Address to check
|
|
* @return true if account is frozen for this token, false otherwise
|
|
*/
|
|
function isTokenFrozen(address token, address account) external view override returns (bool) {
|
|
return _tokenFreezes[token][account];
|
|
}
|
|
|
|
/**
|
|
* @notice Determines if a transfer should be allowed
|
|
* @dev Checks in order: paused, token freezes, compliance freezes, compliance allowed status, bridge-only mode.
|
|
* Lien checks are performed by eMoneyToken based on lien mode.
|
|
* @param token Token address
|
|
* @param from Sender address
|
|
* @param to Recipient address
|
|
* @param amount Transfer amount (unused but required for interface)
|
|
* @return allowed true if transfer should be allowed, false otherwise
|
|
* @return reasonCode bytes32 reason code (ReasonCodes.OK if allowed, otherwise the blocking reason)
|
|
*/
|
|
function canTransfer(
|
|
address token,
|
|
address from,
|
|
address to,
|
|
uint256 amount
|
|
) external view override returns (bool allowed, bytes32 reasonCode) {
|
|
TokenConfig memory config = _tokenConfigs[token];
|
|
|
|
// Check paused
|
|
if (config.paused) {
|
|
return (false, ReasonCodes.PAUSED);
|
|
}
|
|
|
|
// Check token-specific freezes
|
|
if (_tokenFreezes[token][from]) {
|
|
return (false, ReasonCodes.FROM_FROZEN);
|
|
}
|
|
if (_tokenFreezes[token][to]) {
|
|
return (false, ReasonCodes.TO_FROZEN);
|
|
}
|
|
|
|
// Check compliance registry freezes
|
|
if (complianceRegistry.isFrozen(from)) {
|
|
return (false, ReasonCodes.FROM_FROZEN);
|
|
}
|
|
if (complianceRegistry.isFrozen(to)) {
|
|
return (false, ReasonCodes.TO_FROZEN);
|
|
}
|
|
|
|
// Check compliance allowed status
|
|
if (!complianceRegistry.isAllowed(from)) {
|
|
return (false, ReasonCodes.FROM_NOT_COMPLIANT);
|
|
}
|
|
if (!complianceRegistry.isAllowed(to)) {
|
|
return (false, ReasonCodes.TO_NOT_COMPLIANT);
|
|
}
|
|
|
|
// Check bridgeOnly mode
|
|
if (config.bridgeOnly) {
|
|
if (from != config.bridge && to != config.bridge) {
|
|
return (false, ReasonCodes.BRIDGE_ONLY);
|
|
}
|
|
}
|
|
|
|
// Lien mode checks are handled in eMoneyToken._update
|
|
// PolicyManager only provides the lien mode, not the enforcement
|
|
|
|
return (true, ReasonCodes.OK);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the paused state for a token
|
|
* @dev Requires POLICY_OPERATOR_ROLE. When paused, all transfers are blocked.
|
|
* @param token Token address to configure
|
|
* @param paused true to pause, false to unpause
|
|
*/
|
|
function setPaused(address token, bool paused) external override onlyRole(POLICY_OPERATOR_ROLE) {
|
|
_tokenConfigs[token].paused = paused;
|
|
emit TokenPaused(token, paused);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets bridge-only mode for a token
|
|
* @dev Requires POLICY_OPERATOR_ROLE. When enabled, only transfers to/from the bridge address are allowed.
|
|
* @param token Token address to configure
|
|
* @param enabled true to enable bridge-only mode, false to disable
|
|
*/
|
|
function setBridgeOnly(address token, bool enabled) external override onlyRole(POLICY_OPERATOR_ROLE) {
|
|
_tokenConfigs[token].bridgeOnly = enabled;
|
|
emit BridgeOnlySet(token, enabled);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the bridge address for a token
|
|
* @dev Requires POLICY_OPERATOR_ROLE. Used in bridge-only mode.
|
|
* @param token Token address to configure
|
|
* @param bridgeAddr Address of the bridge contract
|
|
*/
|
|
function setBridge(address token, address bridgeAddr) external override onlyRole(POLICY_OPERATOR_ROLE) {
|
|
_tokenConfigs[token].bridge = bridgeAddr;
|
|
emit BridgeSet(token, bridgeAddr);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the lien mode for a token
|
|
* @dev Requires POLICY_OPERATOR_ROLE. Valid modes: 0 = off, 1 = hard freeze, 2 = encumbered.
|
|
* @param token Token address to configure
|
|
* @param mode Lien mode (0, 1, or 2)
|
|
*/
|
|
function setLienMode(address token, uint8 mode) external override onlyRole(POLICY_OPERATOR_ROLE) {
|
|
if (mode > 2) revert PolicyInvalidLienMode(mode);
|
|
|
|
TokenConfig storage config = _tokenConfigs[token];
|
|
bool isNewToken = config.lienMode == 0 && mode != 0 && !config.paused && !config.bridgeOnly && config.bridge == address(0);
|
|
|
|
config.lienMode = mode;
|
|
|
|
if (isNewToken) {
|
|
emit TokenConfigured(token, config.paused, config.bridgeOnly, config.bridge, mode);
|
|
}
|
|
|
|
emit LienModeSet(token, mode);
|
|
}
|
|
|
|
/**
|
|
* @notice Freezes or unfreezes an account for a specific token
|
|
* @dev Requires POLICY_OPERATOR_ROLE. Per-token freeze (in addition to global compliance freezes).
|
|
* @param token Token address
|
|
* @param account Address to freeze/unfreeze
|
|
* @param frozen true to freeze, false to unfreeze
|
|
*/
|
|
function freeze(address token, address account, bool frozen) external override onlyRole(POLICY_OPERATOR_ROLE) {
|
|
_tokenFreezes[token][account] = frozen;
|
|
emit TokenFreeze(token, account, frozen);
|
|
}
|
|
}
|
|
|