// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "../interop/BridgeEscrowVault.sol"; import "../../iso4217w/interfaces/IISO4217WToken.sol"; import "../../iso4217w/oracle/ReserveOracle.sol"; /** * @title WTokenReserveVerifier * @notice Verifies W token reserves before allowing bridge operations * @dev Ensures 1:1 backing maintained across bridge operations */ contract WTokenReserveVerifier is AccessControl { bytes32 public constant VERIFIER_ROLE = keccak256("VERIFIER_ROLE"); bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); BridgeEscrowVault public bridgeEscrowVault; ReserveOracle public reserveOracle; mapping(address => bool) public verifiedTokens; // W token => verified // Reserve verification threshold (10000 = 100%) uint256 public reserveThreshold = 10000; // 100% (must be fully backed) event TokenVerified(address indexed token, bool verified); event ReserveVerified( address indexed token, uint256 reserve, uint256 supply, uint256 bridgeAmount, bool sufficient ); error InsufficientReserve(); error TokenNotVerified(); error InvalidToken(); constructor( address admin, address bridgeEscrowVault_, address reserveOracle_ ) { _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(VERIFIER_ROLE, admin); _grantRole(OPERATOR_ROLE, admin); require(bridgeEscrowVault_ != address(0), "WTokenReserveVerifier: zero bridge"); require(reserveOracle_ != address(0), "WTokenReserveVerifier: zero oracle"); bridgeEscrowVault = BridgeEscrowVault(bridgeEscrowVault_); reserveOracle = ReserveOracle(reserveOracle_); } /** * @notice Verify reserve before bridge operation * @param token W token address * @param bridgeAmount Amount to bridge * @return verified True if reserve is sufficient */ function verifyReserveBeforeBridge( address token, uint256 bridgeAmount ) external returns (bool verified) { if (!verifiedTokens[token]) revert TokenNotVerified(); IISO4217WToken wToken = IISO4217WToken(token); uint256 verifiedReserve = wToken.verifiedReserve(); uint256 currentSupply = wToken.totalSupply(); // After bridge, supply on this chain decreases, but reserve must still cover remaining supply // Reserve must be >= (currentSupply - bridgeAmount) * reserveThreshold / 10000 uint256 requiredReserve = ((currentSupply - bridgeAmount) * reserveThreshold) / 10000; verified = verifiedReserve >= requiredReserve; if (!verified) revert InsufficientReserve(); emit ReserveVerified(token, verifiedReserve, currentSupply, bridgeAmount, verified); } /** * @notice Verify reserve on destination chain (after bridge) * @param token W token address on destination chain * @param bridgeAmount Amount bridged * @param destinationReserve Reserve on destination chain * @param destinationSupply Supply on destination chain (before minting bridged amount) * @return verified True if destination reserve is sufficient */ function verifyDestinationReserve( address token, uint256 bridgeAmount, uint256 destinationReserve, uint256 destinationSupply ) external returns (bool verified) { // After minting bridged amount on destination: newSupply = destinationSupply + bridgeAmount // Required reserve: (newSupply * reserveThreshold) / 10000 uint256 newSupply = destinationSupply + bridgeAmount; uint256 requiredReserve = (newSupply * reserveThreshold) / 10000; verified = destinationReserve >= requiredReserve; if (!verified) revert InsufficientReserve(); emit ReserveVerified(token, destinationReserve, newSupply, bridgeAmount, verified); } /** * @notice Verify reserve sufficiency using oracle * @param token W token address * @param bridgeAmount Amount to bridge * @return verified True if reserve is sufficient according to oracle */ function verifyReserveWithOracle( address token, uint256 bridgeAmount ) external returns (bool verified) { if (!verifiedTokens[token]) revert TokenNotVerified(); IISO4217WToken wToken = IISO4217WToken(token); // Get currency code from token string memory currencyCode = wToken.currencyCode(); // Get verified reserve from oracle (consensus) (uint256 verifiedReserve, ) = reserveOracle.getVerifiedReserve(currencyCode); uint256 currentSupply = wToken.totalSupply(); // Required reserve after bridge uint256 requiredReserve = ((currentSupply - bridgeAmount) * reserveThreshold) / 10000; verified = verifiedReserve >= requiredReserve; if (!verified) revert InsufficientReserve(); emit ReserveVerified(token, verifiedReserve, currentSupply, bridgeAmount, verified); } /** * @notice Register a W token for reserve verification * @param token W token address */ function registerToken(address token) external onlyRole(OPERATOR_ROLE) { require(token != address(0), "WTokenReserveVerifier: zero token"); // Verify it's a valid W token (implements IISO4217WToken) try IISO4217WToken(token).currencyCode() returns (string memory) { verifiedTokens[token] = true; emit TokenVerified(token, true); } catch { revert InvalidToken(); } } /** * @notice Unregister a W token * @param token W token address */ function unregisterToken(address token) external onlyRole(OPERATOR_ROLE) { verifiedTokens[token] = false; emit TokenVerified(token, false); } /** * @notice Set reserve verification threshold * @param threshold Threshold in basis points (10000 = 100%) */ function setReserveThreshold(uint256 threshold) external onlyRole(DEFAULT_ADMIN_ROLE) { require(threshold <= 10000, "WTokenReserveVerifier: threshold > 100%"); require(threshold >= 10000, "WTokenReserveVerifier: threshold must be 100%"); // Hard requirement reserveThreshold = threshold; } /** * @notice Check if token is verified */ function isTokenVerified(address token) external view returns (bool) { return verifiedTokens[token]; } }