Files
smom-dbis-138/test/compliance/CompliantFiatTokenV2.t.sol
defiQUG 76aa419320 feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- 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
2026-04-07 23:40:52 -07:00

386 lines
13 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {CompliantFiatTokenV2} from "../../contracts/tokens/CompliantFiatTokenV2.sol";
contract CompliantFiatTokenV2Test is Test {
using ECDSA for bytes32;
bytes32 private constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 private constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH =
keccak256(
"TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
);
bytes32 private constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH =
keccak256(
"ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
);
bytes32 private constant CANCEL_AUTHORIZATION_TYPEHASH =
keccak256("CancelAuthorization(address authorizer,bytes32 nonce)");
event CompliantOperationDeclared(
bytes32 indexed operationType,
address indexed initiator,
address indexed authorizer,
address executor,
address from,
address to,
uint256 value,
bytes32 reasonHash,
bytes32 accountingRef,
bytes32 messageCorrelationId,
bytes32 legalReferenceHash
);
CompliantFiatTokenV2 internal token;
uint256 internal ownerPk;
uint256 internal adminPk;
address internal owner;
address internal admin;
address internal spender;
address internal recipient;
address internal burner;
address internal governanceExecutor;
function setUp() public {
ownerPk = 0xA11CE;
adminPk = 0xB0B;
owner = vm.addr(ownerPk);
admin = vm.addr(adminPk);
spender = address(0xCAFE);
recipient = address(0xBEEF);
burner = address(0xD00D);
governanceExecutor = address(0x7777);
token = new CompliantFiatTokenV2(
"Euro Coin (Compliant V2)",
"cEURC",
6,
"EUR",
"2",
owner,
admin,
1_000_000 * 10 ** 6,
true
);
bytes32 burnerRole = token.BURNER_ROLE();
vm.prank(admin);
token.grantRole(burnerRole, burner);
vm.prank(admin);
token.setGovernanceController(governanceExecutor);
}
function testPermitWorks() public {
uint256 value = 25_000 * 10 ** 6;
uint256 deadline = block.timestamp + 1 days;
bytes32 structHash = keccak256(
abi.encode(PERMIT_TYPEHASH, owner, spender, value, token.nonces(owner), deadline)
);
bytes32 digest = keccak256(
abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)
);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPk, digest);
token.permit(owner, spender, value, deadline, v, r, s);
assertEq(token.allowance(owner, spender), value);
assertEq(token.nonces(owner), 1);
}
function testTransferWithAuthorizationWorks() public {
uint256 value = 5_000 * 10 ** 6;
uint256 validAfter = block.timestamp - 1;
uint256 validBefore = block.timestamp + 1 days;
bytes32 nonce = keccak256("auth-1");
bytes32 structHash = keccak256(
abi.encode(
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
owner,
recipient,
value,
validAfter,
validBefore,
nonce
)
);
bytes32 digest = keccak256(
abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)
);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPk, digest);
vm.prank(spender);
token.transferWithAuthorization(owner, recipient, value, validAfter, validBefore, nonce, v, r, s);
assertEq(token.balanceOf(recipient), value);
assertEq(token.balanceOf(owner), 1_000_000 * 10 ** 6 - value);
assertTrue(token.authorizationState(owner, nonce));
}
function testReceiveWithAuthorizationRequiresPayeeCallerAndWorks() public {
uint256 value = 7_500 * 10 ** 6;
uint256 validAfter = block.timestamp - 1;
uint256 validBefore = block.timestamp + 1 days;
bytes32 nonce = keccak256("receive-auth-1");
bytes32 structHash = keccak256(
abi.encode(
RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
owner,
recipient,
value,
validAfter,
validBefore,
nonce
)
);
bytes32 digest = keccak256(
abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)
);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPk, digest);
vm.prank(spender);
vm.expectRevert(
abi.encodeWithSelector(
CompliantFiatTokenV2.AuthorizationMustBeUsedByPayee.selector,
recipient,
spender
)
);
token.receiveWithAuthorization(owner, recipient, value, validAfter, validBefore, nonce, v, r, s);
vm.prank(recipient);
token.receiveWithAuthorization(owner, recipient, value, validAfter, validBefore, nonce, v, r, s);
assertEq(token.balanceOf(recipient), value);
assertTrue(token.authorizationState(owner, nonce));
}
function testCancelAuthorizationBlocksFutureUse() public {
uint256 value = 1_000 * 10 ** 6;
uint256 validAfter = block.timestamp - 1;
uint256 validBefore = block.timestamp + 1 days;
bytes32 nonce = keccak256("cancel-auth-1");
bytes32 cancelStructHash = keccak256(
abi.encode(CANCEL_AUTHORIZATION_TYPEHASH, owner, nonce)
);
bytes32 cancelDigest = keccak256(
abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), cancelStructHash)
);
(uint8 cancelV, bytes32 cancelR, bytes32 cancelS) = vm.sign(ownerPk, cancelDigest);
token.cancelAuthorization(owner, nonce, cancelV, cancelR, cancelS);
assertTrue(token.authorizationState(owner, nonce));
bytes32 transferStructHash = keccak256(
abi.encode(
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
owner,
recipient,
value,
validAfter,
validBefore,
nonce
)
);
bytes32 transferDigest = keccak256(
abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), transferStructHash)
);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPk, transferDigest);
vm.prank(spender);
vm.expectRevert(
abi.encodeWithSelector(
CompliantFiatTokenV2.AuthorizationAlreadyUsed.selector,
owner,
nonce
)
);
token.transferWithAuthorization(owner, recipient, value, validAfter, validBefore, nonce, v, r, s);
}
function testMintAndBurnReasonHashesEmitStructuredEvents() public {
bytes32 mintReason = keccak256("mint-reason");
bytes32 burnReason = keccak256("burn-reason");
uint256 mintAmount = 10_000 * 10 ** 6;
vm.expectEmit(true, true, true, false);
emit CompliantOperationDeclared(
token.OPERATION_MINT(),
admin,
admin,
admin,
address(0),
recipient,
mintAmount,
mintReason,
bytes32(0),
bytes32(0),
bytes32(0)
);
vm.prank(admin);
token.mint(recipient, mintAmount, mintReason);
assertEq(token.balanceOf(recipient), mintAmount);
vm.expectEmit(true, true, true, false);
emit CompliantOperationDeclared(
token.OPERATION_BURN(),
recipient,
recipient,
burner,
recipient,
address(0),
mintAmount / 2,
burnReason,
bytes32(0),
bytes32(0),
bytes32(0)
);
vm.prank(burner);
token.burn(recipient, mintAmount / 2, burnReason);
assertEq(token.balanceOf(recipient), mintAmount / 2);
}
function testPauseIsRoleGated() public {
vm.prank(spender);
vm.expectRevert();
token.pause();
vm.prank(admin);
token.pause();
assertTrue(token.paused());
}
function testSupplyControlsEnforceCap() public {
uint256 configuredCap = token.totalSupply() + (1_000 * 10 ** 6);
vm.prank(admin);
token.setSupplyControls(configuredCap, 0, 0);
vm.prank(admin);
token.mint(recipient, 1_000 * 10 ** 6, keccak256("under-cap"));
uint256 attemptedTotalSupply = token.totalSupply() + 1;
vm.prank(admin);
vm.expectRevert(
abi.encodeWithSelector(
CompliantFiatTokenV2.SupplyCapExceeded.selector,
configuredCap,
attemptedTotalSupply
)
);
token.mint(recipient, 1, keccak256("over-cap"));
}
function testLegacyAliasAndForwardCanonicalMetadata() public {
vm.prank(admin);
vm.expectRevert();
token.setForwardCanonical(false);
vm.startPrank(governanceExecutor);
token.addLegacyAlias("cEURC-legacy");
token.setForwardCanonical(false);
token.setSymbolDisplay("cEURC");
vm.stopPrank();
string[] memory aliases = token.legacyAliases();
assertEq(aliases.length, 1);
assertEq(aliases[0], "cEURC-legacy");
assertFalse(token.forwardCanonical());
assertEq(token.symbolDisplay(), "cEURC");
}
function testGovernanceAndSupervisionMetadataCanBeManaged() public {
bytes32 governanceProfileId = keccak256("gov-profile");
bytes32 supervisionProfileId = keccak256("supervision-profile");
bytes32 storageNamespace = keccak256("storage-namespace");
vm.prank(admin);
vm.expectRevert();
token.setGovernanceProfileId(governanceProfileId);
vm.startPrank(governanceExecutor);
token.setGovernanceProfileId(governanceProfileId);
token.setSupervisionProfileId(supervisionProfileId);
token.setStorageNamespace(storageNamespace);
token.setPrimaryJurisdiction("EU");
token.setRegulatoryDisclosureURI("ipfs://disclosure");
token.setReportingURI("ipfs://reporting");
token.setCanonicalUnderlyingAsset(address(0x1234));
token.setSupervisionConfiguration(true, true, 14 days);
vm.stopPrank();
assertEq(token.governanceProfileId(), governanceProfileId);
assertEq(token.supervisionProfileId(), supervisionProfileId);
assertEq(token.storageNamespace(), storageNamespace);
assertEq(token.primaryJurisdiction(), "EU");
assertEq(token.regulatoryDisclosureURI(), "ipfs://disclosure");
assertEq(token.reportingURI(), "ipfs://reporting");
assertEq(token.canonicalUnderlyingAsset(), address(0x1234));
assertTrue(token.supervisionRequired());
assertTrue(token.governmentApprovalRequired());
assertEq(token.minimumUpgradeNoticePeriod(), 14 days);
assertFalse(token.wrappedTransport());
}
function testEip5267DomainIntrospectionMatchesTokenMetadata() public view {
(
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
) = token.eip712Domain();
assertEq(uint8(fields), uint8(0x0f));
assertEq(name, "Euro Coin (Compliant V2)");
assertEq(version, "2");
assertEq(chainId, block.chainid);
assertEq(verifyingContract, address(token));
assertEq(salt, bytes32(0));
assertEq(extensions.length, 0);
}
function testEmergencyMetadataOverridesRemainAvailableOutsideGovernance() public {
vm.prank(admin);
token.emergencySetPresentationMetadata(true, "ipfs://emergency-token", "cEURC-emergency");
vm.prank(admin);
token.emergencySetGovernanceMetadata(
keccak256("emergency-gov"),
keccak256("emergency-sup"),
keccak256("emergency-storage"),
"Emergency Jurisdiction",
address(0x9999),
true,
true,
30 days
);
vm.prank(admin);
token.emergencySetDisclosureMetadata("ipfs://emergency-disclosure", "ipfs://emergency-reporting");
assertTrue(token.forwardCanonical());
assertEq(token.tokenURI(), "ipfs://emergency-token");
assertEq(token.symbolDisplay(), "cEURC-emergency");
assertEq(token.primaryJurisdiction(), "Emergency Jurisdiction");
assertEq(token.canonicalUnderlyingAsset(), address(0x9999));
assertEq(token.regulatoryDisclosureURI(), "ipfs://emergency-disclosure");
assertEq(token.reportingURI(), "ipfs://emergency-reporting");
assertEq(token.minimumUpgradeNoticePeriod(), 30 days);
}
}