diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..3ccea04 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,4 @@ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ +solmate/=lib/solmate/src/ +v2-core/=lib/v2-core/contracts/ \ No newline at end of file diff --git a/script/DeployFeeCollector.s.sol b/script/DeployFeeCollector.s.sol index 8d9901b..67e80c9 100644 --- a/script/DeployFeeCollector.s.sol +++ b/script/DeployFeeCollector.s.sol @@ -11,12 +11,11 @@ contract DeployFeeCollector is Script { function run() public returns (FeeCollector collector) { uint256 privateKey = vm.envUint("FOUNDRY_FEE_COLLECTOR_PRIVATE_KEY"); address owner = vm.envAddress("FOUNDRY_FEE_COLLECTOR_OWNER_ADDRESS"); - address universalRouter = vm.envAddress("FOUNDRY_FEE_COLLECTOR_UNIVERSAL_ROUTER_ADDRESS"); - address permit2 = vm.envAddress("FOUNDRY_FEE_COLLECTOR_PERMIT2_ADDRESS"); + address feeRecipient = vm.envAddress("FOUNDRY_FEE_COLLECTOR_FEE_RECIPIENT_ADDRESS"); address feeToken = vm.envAddress("FOUNDRY_FEE_COLLECTOR_FEE_TOKEN_ADDRESS"); vm.startBroadcast(privateKey); - collector = new FeeCollector{salt: 0x00}(owner, universalRouter, permit2, feeToken); + collector = new FeeCollector{salt: 0x00}(owner, feeRecipient, feeToken, 100 ether); vm.stopBroadcast(); console2.log("Successfully deployed FeeCollector", address(collector)); diff --git a/src/FeeCollector.sol b/src/FeeCollector.sol index f63cb7b..1a6c915 100644 --- a/src/FeeCollector.sol +++ b/src/FeeCollector.sol @@ -5,62 +5,52 @@ import {Owned} from "solmate/auth/Owned.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; import {IFeeCollector} from "./interfaces/IFeeCollector.sol"; -import {IPermit2} from "./external/IPermit2.sol"; /// @notice The collector of protocol fees that will be used to swap and send to a fee recipient address. contract FeeCollector is Owned, IFeeCollector { using SafeTransferLib for ERC20; - error UniversalRouterCallFailed(); + error CallFailed(); - address private immutable universalRouter; + address public feeRecipient; + /// @notice the amount of fee token that must be paid per token + uint256 public feeTokenAmount; + /// @notice the token to receive fees in + ERC20 public immutable feeToken; - ERC20 private immutable feeToken; - IPermit2 private immutable permit2; - - uint256 private constant MAX_APPROVAL_AMOUNT = type(uint256).max; - uint160 private constant MAX_PERMIT2_APPROVAL_AMOUNT = type(uint160).max; - uint48 private constant MAX_PERMIT2_DEADLINE = type(uint48).max; - - constructor(address _owner, address _universalRouter, address _permit2, address _feeToken) Owned(_owner) { - universalRouter = _universalRouter; + constructor(address _owner, address _feeRecipient, address _feeToken, uint256 _feeTokenAmount) Owned(_owner) { + feeRecipient = _feeRecipient; feeToken = ERC20(_feeToken); - permit2 = IPermit2(_permit2); + feeTokenAmount = _feeTokenAmount; } - /// @inheritdoc IFeeCollector - function swapBalance(bytes calldata swapData, uint256 nativeValue) external onlyOwner { - _execute(swapData, nativeValue); + /// @notice allow anyone to take the full balance of any arbitrary tokens + /// @dev as long as they pay `feeTokenAmount` per token taken to the `feeRecipient` + /// this creates a competitive auction as the balances of this contract increase + /// to find the optimal path for the swap + function swapBalances(ERC20[] memory tokens, bytes calldata call) external { + for (uint256 i = 0; i < tokens.length; i++) { + tokens[i].safeTransfer(msg.sender, tokens[i].balanceOf(address(this))); + } + (bool success,) = msg.sender.call(call); + if (!success) { + revert CallFailed(); + } + + feeToken.safeTransferFrom(msg.sender, feeRecipient, feeTokenAmount * tokens.length); } /// @inheritdoc IFeeCollector - function swapBalance(bytes calldata swapData, uint256 nativeValue, ERC20[] calldata tokensToApprove) - external - onlyOwner - { - unchecked { - for (uint256 i = 0; i < tokensToApprove.length; i++) { - tokensToApprove[i].safeApprove(address(permit2), MAX_APPROVAL_AMOUNT); - permit2.approve( - address(tokensToApprove[i]), universalRouter, MAX_PERMIT2_APPROVAL_AMOUNT, MAX_PERMIT2_DEADLINE - ); - } - } - - _execute(swapData, nativeValue); + function withdrawToken(ERC20 token, address to, uint256 amount) external onlyOwner { + token.safeTransfer(to, amount); } - /// @notice Helper function to call UniversalRouter. - /// @param swapData The bytes call data to be forwarded to UniversalRouter. - /// @param nativeValue The amount of native currency to send to UniversalRouter. - function _execute(bytes calldata swapData, uint256 nativeValue) internal { - (bool success,) = universalRouter.call{value: nativeValue}(swapData); - if (!success) revert UniversalRouterCallFailed(); + function setFeeRecipient(address _feeRecipient) external onlyOwner { + feeRecipient = _feeRecipient; } - /// @inheritdoc IFeeCollector - function withdrawFeeToken(address feeRecipient, uint256 amount) external onlyOwner { - feeToken.safeTransfer(feeRecipient, amount); + function setFeeTokenAmount(uint256 _feeTokenAmount) external onlyOwner { + feeTokenAmount = _feeTokenAmount; } receive() external payable {} diff --git a/src/interfaces/IFeeCollector.sol b/src/interfaces/IFeeCollector.sol index dccf5e2..cf8cdec 100644 --- a/src/interfaces/IFeeCollector.sol +++ b/src/interfaces/IFeeCollector.sol @@ -5,19 +5,12 @@ import {ERC20} from "solmate/tokens/ERC20.sol"; /// @notice The collector of protocol fees that will be used to swap and send to a fee recipient address. interface IFeeCollector { - /// @notice Swaps the contract balance. - /// @param swapData The bytes call data to be forwarded to UniversalRouter. - /// @param nativeValue The amount of native currency to send to UniversalRouter. - function swapBalance(bytes calldata swapData, uint256 nativeValue) external; + /// @notice + function swapBalances(ERC20[] memory tokens, bytes calldata call) external; - /// @notice Approves tokens for swapping and then swaps the contract balance. - /// @param swapData The bytes call data to be forwarded to UniversalRouter. - /// @param nativeValue The amount of native currency to send to UniversalRouter. - /// @param tokensToApprove An array of ERC20 tokens to approve for spending. - function swapBalance(bytes calldata swapData, uint256 nativeValue, ERC20[] calldata tokensToApprove) external; - - /// @notice Transfers the fee token balance from this contract to the fee recipient. - /// @param feeRecipient The address to send the fee token balance to. + /// @notice Transfers amount of token to a caller specified recipient. + /// @param token The token to withdraw. + /// @param to The address to send to /// @param amount The amount to withdraw. - function withdrawFeeToken(address feeRecipient, uint256 amount) external; + function withdrawToken(ERC20 token, address to, uint256 amount) external; } diff --git a/test/FeeCollector.t.sol b/test/FeeCollector.t.sol index d3c3683..72c1582 100644 --- a/test/FeeCollector.t.sol +++ b/test/FeeCollector.t.sol @@ -5,8 +5,7 @@ import {Test} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {MockToken} from "./mock/MockToken.sol"; -import {MockUniversalRouter} from "./mock/MockUniversalRouter.sol"; -import {IMockUniversalRouter} from "./mock/MockUniversalRouter.sol"; +import {MockSearcher} from "./mock/MockSearcher.sol"; import {FeeCollector} from "../src/FeeCollector.sol"; contract FeeCollectorTest is Test { @@ -14,132 +13,44 @@ contract FeeCollectorTest is Test { address caller; address feeRecipient; - address permit2; MockToken mockFeeToken; MockToken tokenIn; MockToken tokenOut; - MockUniversalRouter router; + + MockSearcher searcherContract; function setUp() public { // Mock caller and fee recipient caller = makeAddr("caller"); feeRecipient = makeAddr("feeRecipient"); - permit2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; mockFeeToken = new MockToken(); tokenIn = new MockToken(); tokenOut = new MockToken(); - router = new MockUniversalRouter(); - collector = new FeeCollector(caller, address(router), permit2, address(mockFeeToken)); + collector = new FeeCollector(caller, feeRecipient, address(mockFeeToken), 100 ether); + searcherContract = new MockSearcher(payable(address(collector))); } function testSwapBalance() public { tokenIn.mint(address(collector), 100 ether); - tokenOut.mint(address(router), 100 ether); - assertEq(tokenIn.balanceOf(address(collector)), 100 ether); - assertEq(tokenOut.balanceOf(address(router)), 100 ether); - - bytes memory swapData = abi.encodeWithSelector( - IMockUniversalRouter.execute.selector, abi.encode(address(tokenIn), address(tokenOut), 100 ether, 100 ether) - ); - - vm.prank(address(collector)); - tokenIn.approve(address(router), 100 ether); - vm.prank(caller); - collector.swapBalance(swapData, 0); - - assertEq(tokenIn.balanceOf(address(collector)), 0 ether); - assertEq(tokenOut.balanceOf(address(collector)), 100 ether); - assertEq(tokenIn.balanceOf(address(router)), 100 ether); - assertEq(tokenOut.balanceOf(address(router)), 0 ether); - } - - function testSwapBalanceNative() public { - vm.deal(address(collector), 100 ether); - tokenOut.mint(address(router), 100 ether); - assertEq(address(collector).balance, 100 ether); - assertEq(tokenOut.balanceOf(address(router)), 100 ether); - - bytes memory swapData = abi.encodeWithSelector( - IMockUniversalRouter.execute.selector, abi.encode(address(0), address(tokenOut), 100 ether, 100 ether) - ); - - vm.prank(caller); - collector.swapBalance(swapData, 100 ether); - - assertEq(address(collector).balance, 0 ether); - assertEq(tokenOut.balanceOf(address(collector)), 100 ether); - assertEq(address(router).balance, 100 ether); - assertEq(tokenOut.balanceOf(address(router)), 0 ether); - } - - function testSwapBalanceNativeError() public { - tokenIn.mint(address(collector), 100 ether); - tokenOut.mint(address(router), 100 ether); - assertEq(tokenIn.balanceOf(address(collector)), 100 ether); - assertEq(tokenOut.balanceOf(address(router)), 100 ether); - - bytes memory badSwapCallData = abi.encodeWithSelector( - IMockUniversalRouter.execute.selector, abi.encode(address(tokenIn), address(tokenOut)) - ); - - vm.prank(address(collector)); - tokenIn.approve(address(router), 100 ether); - vm.expectRevert(FeeCollector.UniversalRouterCallFailed.selector); - vm.prank(caller); - collector.swapBalance(badSwapCallData, 0); - - assertEq(tokenIn.balanceOf(address(collector)), 100 ether); - assertEq(tokenOut.balanceOf(address(collector)), 0 ether); - assertEq(tokenIn.balanceOf(address(router)), 0 ether); - assertEq(tokenOut.balanceOf(address(router)), 100 ether); - } - - function testSwapBalanceUnauthorized() public { - tokenIn.mint(address(collector), 100 ether); - tokenOut.mint(address(router), 100 ether); assertEq(tokenIn.balanceOf(address(collector)), 100 ether); - assertEq(tokenOut.balanceOf(address(router)), 100 ether); - bytes memory swapData = abi.encodeWithSelector( - IMockUniversalRouter.execute.selector, abi.encode(address(tokenIn), address(tokenOut), 100 ether, 100 ether) - ); + // For the test we just assume the filler has the required fee tokens + mockFeeToken.mint(address(searcherContract), 100 ether); - vm.prank(address(collector)); - tokenIn.approve(address(router), 100 ether); - vm.expectRevert("UNAUTHORIZED"); - vm.prank(address(0xbeef)); - collector.swapBalance(swapData, 0); - - assertEq(tokenIn.balanceOf(address(collector)), 100 ether); - assertEq(tokenOut.balanceOf(address(collector)), 0 ether); - assertEq(tokenIn.balanceOf(address(router)), 0 ether); - assertEq(tokenOut.balanceOf(address(router)), 100 ether); - } + ERC20[] memory tokens = new ERC20[](1); + tokens[0] = tokenIn; + bytes memory call = abi.encodeWithSignature("doMev()"); + searcherContract.swapBalances(tokens, call); - function testWithdrawFeeToken() public { - assertEq(mockFeeToken.balanceOf(address(collector)), 0); - assertEq(mockFeeToken.balanceOf(address(feeRecipient)), 0); - mockFeeToken.mint(address(collector), 100 ether); - assertEq(mockFeeToken.balanceOf(address(collector)), 100 ether); - vm.prank(caller); - collector.withdrawFeeToken(feeRecipient, 100 ether); - assertEq(mockFeeToken.balanceOf(address(collector)), 0); + // Expect that the full tokenIn balance of the collector was sent to the searcher contract + assertEq(tokenIn.balanceOf(address(collector)), 0); + assertEq(tokenIn.balanceOf(address(searcherContract)), 100 ether); + // Expect that the fee tokens were transferred to the fee recipient assertEq(mockFeeToken.balanceOf(address(feeRecipient)), 100 ether); } - function testWithdrawFeeTokenUnauthorized() public { - assertEq(mockFeeToken.balanceOf(address(collector)), 0); - assertEq(mockFeeToken.balanceOf(address(feeRecipient)), 0); - mockFeeToken.mint(address(collector), 100 ether); - assertEq(mockFeeToken.balanceOf(address(collector)), 100 ether); - vm.expectRevert("UNAUTHORIZED"); - vm.prank(address(0xbeef)); - collector.withdrawFeeToken(feeRecipient, 100 ether); - assertEq(mockFeeToken.balanceOf(address(collector)), 100 ether); - } - function testTransferOwnership() public { address newOwner = makeAddr("newOwner"); assertEq(collector.owner(), caller); diff --git a/test/fork/TestFeeCollector.t.sol b/test/fork/TestFeeCollector.t.sol deleted file mode 100644 index fe78c37..0000000 --- a/test/fork/TestFeeCollector.t.sol +++ /dev/null @@ -1,166 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.13; - -import {Test} from "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {ERC20} from "solmate/tokens/ERC20.sol"; -import {MockToken} from "../mock/MockToken.sol"; -import {FeeCollector} from "../../src/FeeCollector.sol"; -import {IFeeCollector} from "../../src/interfaces/IFeeCollector.sol"; -import {IPermit2} from "../../src/external/IPermit2.sol"; - -contract FeeCollectorTest is Test { - ERC20 constant DAI = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); - ERC20 constant USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); - ERC20 constant UNI = ERC20(0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984); - address constant WHALE = 0x55FE002aefF02F77364de339a1292923A15844B8; - address constant UNIVERSAL_ROUTER = 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD; - address constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; - address payable constant FEE_COLLECTOR = payable(0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45); - - address caller; - address feeRecipient; - - FeeCollector collector; - IPermit2 permit2; - - function setUp() public { - caller = makeAddr("caller"); - feeRecipient = makeAddr("feeRecipient"); - - vm.createSelectFork(vm.envString("FORK_URL"), 17972788); - - deployCodeTo("FeeCollector.sol", abi.encode(caller, UNIVERSAL_ROUTER, PERMIT2, address(USDC)), FEE_COLLECTOR); - collector = FeeCollector(FEE_COLLECTOR); - permit2 = IPermit2(PERMIT2); - - assertEq(address(collector), FEE_COLLECTOR, "FeeCollector address does not match expected"); - } - - function testSwapBalance() public { - vm.prank(WHALE); - DAI.transfer(FEE_COLLECTOR, 1000 ether); - - // Check balances and allowances - uint256 preSwapBalance = USDC.balanceOf(address(feeRecipient)); - assertEq(DAI.balanceOf(FEE_COLLECTOR), 1000 ether); - assertEq(USDC.balanceOf(FEE_COLLECTOR), 0); - assertEq(DAI.allowance(FEE_COLLECTOR, PERMIT2), 0); - (uint160 preSwapAllowance,,) = permit2.allowance(FEE_COLLECTOR, address(DAI), UNIVERSAL_ROUTER); - assertEq(preSwapAllowance, 0); - - // Build params for approve and swap - bytes memory DAI_USDC_UR_CALLDATA = - hex"24856bc3000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000010000000000000000000000000068b3465833fb72A70ecDF485E0e4C7bD8665Fc450000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000005adccc500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002b6b175474e89094c44da98b954eedeac495271d0f000064a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000"; - - // Approve DAI to permit2 and permit2 to universal router - vm.startPrank(FEE_COLLECTOR); - DAI.approve(PERMIT2, type(uint256).max); - permit2.approve(address(DAI), UNIVERSAL_ROUTER, type(uint160).max, type(uint48).max); - vm.stopPrank(); - - // Swap collector DAI balance to USDC - vm.prank(caller); - collector.swapBalance(DAI_USDC_UR_CALLDATA, 0); - uint256 collectorUSDCBalance = USDC.balanceOf(address(collector)); - assertEq(collectorUSDCBalance, 99989240); - - // Withdraw USDC to feeRecipient - assertEq(USDC.balanceOf(address(feeRecipient)), preSwapBalance); - vm.prank(caller); - collector.withdrawFeeToken(feeRecipient, collectorUSDCBalance); - assertEq(USDC.balanceOf(address(feeRecipient)), preSwapBalance + collectorUSDCBalance); - assertEq(USDC.balanceOf(FEE_COLLECTOR), 0); - } - - function testSwapBalanceWithApproves() public { - vm.prank(WHALE); - DAI.transfer(FEE_COLLECTOR, 1000 ether); - - // Check balances and allowances - assertEq(DAI.balanceOf(FEE_COLLECTOR), 1000 ether); - assertEq(USDC.balanceOf(address(feeRecipient)), 0); - assertEq(USDC.balanceOf(FEE_COLLECTOR), 0); - assertEq(DAI.allowance(FEE_COLLECTOR, PERMIT2), 0); - (uint160 preSwapAllowance,,) = permit2.allowance(FEE_COLLECTOR, address(DAI), UNIVERSAL_ROUTER); - assertEq(preSwapAllowance, 0); - - // Build params for approve and swap - ERC20[] memory tokensToApprove = new ERC20[](1); - tokensToApprove[0] = DAI; - bytes memory DAI_USDC_UR_CALLDATA = - hex"24856bc3000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000010000000000000000000000000068b3465833fb72A70ecDF485E0e4C7bD8665Fc450000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000005adccc500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002b6b175474e89094c44da98b954eedeac495271d0f000064a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000"; - - // Swap collector DAI balance to USDC - vm.prank(caller); - collector.swapBalance(DAI_USDC_UR_CALLDATA, 0, tokensToApprove); - uint256 collectorUSDCBalance = USDC.balanceOf(address(collector)); - assertEq(collectorUSDCBalance, 99989240); - assertEq(DAI.allowance(FEE_COLLECTOR, PERMIT2), type(uint256).max); - (uint160 postSwapAllowance,,) = permit2.allowance(FEE_COLLECTOR, address(DAI), UNIVERSAL_ROUTER); - assertEq(postSwapAllowance, type(uint160).max); - - // Withdraw USDC to feeRecipient - assertEq(USDC.balanceOf(address(feeRecipient)), 0); - vm.prank(caller); - collector.withdrawFeeToken(feeRecipient, collectorUSDCBalance); - assertEq(USDC.balanceOf(address(feeRecipient)), collectorUSDCBalance); - assertEq(USDC.balanceOf(FEE_COLLECTOR), 0); - } - - function testSwapBalanceNative() public { - vm.deal(FEE_COLLECTOR, 1000 ether); - - // Check balances and allowances - uint256 preSwapBalance = USDC.balanceOf(address(feeRecipient)); - assertEq(FEE_COLLECTOR.balance, 1000 ether); - - // Build params for native swap - bytes memory ETH_USDC_UR_CALLDATA = - hex"24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad00000000000000000000000000000000000000000000003635c9adc5dea00000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc4500000000000000000000000000000000000000000000003635c9adc5dea000000000000000000000000000000000000000000000000000000000015d817f744000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; - - vm.prank(caller); - collector.swapBalance(ETH_USDC_UR_CALLDATA, 1000 ether); - uint256 collectorUSDCBalance = USDC.balanceOf(address(collector)); - assertEq(collectorUSDCBalance, 1531751180017); - - // Withdraw USDC to feeRecipient - assertEq(USDC.balanceOf(address(feeRecipient)), preSwapBalance); - vm.prank(caller); - collector.withdrawFeeToken(feeRecipient, collectorUSDCBalance); - assertEq(USDC.balanceOf(address(feeRecipient)), preSwapBalance + collectorUSDCBalance); - assertEq(USDC.balanceOf(FEE_COLLECTOR), 0); - } - - function testSwapBalanceBatchSwap() public { - vm.prank(WHALE); - DAI.transfer(FEE_COLLECTOR, 100 ether); - vm.deal(FEE_COLLECTOR, 1000 ether); - - // Check balances and allowances - uint256 preSwapBalance = USDC.balanceOf(address(feeRecipient)); - assertEq(FEE_COLLECTOR.balance, 1000 ether); - assertEq(DAI.balanceOf(FEE_COLLECTOR), 100 ether); - - // Build params for approve and swap - ERC20[] memory tokensToApprove = new ERC20[](1); - tokensToApprove[0] = DAI; - bytes memory ETH_AND_DAI_TO_USDC_UR_CALLDATA = - hex"24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000000400000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad00000000000000000000000000000000000000000000003635c9adc5dea00000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc4500000000000000000000000000000000000000000000003635c9adc5dea00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000010000000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc450000000000000000000000000000000000000000000000056bc75e2d63100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002b6b175474e89094c44da98b954eedeac495271d0f000064a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000"; - - // Swap native and DAI to USDC - vm.prank(caller); - collector.swapBalance(ETH_AND_DAI_TO_USDC_UR_CALLDATA, 1000 ether, tokensToApprove); - uint256 collectorUSDCBalance = USDC.balanceOf(address(collector)); - assertEq(DAI.balanceOf(FEE_COLLECTOR), 0); - assertEq(FEE_COLLECTOR.balance, 0); - assertEq(collectorUSDCBalance, 1531851169257); - - // Withdraw USDC to feeRecipient - assertEq(USDC.balanceOf(address(feeRecipient)), preSwapBalance); - vm.prank(caller); - collector.withdrawFeeToken(feeRecipient, collectorUSDCBalance); - assertEq(USDC.balanceOf(address(feeRecipient)), preSwapBalance + collectorUSDCBalance); - assertEq(USDC.balanceOf(FEE_COLLECTOR), 0); - } -} diff --git a/test/mock/MockSearcher.sol b/test/mock/MockSearcher.sol new file mode 100644 index 0000000..9eac125 --- /dev/null +++ b/test/mock/MockSearcher.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {ERC20} from "solmate/tokens/ERC20.sol"; +import {FeeCollector} from "../../src/FeeCollector.sol"; + +contract MockSearcher { + FeeCollector public immutable feeCollector; + + constructor(address payable _feeCollector) { + feeCollector = FeeCollector(_feeCollector); + // approve feeCollector to spend feeToken from this contract + ERC20(feeCollector.feeToken()).approve(address(feeCollector), type(uint256).max); + } + + function swapBalances(ERC20[] memory tokens, bytes calldata call) external { + feeCollector.swapBalances(tokens, call); + } + + fallback() external { + if (msg.sender != address(feeCollector)) { + revert("Unauthorized"); + } + } +}