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
This commit is contained in:
@@ -14,6 +14,12 @@ contract CompliantFiatTokenV2Test is Test {
|
||||
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,
|
||||
@@ -117,6 +123,89 @@ contract CompliantFiatTokenV2Test is Test {
|
||||
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");
|
||||
@@ -245,6 +334,26 @@ contract CompliantFiatTokenV2Test is Test {
|
||||
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");
|
||||
|
||||
54
test/compliance/CompliantMonetaryUnitTokenTest.t.sol
Normal file
54
test/compliance/CompliantMonetaryUnitTokenTest.t.sol
Normal file
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {CompliantBTC} from "../../contracts/tokens/CompliantBTC.sol";
|
||||
|
||||
contract CompliantMonetaryUnitTokenTest is Test {
|
||||
CompliantBTC public token;
|
||||
address public owner;
|
||||
address public admin;
|
||||
address public user1;
|
||||
|
||||
function setUp() public {
|
||||
owner = address(this);
|
||||
admin = address(this);
|
||||
user1 = address(0xB0B);
|
||||
token = new CompliantBTC(owner, admin, 21_000_000 * 10**8);
|
||||
}
|
||||
|
||||
function testDecimals() public view {
|
||||
assertEq(token.decimals(), 8);
|
||||
}
|
||||
|
||||
function testUnitCode() public view {
|
||||
assertEq(token.unitCode(), "BTC");
|
||||
assertTrue(token.isMonetaryUnit());
|
||||
}
|
||||
|
||||
function testInitialSupplyUsesSatoshiPrecision() public view {
|
||||
assertEq(token.totalSupply(), 21_000_000 * 10**8);
|
||||
assertEq(token.balanceOf(owner), 21_000_000 * 10**8);
|
||||
}
|
||||
|
||||
function testTransferUsesEightDecimals() public {
|
||||
uint256 amount = 12_500_000;
|
||||
token.transfer(user1, amount);
|
||||
assertEq(token.balanceOf(user1), amount);
|
||||
assertEq(token.balanceOf(owner), 21_000_000 * 10**8 - amount);
|
||||
}
|
||||
|
||||
function testPauseBlocksTransfers() public {
|
||||
token.pause();
|
||||
assertTrue(token.paused());
|
||||
vm.expectRevert();
|
||||
token.transfer(user1, 100_000_000);
|
||||
}
|
||||
|
||||
function testMintAndBurnUseSatoshiPrecision() public {
|
||||
token.mint(user1, 250_000_000);
|
||||
assertEq(token.balanceOf(user1), 250_000_000);
|
||||
token.burn(100_000_000);
|
||||
assertEq(token.balanceOf(owner), 21_000_000 * 10**8 - 100_000_000);
|
||||
}
|
||||
}
|
||||
@@ -134,4 +134,24 @@ contract CompliantWrappedTokenTest is Test {
|
||||
assertEq(token.reportingURI(), "ipfs://cw-emergency-reporting");
|
||||
assertEq(token.minimumUpgradeNoticePeriod(), 30 days);
|
||||
}
|
||||
|
||||
function testWrappedBTCCanUseEightDecimalsAndFrozenBridgeRoles() public {
|
||||
CompliantWrappedToken wrappedBtc = new CompliantWrappedToken("Wrapped cBTC", "cWBTC", 8, admin);
|
||||
wrappedBtc.grantRole(MINTER_ROLE, bridge);
|
||||
wrappedBtc.grantRole(BURNER_ROLE, bridge);
|
||||
|
||||
vm.prank(bridge);
|
||||
wrappedBtc.mint(user1, 125_000_000);
|
||||
assertEq(wrappedBtc.decimals(), 8);
|
||||
assertEq(wrappedBtc.balanceOf(user1), 125_000_000);
|
||||
|
||||
wrappedBtc.freezeOperationalRoles();
|
||||
|
||||
vm.prank(bridge);
|
||||
wrappedBtc.burnFrom(user1, 25_000_000);
|
||||
assertEq(wrappedBtc.balanceOf(user1), 100_000_000);
|
||||
|
||||
vm.expectRevert();
|
||||
wrappedBtc.grantRole(MINTER_ROLE, address(0xCAFE));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user