- 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
88 lines
3.1 KiB
Solidity
88 lines
3.1 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 {SwapFlashWorkflowBorrower, IDODOStyleSwapExactIn} from "../../contracts/flash/SwapFlashWorkflowBorrower.sol";
|
|
|
|
/// @notice 1:1 swap router for tests (ignores pool).
|
|
contract MockSwapRouter1to1 is IDODOStyleSwapExactIn {
|
|
ERC20 public immutable tokenA;
|
|
ERC20 public immutable tokenB;
|
|
|
|
constructor(ERC20 a, ERC20 b) {
|
|
tokenA = a;
|
|
tokenB = b;
|
|
}
|
|
|
|
function swapExactIn(address, address tokenIn, uint256 amountIn, uint256 minAmountOut)
|
|
external
|
|
override
|
|
returns (uint256 amountOut)
|
|
{
|
|
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
|
|
address tokenOut = tokenIn == address(tokenA) ? address(tokenB) : address(tokenA);
|
|
amountOut = amountIn;
|
|
require(amountOut >= minAmountOut, "min");
|
|
IERC20(tokenOut).transfer(msg.sender, amountOut);
|
|
}
|
|
}
|
|
|
|
contract MockERC20Mint is ERC20 {
|
|
constructor(string memory n, string memory s) ERC20(n, s) {}
|
|
|
|
function mint(address to, uint256 v) external {
|
|
_mint(to, v);
|
|
}
|
|
}
|
|
|
|
contract SwapFlashWorkflowBorrowerTest is Test {
|
|
SimpleERC3156FlashVault internal vault;
|
|
MockERC20Mint internal tokenA;
|
|
MockERC20Mint internal tokenB;
|
|
MockSwapRouter1to1 internal router;
|
|
SwapFlashWorkflowBorrower internal borrower;
|
|
|
|
address internal owner = address(0xA11);
|
|
address internal user = address(0xB22);
|
|
|
|
function setUp() public {
|
|
vm.startPrank(owner);
|
|
vault = new SimpleERC3156FlashVault(owner, 5);
|
|
tokenA = new MockERC20Mint("A", "A");
|
|
tokenB = new MockERC20Mint("B", "B");
|
|
tokenA.mint(address(vault), 1_000_000e18);
|
|
vault.setTokenSupported(address(tokenA), true);
|
|
vm.stopPrank();
|
|
|
|
router = new MockSwapRouter1to1(tokenA, tokenB);
|
|
tokenA.mint(address(router), 10_000_000e18);
|
|
tokenB.mint(address(router), 10_000_000e18);
|
|
|
|
borrower = new SwapFlashWorkflowBorrower(address(vault));
|
|
}
|
|
|
|
function test_roundTripSwap_prefundFeeInBorrowedToken() public {
|
|
uint256 amount = 50_000e18;
|
|
uint256 fee = vault.flashFee(address(tokenA), amount);
|
|
tokenA.mint(address(borrower), fee);
|
|
|
|
SwapFlashWorkflowBorrower.SwapFlashParams memory p = SwapFlashWorkflowBorrower.SwapFlashParams({
|
|
integration: address(router),
|
|
pool: address(0xdead),
|
|
midToken: address(tokenB),
|
|
minOutFirst: amount,
|
|
minOutSecond: amount
|
|
});
|
|
bytes memory data = abi.encode(p);
|
|
|
|
vm.prank(user);
|
|
vault.flashLoan(IERC3156FlashBorrower(address(borrower)), address(tokenA), amount, data);
|
|
|
|
assertEq(vault.totalFeesCollected(address(tokenA)), fee);
|
|
}
|
|
}
|