- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault. - Token-aggregation service routes, planner, chain config, relay env templates. - Config snapshots and multi-chain deployment markdown updates. - gitignore services/btc-intake/dist/ (tsc output); do not track dist. Run forge build && forge test before deploy (large solc graph). Made-with: Cursor
248 lines
9.9 KiB
Solidity
248 lines
9.9 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "@openzeppelin/contracts/access/AccessControl.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import "../../reserve/IReserveSystem.sol";
|
|
import "./ICWReserveVerifier.sol";
|
|
|
|
interface ICWMultiTokenBridgeL1AccountingV2 {
|
|
function supportedCanonicalToken(address token) external view returns (bool);
|
|
function paused(address token) external view returns (bool);
|
|
function lockedBalance(address token) external view returns (uint256);
|
|
function totalOutstanding(address token) external view returns (uint256);
|
|
function outstandingMinted(address token, uint64 destinationChainSelector) external view returns (uint256);
|
|
function maxOutstanding(address token, uint64 destinationChainSelector) external view returns (uint256);
|
|
}
|
|
|
|
interface IOwnableLikeV2 {
|
|
function owner() external view returns (address);
|
|
}
|
|
|
|
interface IBalanceOfAsset {
|
|
function balanceOf(address account) external view returns (uint256);
|
|
}
|
|
|
|
/**
|
|
* @title CWAssetReserveVerifier
|
|
* @notice Generic reserve verifier for canonical c* -> mirrored cW* bridge lanes.
|
|
* @dev Uses bridge accounting plus optional vault and reserve-system checks so one verifier
|
|
* can cover stable, monetary-unit, and gas-native families behind a single L1 bridge.
|
|
*/
|
|
contract CWAssetReserveVerifier is AccessControl, ICWReserveVerifier {
|
|
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
|
|
uint256 public constant HARD_THRESHOLD_BPS = 10_000;
|
|
|
|
struct TokenConfig {
|
|
bool enabled;
|
|
address reserveAsset;
|
|
bool requireVaultBacking;
|
|
bool requireReserveSystemBalance;
|
|
bool requireTokenOwnerMatchVault;
|
|
}
|
|
|
|
struct VerificationStatus {
|
|
bool supportedCanonicalToken;
|
|
bool bridgePaused;
|
|
bool escrowSufficient;
|
|
bool chainCapSufficient;
|
|
bool reserveSystemAdequate;
|
|
bool vaultAdequate;
|
|
bool tokenOwnerMatchesVault;
|
|
bool passes;
|
|
uint256 lockedBalanceAmount;
|
|
uint256 totalOutstandingAmount;
|
|
uint256 chainOutstandingAmount;
|
|
uint256 maxOutstandingAmount;
|
|
uint256 canonicalTotalSupply;
|
|
uint256 reserveSystemBalance;
|
|
uint256 vaultReserveBalance;
|
|
uint256 vaultBackingRatio;
|
|
}
|
|
|
|
ICWMultiTokenBridgeL1AccountingV2 public bridge;
|
|
address public assetVault;
|
|
IReserveSystem public reserveSystem;
|
|
|
|
mapping(address => TokenConfig) public tokenConfigs;
|
|
|
|
event BridgeUpdated(address indexed newBridge);
|
|
event AssetVaultUpdated(address indexed newVault);
|
|
event ReserveSystemUpdated(address indexed newReserveSystem);
|
|
event TokenConfigured(
|
|
address indexed canonicalToken,
|
|
address indexed reserveAsset,
|
|
bool requireVaultBacking,
|
|
bool requireReserveSystemBalance,
|
|
bool requireTokenOwnerMatchVault
|
|
);
|
|
event TokenDisabled(address indexed canonicalToken);
|
|
|
|
error ZeroAddress();
|
|
error TokenNotConfigured();
|
|
error VaultRequired();
|
|
error ReserveSystemRequired();
|
|
error ReserveAssetRequired();
|
|
error UnsupportedCanonicalToken();
|
|
error BridgeTokenPaused();
|
|
error EscrowInvariantViolation();
|
|
error ChainOutstandingCapExceeded();
|
|
error TokenOwnerMismatch();
|
|
error VaultBackingInsufficient();
|
|
error ReserveSystemBackingInsufficient();
|
|
|
|
constructor(
|
|
address admin,
|
|
address bridge_,
|
|
address assetVault_,
|
|
address reserveSystem_
|
|
) {
|
|
if (admin == address(0) || bridge_ == address(0)) revert ZeroAddress();
|
|
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
_grantRole(OPERATOR_ROLE, admin);
|
|
|
|
bridge = ICWMultiTokenBridgeL1AccountingV2(bridge_);
|
|
assetVault = assetVault_;
|
|
reserveSystem = IReserveSystem(reserveSystem_);
|
|
}
|
|
|
|
function setBridge(address bridge_) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
if (bridge_ == address(0)) revert ZeroAddress();
|
|
bridge = ICWMultiTokenBridgeL1AccountingV2(bridge_);
|
|
emit BridgeUpdated(bridge_);
|
|
}
|
|
|
|
function setAssetVault(address assetVault_) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
assetVault = assetVault_;
|
|
emit AssetVaultUpdated(assetVault_);
|
|
}
|
|
|
|
function setReserveSystem(address reserveSystem_) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
reserveSystem = IReserveSystem(reserveSystem_);
|
|
emit ReserveSystemUpdated(reserveSystem_);
|
|
}
|
|
|
|
function configureToken(
|
|
address canonicalToken,
|
|
address reserveAsset,
|
|
bool requireVaultBacking,
|
|
bool requireReserveSystemBalance,
|
|
bool requireTokenOwnerMatchVault
|
|
) external onlyRole(OPERATOR_ROLE) {
|
|
if (canonicalToken == address(0)) revert ZeroAddress();
|
|
if ((requireVaultBacking || requireTokenOwnerMatchVault) && assetVault == address(0)) revert VaultRequired();
|
|
if (requireReserveSystemBalance && address(reserveSystem) == address(0)) revert ReserveSystemRequired();
|
|
if ((requireVaultBacking || requireReserveSystemBalance) && reserveAsset == address(0)) revert ReserveAssetRequired();
|
|
|
|
tokenConfigs[canonicalToken] = TokenConfig({
|
|
enabled: true,
|
|
reserveAsset: reserveAsset,
|
|
requireVaultBacking: requireVaultBacking,
|
|
requireReserveSystemBalance: requireReserveSystemBalance,
|
|
requireTokenOwnerMatchVault: requireTokenOwnerMatchVault
|
|
});
|
|
|
|
emit TokenConfigured(
|
|
canonicalToken,
|
|
reserveAsset,
|
|
requireVaultBacking,
|
|
requireReserveSystemBalance,
|
|
requireTokenOwnerMatchVault
|
|
);
|
|
}
|
|
|
|
function disableToken(address canonicalToken) external onlyRole(OPERATOR_ROLE) {
|
|
delete tokenConfigs[canonicalToken];
|
|
emit TokenDisabled(canonicalToken);
|
|
}
|
|
|
|
function verifyLock(
|
|
address canonicalToken,
|
|
uint64 destinationChainSelector,
|
|
uint256
|
|
) external view override returns (bool verified) {
|
|
TokenConfig memory config = tokenConfigs[canonicalToken];
|
|
if (!config.enabled) revert TokenNotConfigured();
|
|
|
|
VerificationStatus memory status = _buildStatus(canonicalToken, destinationChainSelector, config);
|
|
|
|
if (!status.supportedCanonicalToken) revert UnsupportedCanonicalToken();
|
|
if (status.bridgePaused) revert BridgeTokenPaused();
|
|
if (!status.escrowSufficient) revert EscrowInvariantViolation();
|
|
if (!status.chainCapSufficient) revert ChainOutstandingCapExceeded();
|
|
if (config.requireTokenOwnerMatchVault && !status.tokenOwnerMatchesVault) revert TokenOwnerMismatch();
|
|
if (config.requireVaultBacking && !status.vaultAdequate) revert VaultBackingInsufficient();
|
|
if (config.requireReserveSystemBalance && !status.reserveSystemAdequate) revert ReserveSystemBackingInsufficient();
|
|
|
|
return true;
|
|
}
|
|
|
|
function getVerificationStatus(
|
|
address canonicalToken,
|
|
uint64 destinationChainSelector
|
|
) external view returns (VerificationStatus memory status) {
|
|
TokenConfig memory config = tokenConfigs[canonicalToken];
|
|
if (!config.enabled) revert TokenNotConfigured();
|
|
return _buildStatus(canonicalToken, destinationChainSelector, config);
|
|
}
|
|
|
|
function _buildStatus(
|
|
address canonicalToken,
|
|
uint64 destinationChainSelector,
|
|
TokenConfig memory config
|
|
) internal view returns (VerificationStatus memory status) {
|
|
status.supportedCanonicalToken = bridge.supportedCanonicalToken(canonicalToken);
|
|
status.bridgePaused = bridge.paused(canonicalToken);
|
|
status.lockedBalanceAmount = bridge.lockedBalance(canonicalToken);
|
|
status.totalOutstandingAmount = bridge.totalOutstanding(canonicalToken);
|
|
status.chainOutstandingAmount = bridge.outstandingMinted(canonicalToken, destinationChainSelector);
|
|
status.maxOutstandingAmount = bridge.maxOutstanding(canonicalToken, destinationChainSelector);
|
|
status.escrowSufficient = status.lockedBalanceAmount >= status.totalOutstandingAmount;
|
|
status.chainCapSufficient =
|
|
status.maxOutstandingAmount == 0 || status.chainOutstandingAmount <= status.maxOutstandingAmount;
|
|
status.canonicalTotalSupply = IERC20(canonicalToken).totalSupply();
|
|
|
|
status.reserveSystemAdequate = !config.requireReserveSystemBalance;
|
|
if (config.requireReserveSystemBalance) {
|
|
status.reserveSystemBalance = reserveSystem.getReserveBalance(config.reserveAsset);
|
|
status.reserveSystemAdequate = status.reserveSystemBalance >= status.canonicalTotalSupply;
|
|
}
|
|
|
|
status.vaultAdequate = !config.requireVaultBacking;
|
|
status.tokenOwnerMatchesVault = !config.requireTokenOwnerMatchVault;
|
|
|
|
if (config.requireTokenOwnerMatchVault) {
|
|
try IOwnableLikeV2(canonicalToken).owner() returns (address tokenOwner) {
|
|
status.tokenOwnerMatchesVault = tokenOwner == assetVault;
|
|
} catch {
|
|
status.tokenOwnerMatchesVault = false;
|
|
}
|
|
}
|
|
|
|
if (config.requireVaultBacking) {
|
|
try IBalanceOfAsset(config.reserveAsset).balanceOf(assetVault) returns (uint256 reserveBalance) {
|
|
status.vaultReserveBalance = reserveBalance;
|
|
if (status.canonicalTotalSupply > 0) {
|
|
status.vaultBackingRatio = (reserveBalance * HARD_THRESHOLD_BPS) / status.canonicalTotalSupply;
|
|
} else {
|
|
status.vaultBackingRatio = HARD_THRESHOLD_BPS;
|
|
}
|
|
status.vaultAdequate =
|
|
status.vaultBackingRatio >= HARD_THRESHOLD_BPS && reserveBalance >= status.canonicalTotalSupply;
|
|
} catch {
|
|
status.vaultAdequate = false;
|
|
}
|
|
}
|
|
|
|
status.passes =
|
|
status.supportedCanonicalToken &&
|
|
!status.bridgePaused &&
|
|
status.escrowSufficient &&
|
|
status.chainCapSufficient &&
|
|
status.tokenOwnerMatchesVault &&
|
|
status.vaultAdequate &&
|
|
status.reserveSystemAdequate;
|
|
}
|
|
}
|