Files
smom-dbis-138/contracts/cw-settlement/CWNavOracle.sol

163 lines
6.2 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "../reserve/IReserveSystem.sol";
interface ICWNavBridgeL1 {
function lockedBalance(address token) external view returns (uint256);
function totalOutstanding(address token) external view returns (uint256);
}
/**
* @title CWNavOracle
* @notice Publishes reserve-backed NAV for canonical c* / global cW supply pairs.
* @dev Global cW supply is aggregated off-chain across mesh chains and pushed by OPERATOR_ROLE.
* On-chain locked/outstanding reads come from CWMultiTokenBridgeL1.
*/
contract CWNavOracle is AccessControl {
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
uint256 public constant BPS = 10_000;
uint256 public constant NAV_SCALE = 1e18;
struct TokenConfig {
bool enabled;
address reserveAsset;
}
struct NavSnapshot {
uint256 totalLockedAssets;
uint256 totalReserveAssets;
uint256 totalCwSupply;
uint256 backingRatioBps;
uint256 navPerShare;
uint256 chain138CollateralBps;
uint256 treasuryReservesBps;
uint256 protocolFeesBps;
bytes32 attestationHash;
uint256 updatedAt;
}
ICWNavBridgeL1 public bridge;
IReserveSystem public reserveSystem;
mapping(address => TokenConfig) public tokenConfigs;
mapping(address => uint256) public globalCwSupply;
mapping(address => NavSnapshot) public latestSnapshot;
event BridgeUpdated(address indexed newBridge);
event ReserveSystemUpdated(address indexed newReserveSystem);
event TokenConfigured(address indexed canonicalToken, address indexed reserveAsset);
event TokenDisabled(address indexed canonicalToken);
event NavPublished(address indexed canonicalToken, uint256 backingRatioBps, uint256 navPerShare, bytes32 attestationHash);
error ZeroAddress();
error TokenNotConfigured();
constructor(address admin, address bridge_, address reserveSystem_) {
if (admin == address(0) || bridge_ == address(0)) {
revert ZeroAddress();
}
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(OPERATOR_ROLE, admin);
bridge = ICWNavBridgeL1(bridge_);
reserveSystem = IReserveSystem(reserveSystem_);
}
function setBridge(address bridge_) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (bridge_ == address(0)) {
revert ZeroAddress();
}
bridge = ICWNavBridgeL1(bridge_);
emit BridgeUpdated(bridge_);
}
function setReserveSystem(address reserveSystem_) external onlyRole(DEFAULT_ADMIN_ROLE) {
reserveSystem = IReserveSystem(reserveSystem_);
emit ReserveSystemUpdated(reserveSystem_);
}
function configureToken(address canonicalToken, address reserveAsset) external onlyRole(OPERATOR_ROLE) {
if (canonicalToken == address(0)) {
revert ZeroAddress();
}
tokenConfigs[canonicalToken] = TokenConfig({enabled: true, reserveAsset: reserveAsset});
emit TokenConfigured(canonicalToken, reserveAsset);
}
function disableToken(address canonicalToken) external onlyRole(OPERATOR_ROLE) {
delete tokenConfigs[canonicalToken];
emit TokenDisabled(canonicalToken);
}
function publishNav(
address canonicalToken,
uint256 globalCwSupplyAmount,
uint256 chain138CollateralBps,
uint256 treasuryReservesBps,
uint256 protocolFeesBps,
bytes32 attestationHash
) external onlyRole(OPERATOR_ROLE) {
TokenConfig memory config = tokenConfigs[canonicalToken];
if (!config.enabled) {
revert TokenNotConfigured();
}
require(chain138CollateralBps + treasuryReservesBps + protocolFeesBps == BPS, "CWNavOracle: bad decomposition");
globalCwSupply[canonicalToken] = globalCwSupplyAmount;
uint256 locked = bridge.lockedBalance(canonicalToken);
uint256 reserveBalance = address(reserveSystem) == address(0) || config.reserveAsset == address(0)
? 0
: reserveSystem.getReserveBalance(config.reserveAsset);
uint256 totalAssets = locked + reserveBalance;
uint256 backingRatioBps = globalCwSupplyAmount == 0 ? 0 : (totalAssets * BPS) / globalCwSupplyAmount;
uint256 navPerShareValue = globalCwSupplyAmount == 0 ? 0 : (totalAssets * NAV_SCALE) / globalCwSupplyAmount;
NavSnapshot memory snap = NavSnapshot({
totalLockedAssets: locked,
totalReserveAssets: reserveBalance,
totalCwSupply: globalCwSupplyAmount,
backingRatioBps: backingRatioBps,
navPerShare: navPerShareValue,
chain138CollateralBps: chain138CollateralBps,
treasuryReservesBps: treasuryReservesBps,
protocolFeesBps: protocolFeesBps,
attestationHash: attestationHash,
updatedAt: block.timestamp
});
latestSnapshot[canonicalToken] = snap;
emit NavPublished(canonicalToken, backingRatioBps, navPerShareValue, attestationHash);
}
function totalLockedAssets(address canonicalToken) external view returns (uint256) {
return bridge.lockedBalance(canonicalToken);
}
function totalReserveAssets(address canonicalToken) external view returns (uint256) {
TokenConfig memory config = tokenConfigs[canonicalToken];
if (!config.enabled || address(reserveSystem) == address(0) || config.reserveAsset == address(0)) {
return 0;
}
return reserveSystem.getReserveBalance(config.reserveAsset);
}
function totalCWUSupply(address canonicalToken) external view returns (uint256) {
return globalCwSupply[canonicalToken];
}
function backingRatio(address canonicalToken) external view returns (uint256 backingRatioBps) {
return latestSnapshot[canonicalToken].backingRatioBps;
}
function navPerShare(address canonicalToken) external view returns (uint256) {
return latestSnapshot[canonicalToken].navPerShare;
}
function getNavSnapshot(address canonicalToken) external view returns (NavSnapshot memory) {
return latestSnapshot[canonicalToken];
}
}