- 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
80 lines
3.0 KiB
Solidity
80 lines
3.0 KiB
Solidity
// 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 {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
|
|
import {SimpleERC3156FlashVault} from "../../contracts/flash/SimpleERC3156FlashVault.sol";
|
|
import {CrossChainFlashBorrower} from "../../contracts/flash/CrossChainFlashBorrower.sol";
|
|
import {ICrossChainFlashBridge} from "../../contracts/flash/interfaces/ICrossChainFlashBridge.sol";
|
|
|
|
contract MockCrossChainBridge is ICrossChainFlashBridge {
|
|
event BridgeCalled(address token, uint256 amount, uint64 dest, address recipient, bytes extra, uint256 value);
|
|
|
|
function bridgeTokensFrom(
|
|
address token,
|
|
uint256 amount,
|
|
uint64 destinationChainSelector,
|
|
address recipientOnDestination,
|
|
bytes calldata extraData
|
|
) external payable override returns (bytes32 messageId) {
|
|
IERC20(token).transferFrom(msg.sender, address(this), amount);
|
|
emit BridgeCalled(token, amount, destinationChainSelector, recipientOnDestination, extraData, msg.value);
|
|
messageId = keccak256(abi.encodePacked(block.number, token, amount));
|
|
}
|
|
}
|
|
|
|
contract MockERC20Mint is ERC20 {
|
|
constructor() ERC20("T", "T") {}
|
|
|
|
function mint(address to, uint256 v) external {
|
|
_mint(to, v);
|
|
}
|
|
}
|
|
|
|
contract CrossChainFlashBorrowerTest is Test {
|
|
SimpleERC3156FlashVault internal vault;
|
|
MockERC20Mint internal token;
|
|
MockCrossChainBridge internal bridge;
|
|
CrossChainFlashBorrower internal borrower;
|
|
|
|
address internal owner = address(0xA11);
|
|
address internal user = address(0xB22);
|
|
|
|
function setUp() public {
|
|
vm.startPrank(owner);
|
|
vault = new SimpleERC3156FlashVault(owner, 5);
|
|
token = new MockERC20Mint();
|
|
token.mint(address(vault), 1_000_000e18);
|
|
vault.setTokenSupported(address(token), true);
|
|
vm.stopPrank();
|
|
|
|
bridge = new MockCrossChainBridge();
|
|
borrower = new CrossChainFlashBorrower(address(vault));
|
|
}
|
|
|
|
function test_flashBridge_out_repaysFromPrefund() public {
|
|
uint256 amount = 40_000e18;
|
|
uint256 fee = vault.flashFee(address(token), amount);
|
|
uint256 bridgeAmount = amount;
|
|
|
|
token.mint(address(borrower), bridgeAmount + fee);
|
|
|
|
CrossChainFlashBorrower.CrossChainFlashParams memory p = CrossChainFlashBorrower.CrossChainFlashParams({
|
|
bridge: address(bridge),
|
|
bridgeAmount: bridgeAmount,
|
|
destinationChainSelector: 123,
|
|
recipientOnDestination: address(0xbeef),
|
|
bridgeExtraData: hex"abcd",
|
|
nativeBridgeFee: 0
|
|
});
|
|
|
|
vm.prank(user);
|
|
vault.flashLoan(IERC3156FlashBorrower(address(borrower)), address(token), amount, abi.encode(p));
|
|
|
|
assertEq(token.balanceOf(address(bridge)), bridgeAmount);
|
|
assertEq(vault.totalFeesCollected(address(token)), fee);
|
|
}
|
|
}
|