From 4540ec44808676fa9ef143495a52ee004cb9144f Mon Sep 17 00:00:00 2001 From: defiQUG Date: Fri, 24 Apr 2026 22:06:26 -0700 Subject: [PATCH] feat: add universal resource policy profile registry --- .../PolicyProfileRegistry.sol | 64 +++++++++++++++++++ .../DeployPolicyProfileRegistry.s.sol | 21 ++++++ .../PolicyProfileRegistry.t.sol | 29 +++++++++ 3 files changed, 114 insertions(+) create mode 100644 contracts/universal-resource/PolicyProfileRegistry.sol create mode 100644 script/universal-resource/DeployPolicyProfileRegistry.s.sol create mode 100644 test/universal-resource/PolicyProfileRegistry.t.sol diff --git a/contracts/universal-resource/PolicyProfileRegistry.sol b/contracts/universal-resource/PolicyProfileRegistry.sol new file mode 100644 index 0000000..d585f47 --- /dev/null +++ b/contracts/universal-resource/PolicyProfileRegistry.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +/** + * @title PolicyProfileRegistry + * @notice Minimal on-chain anchor for URA `policyProfileId` rows (content hash + version + effective time). + * Pair with off-chain `config/universal-resource-activation/policy-profiles.json` and + * `scripts/ura/policy-profiles-content-hash.mjs` in the proxmox repo. Not a full GRU M00 diamond. + */ +contract PolicyProfileRegistry is AccessControl { + bytes32 public constant PUBLISHER_ROLE = keccak256("PUBLISHER_ROLE"); + + struct Record { + bytes32 contentHash; + uint64 version; + uint256 effectiveFrom; + bool exists; + } + + mapping(bytes32 => Record) private _records; + + event ProfilePublished( + bytes32 indexed profileKey, + string policyProfileId, + bytes32 contentHash, + uint64 version, + uint256 effectiveFrom + ); + + constructor(address admin) { + require(admin != address(0), "PolicyProfileRegistry: admin"); + _grantRole(DEFAULT_ADMIN_ROLE, admin); + _grantRole(PUBLISHER_ROLE, admin); + } + + function profileKey(string calldata policyProfileId) public pure returns (bytes32) { + return keccak256(bytes(policyProfileId)); + } + + /** + * @param contentHash keccak256 of canonical JSON for this profile row (see proxmox hash script) + */ + function publishProfile( + string calldata policyProfileId, + bytes32 contentHash, + uint64 version, + uint256 effectiveFrom + ) external onlyRole(PUBLISHER_ROLE) { + bytes32 key = profileKey(policyProfileId); + _records[key] = Record({ + contentHash: contentHash, + version: version, + effectiveFrom: effectiveFrom, + exists: true + }); + emit ProfilePublished(key, policyProfileId, contentHash, version, effectiveFrom); + } + + function getRecord(string calldata policyProfileId) external view returns (Record memory) { + return _records[profileKey(policyProfileId)]; + } +} diff --git a/script/universal-resource/DeployPolicyProfileRegistry.s.sol b/script/universal-resource/DeployPolicyProfileRegistry.s.sol new file mode 100644 index 0000000..0e8ea1e --- /dev/null +++ b/script/universal-resource/DeployPolicyProfileRegistry.s.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Script, console2} from "forge-std/Script.sol"; +import {PolicyProfileRegistry} from "../../contracts/universal-resource/PolicyProfileRegistry.sol"; + +/// @notice Deploy standalone URA policy profile anchor (not full GRU M00). Set POLICY_PROFILE_REGISTRY_ADMIN or defaults to deployer. +contract DeployPolicyProfileRegistry is Script { + function run() external { + uint256 pk = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(pk); + address admin = vm.envOr("POLICY_PROFILE_REGISTRY_ADMIN", deployer); + + vm.startBroadcast(pk); + PolicyProfileRegistry reg = new PolicyProfileRegistry(admin); + vm.stopBroadcast(); + + console2.log("PolicyProfileRegistry", address(reg)); + console2.log("admin", admin); + } +} diff --git a/test/universal-resource/PolicyProfileRegistry.t.sol b/test/universal-resource/PolicyProfileRegistry.t.sol new file mode 100644 index 0000000..c19ccf9 --- /dev/null +++ b/test/universal-resource/PolicyProfileRegistry.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {PolicyProfileRegistry} from "../../contracts/universal-resource/PolicyProfileRegistry.sol"; + +contract PolicyProfileRegistryTest is Test { + PolicyProfileRegistry internal reg; + address internal admin = address(0xA11CE); + + function setUp() public { + vm.prank(admin); + reg = new PolicyProfileRegistry(admin); + } + + function testPublishAndRead() public { + bytes32 h = keccak256("profile-json"); + vm.prank(admin); + reg.publishProfile("institutional_custody_skr_v1", h, 1, block.timestamp); + PolicyProfileRegistry.Record memory r = reg.getRecord("institutional_custody_skr_v1"); + assertTrue(r.exists); + assertEq(r.contentHash, h); + assertEq(r.version, 1); + } + + function testProfileKeyStable() public view { + assertEq(reg.profileKey("a"), keccak256(bytes("a"))); + } +}