// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; import "../../src/eMoneyToken.sol"; import "../../src/PolicyManager.sol"; import "../../src/ComplianceRegistry.sol"; import "../../src/DebtRegistry.sol"; import "../../src/errors/TokenErrors.sol"; import "../../src/libraries/ReasonCodes.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; contract TransferFuzz is Test { eMoneyToken public token; PolicyManager public policyManager; ComplianceRegistry public complianceRegistry; DebtRegistry public debtRegistry; address public admin; address public issuer; address public user1; address public user2; function setUp() public { admin = address(0x1); issuer = address(0x2); user1 = address(0x10); user2 = address(0x20); complianceRegistry = new ComplianceRegistry(admin); debtRegistry = new DebtRegistry(admin); policyManager = new PolicyManager(admin, address(complianceRegistry), address(debtRegistry)); eMoneyToken implementation = new eMoneyToken(); bytes memory initData = abi.encodeWithSelector( eMoneyToken.initialize.selector, "Test Token", "TEST", 18, issuer, address(policyManager), address(debtRegistry), address(complianceRegistry) ); ERC1967Proxy proxy = new ERC1967Proxy(address(implementation), initData); token = eMoneyToken(address(proxy)); vm.startPrank(admin); policyManager.grantRole(policyManager.POLICY_OPERATOR_ROLE(), admin); policyManager.setLienMode(address(token), 2); // Encumbered mode complianceRegistry.grantRole(complianceRegistry.COMPLIANCE_ROLE(), admin); complianceRegistry.setCompliance(user1, true, 1, bytes32(0)); complianceRegistry.setCompliance(user2, true, 1, bytes32(0)); debtRegistry.grantRole(debtRegistry.DEBT_AUTHORITY_ROLE(), admin); vm.stopPrank(); } function testFuzz_transferWithLien( uint256 mintAmount, uint256 lienAmount, uint256 transferAmount ) public { // Bound inputs to reasonable ranges mintAmount = bound(mintAmount, 1, type(uint128).max); lienAmount = bound(lienAmount, 0, mintAmount); transferAmount = bound(transferAmount, 0, mintAmount); // Mint to user1 vm.prank(issuer); token.mint(user1, mintAmount, ReasonCodes.OK); // Place lien if (lienAmount > 0) { vm.prank(admin); debtRegistry.placeLien(user1, lienAmount, 0, 1, ReasonCodes.LIEN_BLOCK); } uint256 freeBalance = token.freeBalanceOf(user1); bool shouldSucceed = transferAmount <= freeBalance && transferAmount > 0; if (shouldSucceed) { vm.prank(user1); token.transfer(user2, transferAmount); assertEq(token.balanceOf(user1), mintAmount - transferAmount); assertEq(token.balanceOf(user2), transferAmount); } else if (transferAmount > freeBalance && lienAmount > 0) { // Should fail with insufficient free balance vm.expectRevert(); vm.prank(user1); token.transfer(user2, transferAmount); } } function testFuzz_transferWithMultipleLiens( uint256 mintAmount, uint256[3] memory lienAmounts, uint256 transferAmount ) public { mintAmount = bound(mintAmount, 1000, type(uint128).max); transferAmount = bound(transferAmount, 0, mintAmount); // Bound lien amounts for (uint256 i = 0; i < 3; i++) { lienAmounts[i] = bound(lienAmounts[i], 0, mintAmount / 3); } // Mint to user1 vm.prank(issuer); token.mint(user1, mintAmount, ReasonCodes.OK); // Place multiple liens uint256 totalLienAmount = 0; for (uint256 i = 0; i < 3; i++) { if (lienAmounts[i] > 0) { vm.prank(admin); debtRegistry.placeLien(user1, lienAmounts[i], 0, 1, ReasonCodes.LIEN_BLOCK); totalLienAmount += lienAmounts[i]; } } uint256 freeBalance = mintAmount > totalLienAmount ? mintAmount - totalLienAmount : 0; bool shouldSucceed = transferAmount <= freeBalance && transferAmount > 0; if (shouldSucceed) { vm.prank(user1); token.transfer(user2, transferAmount); assertEq(token.balanceOf(user1), mintAmount - transferAmount); } else if (transferAmount > freeBalance && totalLienAmount > 0) { vm.expectRevert(); vm.prank(user1); token.transfer(user2, transferAmount); } } function testFuzz_freeBalanceCalculation( uint256 balance, uint256 encumbrance ) public { balance = bound(balance, 0, type(uint128).max); encumbrance = bound(encumbrance, 0, type(uint128).max); if (balance > 0) { vm.prank(issuer); token.mint(user1, balance, ReasonCodes.OK); } if (encumbrance > 0) { vm.prank(admin); debtRegistry.placeLien(user1, encumbrance, 0, 1, ReasonCodes.LIEN_BLOCK); } uint256 freeBalance = token.freeBalanceOf(user1); uint256 expectedFreeBalance = balance > encumbrance ? balance - encumbrance : 0; assertEq(freeBalance, expectedFreeBalance, "Free balance calculation incorrect"); } }