diff --git a/foundry/foundry.toml b/foundry/foundry.toml index a3c8d7b..4a2b1a6 100644 --- a/foundry/foundry.toml +++ b/foundry/foundry.toml @@ -9,7 +9,8 @@ optimizer_runs = 1000 via_ir = true [rpc_endpoints] -mainnet = "${RPC_URL}" +# Public RPC endpoint +mainnet = "https://eth.llamarpc.com" [fmt] diff --git a/foundry/src/executors/BalancerV2Executor.sol b/foundry/src/executors/BalancerV2Executor.sol index 1572b17..460c9e6 100644 --- a/foundry/src/executors/BalancerV2Executor.sol +++ b/foundry/src/executors/BalancerV2Executor.sol @@ -29,7 +29,7 @@ contract BalancerV2Executor is IExecutor { ) = _decodeData(data); if (needsApproval) { - tokenIn.approve(VAULT, type(uint256).max); + tokenIn.approve(VAULT, givenAmount); } IVault.SingleSwap memory singleSwap = IVault.SingleSwap({ diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index ed1b66f..1410654 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -18,6 +18,7 @@ contract Constants is Test { // Assets address WETH_ADDR = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); address DAI_ADDR = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); + address BAL_ADDR = address(0xba100000625a3754423978a60c9317c58a424e3D); /** * @dev Deploys a dummy contract with non-empty bytecode diff --git a/foundry/test/executors/BalancerV2Executor.t.sol b/foundry/test/executors/BalancerV2Executor.t.sol new file mode 100644 index 0000000..6d34c9a --- /dev/null +++ b/foundry/test/executors/BalancerV2Executor.t.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "@src/executors/BalancerV2Executor.sol"; +import {Test} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "../Constants.sol"; + +contract BalancerV2ExecutorExposed is BalancerV2Executor { + function decodeParams(bytes calldata data) + external + pure + returns ( + IERC20 tokenIn, + IERC20 tokenOut, + bytes32 poolId, + address receiver, + bool needsApproval + ) + { + return _decodeData(data); + } +} + +contract BalancerV2ExecutorTest is + BalancerV2ExecutorExposed, + Test, + Constants +{ + using SafeERC20 for IERC20; + + BalancerV2ExecutorExposed balancerV2Exposed; + IERC20 WETH = IERC20(WETH_ADDR); + IERC20 BAL = IERC20(BAL_ADDR); + bytes32 constant WETH_BAL_POOL_ID = + 0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014; + + function setUp() public { + uint256 forkBlock = 17323404; + vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); + balancerV2Exposed = new BalancerV2ExecutorExposed(); + } + + function testDecodeParams() public view { + bytes memory params = abi.encodePacked( + WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, address(2), true + ); + + ( + IERC20 tokenIn, + IERC20 tokenOut, + bytes32 poolId, + address receiver, + bool needsApproval + ) = balancerV2Exposed.decodeParams(params); + + assertEq(address(tokenIn), WETH_ADDR); + assertEq(address(tokenOut), BAL_ADDR); + assertEq(poolId, WETH_BAL_POOL_ID); + assertEq(receiver, address(2)); + assertEq(needsApproval, true); + } + + function testDecodeParamsInvalidDataLength() public { + bytes memory invalidParams = + abi.encodePacked(WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, address(2)); + + vm.expectRevert(BalancerV2Executor__InvalidDataLength.selector); + balancerV2Exposed.decodeParams(invalidParams); + } + + function testSwap() public { + uint256 amountIn = 10 ** 18; + bytes memory protocolData = + abi.encodePacked(WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, BOB, true); + + deal(WETH_ADDR, address(balancerV2Exposed), amountIn); + uint256 balanceBefore = BAL.balanceOf(BOB); + + uint256 amountOut = balancerV2Exposed.swap(amountIn, protocolData); + + uint256 balanceAfter = BAL.balanceOf(BOB); + assertGt(balanceAfter, balanceBefore); + assertEq(balanceAfter - balanceBefore, amountOut); + } +}