// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; import "../../contracts/registry/UniversalAssetRegistry.sol"; import "../../contracts/governance/GovernanceController.sol"; import "../../contracts/tokens/CompliantFiatTokenV2.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; contract JurisdictionalGovernanceTest is Test { UniversalAssetRegistry internal registry; GovernanceController internal governance; CompliantFiatTokenV2 internal token; address internal admin; address internal authority; address internal transitionAuthority; function setUp() public { admin = makeAddr("admin"); authority = makeAddr("authority"); transitionAuthority = makeAddr("transitionAuthority"); vm.startPrank(admin); UniversalAssetRegistry registryImpl = new UniversalAssetRegistry(); bytes memory registryInit = abi.encodeCall(UniversalAssetRegistry.initialize, (admin)); ERC1967Proxy registryProxy = new ERC1967Proxy(address(registryImpl), registryInit); registry = UniversalAssetRegistry(address(registryProxy)); GovernanceController governanceImpl = new GovernanceController(); bytes memory governanceInit = abi.encodeCall(GovernanceController.initialize, (address(registry), admin)); ERC1967Proxy governanceProxy = new ERC1967Proxy(address(governanceImpl), governanceInit); governance = GovernanceController(address(governanceProxy)); token = new CompliantFiatTokenV2( "Euro Coin (Compliant V2)", "cEURC", 6, "EUR", "2", admin, admin, 0, true ); registry.setGovernanceController(address(governance)); token.setGovernanceController(address(governance)); token.emergencySetGovernanceMetadata( token.governanceProfileId(), token.supervisionProfileId(), token.storageNamespace(), "EU", address(0), true, true, 14 days ); registry.emergencySetJurisdictionProfile( "EU", true, true, true, 14 days, "ipfs://eu-supervision", keccak256("eu-policy") ); registry.emergencySetJurisdictionProfile( "SG", true, true, true, 21 days, "ipfs://sg-supervision", keccak256("sg-policy") ); registry.emergencySetJurisdictionAuthority("EU", authority, true, true, true, true, true); registry.emergencySetJurisdictionAuthority("SG", transitionAuthority, true, true, true, true, true); registry.addValidator(authority); registry.registerGRUCompliantAsset(address(token), "Euro Coin (Compliant V2)", "cEURC", 6, "EU"); vm.stopPrank(); } function testRegistryPullsGovernanceMetadataFromTokenAndJurisdiction() public view { UniversalAssetRegistry.UniversalAsset memory asset = registry.getAsset(address(token)); assertEq(asset.jurisdiction, "EU"); assertEq(asset.assetId, token.assetId()); assertEq(asset.assetVersionId, token.assetVersionId()); assertEq(asset.governanceProfileId, token.governanceProfileId()); assertEq(asset.supervisionProfileId, token.supervisionProfileId()); assertEq(asset.storageNamespace, token.storageNamespace()); assertTrue(asset.supervisionRequired); assertTrue(asset.governmentApprovalRequired); assertEq(asset.minimumUpgradeNoticePeriod, 14 days); } function testQueueRequiresJurisdictionApprovalAndUsesJurisdictionNoticePeriod() public { address[] memory targets = new address[](1); uint256[] memory values = new uint256[](1); bytes[] memory calldatas = new bytes[](1); bytes32 updatedGovernanceProfile = keccak256("eu-governance-v2"); targets[0] = address(token); values[0] = 0; calldatas[0] = abi.encodeWithSelector( CompliantFiatTokenV2.setGovernanceProfileId.selector, updatedGovernanceProfile ); vm.prank(admin); uint256 proposalId = governance.proposeForAsset( address(token), targets, values, calldatas, "Update EU governance metadata", GovernanceController.GovernanceMode.TimelockShort ); assertEq(governance.proposalAssets(proposalId), address(token)); vm.roll(block.number + governance.votingDelay() + 1); vm.prank(authority); governance.castVote(proposalId, 1); vm.roll(block.number + governance.votingPeriod() + 1); vm.expectRevert("Primary jurisdiction approval required"); governance.queue(proposalId); vm.prank(authority); governance.approveProposalJurisdiction(proposalId); uint256 beforeQueue = block.timestamp; governance.queue(proposalId); uint256 eta = governance.getProposalEta(proposalId); assertGe(eta, beforeQueue + 14 days); assertEq(governance.proposalJurisdictionApprovalCount(proposalId), 1); assertEq(governance.proposalLastJurisdictionApprover(proposalId), authority); vm.warp(eta); governance.execute(proposalId); assertEq(token.governanceProfileId(), updatedGovernanceProfile); } function testAssetScopedProposalRejectsRegistryCallsForAnotherAsset() public { address[] memory targets = new address[](1); uint256[] memory values = new uint256[](1); bytes[] memory calldatas = new bytes[](1); targets[0] = address(registry); values[0] = 0; calldatas[0] = abi.encodeWithSelector( UniversalAssetRegistry.syncAssetMetadataFromToken.selector, address(0xBEEF) ); vm.prank(admin); vm.expectRevert("Registry asset mismatch"); governance.proposeForAsset( address(token), targets, values, calldatas, "Attempt mismatched registry sync", GovernanceController.GovernanceMode.TimelockShort ); } function testRegistryMetadataRequiresGovernanceButEmergencyPathRemainsAvailable() public { vm.prank(admin); vm.expectRevert(); registry.setAssetGovernanceProfile( address(token), keccak256("blocked-gov-profile"), keccak256("blocked-sup-profile"), keccak256("blocked-storage"), address(0x1234), false, true, true, 21 days, "ipfs://blocked-disclosure", "ipfs://blocked-reporting" ); vm.prank(admin); registry.emergencySetAssetGovernanceProfile( address(token), keccak256("manual-gov-profile"), keccak256("manual-sup-profile"), keccak256("manual-storage"), address(0x1234), false, true, true, 21 days, "ipfs://manual-disclosure", "ipfs://manual-reporting" ); UniversalAssetRegistry.UniversalAsset memory asset = registry.getAsset(address(token)); assertEq(asset.governanceProfileId, keccak256("manual-gov-profile")); assertEq(asset.supervisionProfileId, keccak256("manual-sup-profile")); assertEq(asset.storageNamespace, keccak256("manual-storage")); assertEq(asset.canonicalUnderlyingAsset, address(0x1234)); assertEq(asset.regulatoryDisclosureURI, "ipfs://manual-disclosure"); assertEq(asset.reportingURI, "ipfs://manual-reporting"); assertEq(asset.minimumUpgradeNoticePeriod, 21 days); } function testJurisdictionProfileCanBeManagedThroughAssetScopedGovernance() public { address[] memory targets = new address[](1); uint256[] memory values = new uint256[](1); bytes[] memory calldatas = new bytes[](1); targets[0] = address(registry); values[0] = 0; calldatas[0] = abi.encodeWithSelector( UniversalAssetRegistry.setDerivedJurisdictionProfile.selector, address(token), true, true, true, 30 days, "ipfs://eu-supervision-v2", keccak256("eu-policy-v2") ); vm.prank(admin); uint256 proposalId = governance.proposeForAsset( address(token), targets, values, calldatas, "Update derived EU jurisdiction profile", GovernanceController.GovernanceMode.TimelockShort ); vm.roll(block.number + governance.votingDelay() + 1); vm.prank(authority); governance.castVote(proposalId, 1); vm.roll(block.number + governance.votingPeriod() + 1); vm.prank(authority); governance.approveProposalJurisdiction(proposalId); governance.queue(proposalId); vm.warp(governance.getProposalEta(proposalId)); governance.execute(proposalId); UniversalAssetRegistry.JurisdictionProfile memory profile = registry.getJurisdictionProfile("EU"); assertEq(profile.minimumUpgradeNoticePeriod, 30 days); assertEq(profile.supervisionURI, "ipfs://eu-supervision-v2"); assertEq(profile.policyHash, keccak256("eu-policy-v2")); } function testJurisdictionTransitionRequiresCurrentAndDestinationApprovals() public { address[] memory targets = new address[](2); uint256[] memory values = new uint256[](2); bytes[] memory calldatas = new bytes[](2); targets[0] = address(token); values[0] = 0; calldatas[0] = abi.encodeWithSelector( CompliantFiatTokenV2.setPrimaryJurisdiction.selector, "SG" ); targets[1] = address(registry); values[1] = 0; calldatas[1] = abi.encodeWithSelector( UniversalAssetRegistry.syncAssetMetadataFromToken.selector, address(token) ); vm.prank(admin); uint256 proposalId = governance.proposeForAsset( address(token), targets, values, calldatas, "Move asset jurisdiction to SG", GovernanceController.GovernanceMode.TimelockShort ); assertEq(governance.proposalTransitionJurisdictionIds(proposalId), registry.jurisdictionIdFor("SG")); vm.roll(block.number + governance.votingDelay() + 1); vm.prank(authority); governance.castVote(proposalId, 1); vm.roll(block.number + governance.votingPeriod() + 1); vm.prank(authority); governance.approveProposalJurisdiction(proposalId); vm.expectRevert("Transition jurisdiction approval required"); governance.queue(proposalId); vm.prank(transitionAuthority); governance.approveProposalJurisdiction(proposalId); uint256 beforeQueue = block.timestamp; governance.queue(proposalId); assertTrue(governance.hasJurisdictionApproval(proposalId, authority)); assertTrue(governance.hasTransitionJurisdictionApproval(proposalId, transitionAuthority)); assertEq(governance.proposalTransitionJurisdictionApprovalCount(proposalId), 1); assertEq(governance.proposalLastTransitionJurisdictionApprover(proposalId), transitionAuthority); assertGe(governance.getProposalEta(proposalId), beforeQueue + 21 days); vm.warp(governance.getProposalEta(proposalId)); governance.execute(proposalId); UniversalAssetRegistry.UniversalAsset memory asset = registry.getAsset(address(token)); assertEq(token.primaryJurisdiction(), "SG"); assertEq(asset.jurisdiction, "SG"); assertEq(registry.getAssetJurisdictionId(address(token)), registry.jurisdictionIdFor("SG")); } }