diff --git a/foundry/src/executors/BalancerV2Executor.sol b/foundry/src/executors/BalancerV2Executor.sol index 0f69fcd..817c98b 100644 --- a/foundry/src/executors/BalancerV2Executor.sol +++ b/foundry/src/executors/BalancerV2Executor.sol @@ -10,14 +10,17 @@ import { import {IAsset} from "@balancer-labs/v2-interfaces/contracts/vault/IAsset.sol"; // slither-disable-next-line solc-version import {IVault} from "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol"; +import {TokenTransfer} from "./TokenTransfer.sol"; error BalancerV2Executor__InvalidDataLength(); -contract BalancerV2Executor is IExecutor { +contract BalancerV2Executor is IExecutor, TokenTransfer { using SafeERC20 for IERC20; address private constant VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; + constructor(address _permit2) TokenTransfer(_permit2) {} + // slither-disable-next-line locked-ether function swap(uint256 givenAmount, bytes calldata data) external @@ -29,9 +32,20 @@ contract BalancerV2Executor is IExecutor { IERC20 tokenOut, bytes32 poolId, address receiver, - bool needsApproval + bool needsApproval, + TransferType transferType ) = _decodeData(data); + _transfer( + address(tokenIn), + msg.sender, + // Receiver can never be the pool, since the pool expects funds in the router contract + // Thus, this call will only ever be used to transfer funds from the user into the router. + address(this), + givenAmount, + transferType + ); + if (needsApproval) { // slither-disable-next-line unused-return tokenIn.approve(VAULT, type(uint256).max); @@ -67,10 +81,11 @@ contract BalancerV2Executor is IExecutor { IERC20 tokenOut, bytes32 poolId, address receiver, - bool needsApproval + bool needsApproval, + TransferType transferType ) { - if (data.length != 93) { + if (data.length != 94) { revert BalancerV2Executor__InvalidDataLength(); } @@ -79,5 +94,6 @@ contract BalancerV2Executor is IExecutor { poolId = bytes32(data[40:72]); receiver = address(bytes20(data[72:92])); needsApproval = uint8(data[92]) > 0; + transferType = TransferType(uint8(data[93])); } } diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index 9a8c1e6..38173e9 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -64,7 +64,15 @@ contract CurveExecutor is IExecutor, TokenTransfer { TransferType transferType ) = _decodeData(data); - _transfer(tokenIn, msg.sender, pool, amountIn, transferType); + _transfer( + tokenIn, + msg.sender, + // Receiver can never be the pool, since the pool expects funds in the router contract + // Thus, this call will only ever be used to transfer funds from the user into the router. + address(this), + amountIn, + transferType + ); if (tokenApprovalNeeded && tokenIn != nativeToken) { // slither-disable-next-line unused-return diff --git a/foundry/src/executors/UniswapV4Executor.sol b/foundry/src/executors/UniswapV4Executor.sol index adcea98..aafbd7d 100644 --- a/foundry/src/executors/UniswapV4Executor.sol +++ b/foundry/src/executors/UniswapV4Executor.sol @@ -53,7 +53,9 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer { _transfer( tokenIn, msg.sender, - address(this), // irrelevant attribute + // Receiver can never be the pool, since the pool expects funds in the router contract + // Thus, this call will only ever be used to transfer funds from the user into the router. + address(this), amountIn, transferType ); diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index d5b9651..83b665f 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -106,7 +106,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { pancakev3Executor = new UniswapV3Executor( factoryPancakeV3, initCodePancakeV3, PERMIT2_ADDRESS ); - balancerv2Executor = new BalancerV2Executor(); + balancerv2Executor = new BalancerV2Executor(PERMIT2_ADDRESS); ekuboExecutor = new EkuboExecutor(ekuboCore); curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE, PERMIT2_ADDRESS); diff --git a/foundry/test/executors/BalancerV2Executor.t.sol b/foundry/test/executors/BalancerV2Executor.t.sol index 9187187..78b435b 100644 --- a/foundry/test/executors/BalancerV2Executor.t.sol +++ b/foundry/test/executors/BalancerV2Executor.t.sol @@ -6,6 +6,8 @@ import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; contract BalancerV2ExecutorExposed is BalancerV2Executor { + constructor(address _permit2) BalancerV2Executor(_permit2) {} + function decodeParams(bytes calldata data) external pure @@ -14,18 +16,15 @@ contract BalancerV2ExecutorExposed is BalancerV2Executor { IERC20 tokenOut, bytes32 poolId, address receiver, - bool needsApproval + bool needsApproval, + TransferType transferType ) { return _decodeData(data); } } -contract BalancerV2ExecutorTest is - BalancerV2ExecutorExposed, - Test, - Constants -{ +contract BalancerV2ExecutorTest is Test, Constants { using SafeERC20 for IERC20; BalancerV2ExecutorExposed balancerV2Exposed; @@ -37,12 +36,17 @@ contract BalancerV2ExecutorTest is function setUp() public { uint256 forkBlock = 17323404; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - balancerV2Exposed = new BalancerV2ExecutorExposed(); + balancerV2Exposed = new BalancerV2ExecutorExposed(PERMIT2_ADDRESS); } function testDecodeParams() public view { bytes memory params = abi.encodePacked( - WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, address(2), true + WETH_ADDR, + BAL_ADDR, + WETH_BAL_POOL_ID, + address(2), + true, + TokenTransfer.TransferType.NONE ); ( @@ -50,7 +54,8 @@ contract BalancerV2ExecutorTest is IERC20 tokenOut, bytes32 poolId, address receiver, - bool needsApproval + bool needsApproval, + TokenTransfer.TransferType transferType ) = balancerV2Exposed.decodeParams(params); assertEq(address(tokenIn), WETH_ADDR); @@ -58,6 +63,7 @@ contract BalancerV2ExecutorTest is assertEq(poolId, WETH_BAL_POOL_ID); assertEq(receiver, address(2)); assertEq(needsApproval, true); + assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE)); } function testDecodeParamsInvalidDataLength() public { @@ -70,8 +76,14 @@ contract BalancerV2ExecutorTest is function testSwap() public { uint256 amountIn = 10 ** 18; - bytes memory protocolData = - abi.encodePacked(WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, BOB, true); + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + BAL_ADDR, + WETH_BAL_POOL_ID, + BOB, + true, + TokenTransfer.TransferType.NONE + ); deal(WETH_ADDR, address(balancerV2Exposed), amountIn); uint256 balanceBefore = BAL.balanceOf(BOB); @@ -86,14 +98,15 @@ contract BalancerV2ExecutorTest is function testDecodeIntegration() public view { // Generated by the SwapEncoder - test_encode_balancer_v2 bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e01"; + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0105"; ( IERC20 tokenIn, IERC20 tokenOut, bytes32 poolId, address receiver, - bool needsApproval + bool needsApproval, + TokenTransfer.TransferType transferType ) = balancerV2Exposed.decodeParams(protocolData); assertEq(address(tokenIn), WETH_ADDR); @@ -101,12 +114,13 @@ contract BalancerV2ExecutorTest is assertEq(poolId, WETH_BAL_POOL_ID); assertEq(receiver, BOB); assertEq(needsApproval, true); + assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE)); } function testSwapIntegration() public { // Generated by the SwapEncoder - test_encode_balancer_v2 bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e01"; + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0105"; uint256 amountIn = 10 ** 18; deal(WETH_ADDR, address(balancerV2Exposed), amountIn); diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 2ea8954..1602ae3 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -281,6 +281,7 @@ impl SwapEncoder for BalancerV2SwapEncoder { component_id, bytes_to_address(&encoding_context.receiver)?, approval_needed, + (encoding_context.transfer_type as u8).to_be_bytes(), ); Ok(args.abi_encode_packed()) } @@ -703,7 +704,7 @@ mod tests { router_address: Some(Bytes::zero(20)), group_token_in: token_in.clone(), group_token_out: token_out.clone(), - transfer_type: TransferType::Transfer, + transfer_type: TransferType::None, }; let encoder = BalancerV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -731,7 +732,9 @@ mod tests { // receiver "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", // approval needed - "01" + "01", + // transfer type + "05" )) ); }