// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {CrossChainFlashRepayReceiver} from "../../contracts/flash/CrossChainFlashRepayReceiver.sol"; import {IRouterClient} from "../../contracts/ccip/IRouterClient.sol"; contract MockToken is ERC20 { constructor() ERC20("X", "X") {} function mint(address to, uint256 v) external { _mint(to, v); } } contract CrossChainFlashRepayReceiverTest is Test { CrossChainFlashRepayReceiver internal recv; MockToken internal token; address internal router; function setUp() public { router = makeAddr("ccipRouter"); recv = new CrossChainFlashRepayReceiver(router); token = new MockToken(); } function _msg(address beneficiary, bytes32 obligation, uint256 amt, address tok) internal pure returns (IRouterClient.Any2EVMMessage memory m) { IRouterClient.TokenAmount[] memory amounts = new IRouterClient.TokenAmount[](1); amounts[0] = IRouterClient.TokenAmount({token: tok, amount: amt, amountType: IRouterClient.TokenAmountType.Fiat}); m = IRouterClient.Any2EVMMessage({ messageId: keccak256("mid"), sourceChainSelector: 138, sender: abi.encode(address(0x111)), data: abi.encode(beneficiary, obligation), tokenAmounts: amounts }); } function test_ccipReceive_forwardsToRecipient() public { address beneficiary = address(0xB0B); bytes32 obligation = keccak256("obligation-1"); uint256 amt = 777e18; token.mint(address(recv), amt); IRouterClient.Any2EVMMessage memory message = _msg(beneficiary, obligation, amt, address(token)); vm.prank(router); recv.ccipReceive(message); assertEq(token.balanceOf(beneficiary), amt); } function test_ccipReceive_revert_notRouter() public { IRouterClient.Any2EVMMessage memory message = _msg(address(0x1), bytes32(0), 1, address(token)); vm.expectRevert(CrossChainFlashRepayReceiver.OnlyRouter.selector); recv.ccipReceive(message); } }