Files
smom-dbis-138/test/integration/JurisdictionalGovernance.t.sol

326 lines
12 KiB
Solidity

// 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"));
}
}