// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; /** * @title GRUVaultIndex * @notice Canonical index of GRU vault instances across tiers, tokens, IBAN, and URA policy profiles. * @dev Written by VaultFactory (FACTORY_ROLE) or registrar during vault creation. */ contract GRUVaultIndex is AccessControl { bytes32 public constant FACTORY_ROLE = keccak256("FACTORY_ROLE"); bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE"); /// @dev GRU monetary tier: 0=M00, 1=M0, 2=M1 struct VaultRecord { address entity; address baseToken; address depositToken; address debtToken; uint8 gruTier; bytes32 ibanHash; bytes32 policyProfileKey; uint256 recordedAt; bool active; } mapping(address => VaultRecord) public vaults; address[] public allVaults; event VaultRecorded( address indexed vault, address indexed entity, address baseToken, address depositToken, address debtToken, uint8 gruTier, bytes32 ibanHash, bytes32 policyProfileKey ); event PolicyProfileKeyPatched(address indexed vault, bytes32 policyProfileKey); event VaultImported( address indexed vault, address indexed entity, address baseToken, bytes32 policyProfileKey ); constructor(address admin) { require(admin != address(0), "GRUVaultIndex: zero admin"); _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(REGISTRAR_ROLE, admin); } function grantFactoryRole(address factory) external onlyRole(DEFAULT_ADMIN_ROLE) { require(factory != address(0), "GRUVaultIndex: zero factory"); _grantRole(FACTORY_ROLE, factory); } function recordVault( address vault, address entity, address baseToken, address depositToken, address debtToken, uint8 gruTier, bytes32 ibanHash, bytes32 policyProfileKey ) external onlyRole(FACTORY_ROLE) { require(vault != address(0), "GRUVaultIndex: zero vault"); require(!vaults[vault].active, "GRUVaultIndex: exists"); vaults[vault] = VaultRecord({ entity: entity, baseToken: baseToken, depositToken: depositToken, debtToken: debtToken, gruTier: gruTier, ibanHash: ibanHash, policyProfileKey: policyProfileKey, recordedAt: block.timestamp, active: true }); allVaults.push(vault); emit VaultRecorded(vault, entity, baseToken, depositToken, debtToken, gruTier, ibanHash, policyProfileKey); } /// @notice Admin backfill when vaults were indexed before policy profile publish. function patchPolicyProfileKey(address vault, bytes32 policyProfileKey) external onlyRole(DEFAULT_ADMIN_ROLE) { require(vaults[vault].active, "GRUVaultIndex: unknown"); vaults[vault].policyProfileKey = policyProfileKey; emit PolicyProfileKeyPatched(vault, policyProfileKey); } /// @notice Registrar migration import (e.g. re-index with policy keys from a prior GRUVaultIndex). function importVault( address vault, address entity, address baseToken, address depositToken, address debtToken, uint8 gruTier, bytes32 ibanHash, bytes32 policyProfileKey, uint256 recordedAt ) external onlyRole(REGISTRAR_ROLE) { require(vault != address(0), "GRUVaultIndex: zero vault"); require(!vaults[vault].active, "GRUVaultIndex: exists"); vaults[vault] = VaultRecord({ entity: entity, baseToken: baseToken, depositToken: depositToken, debtToken: debtToken, gruTier: gruTier, ibanHash: ibanHash, policyProfileKey: policyProfileKey, recordedAt: recordedAt, active: true }); allVaults.push(vault); emit VaultImported(vault, entity, baseToken, policyProfileKey); } }