// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../interfaces/IBalancerVault.sol"; import "../RouteTypesV2.sol"; import "../interfaces/IRouteExecutorAdapter.sol"; contract BalancerRouteExecutorAdapter is IRouteExecutorAdapter { using SafeERC20 for IERC20; function validate( RouteTypesV2.RouteLeg calldata leg ) external pure override returns (bool ok, string memory reason) { if (leg.provider != RouteTypesV2.Provider.Balancer) { return (false, "BalancerRouteExecutorAdapter: invalid provider"); } if (leg.target == address(0)) { return (false, "BalancerRouteExecutorAdapter: zero target"); } if (leg.providerData.length != 32) { return (false, "BalancerRouteExecutorAdapter: invalid providerData"); } return (true, ""); } function quote( RouteTypesV2.RouteLeg calldata leg, uint256 amountIn ) external view override returns (uint256 amountOut, uint256 gasEstimate) { bytes32 poolId = abi.decode(leg.providerData, (bytes32)); (address[] memory tokens, uint256[] memory balances,) = IBalancerVault(leg.target).getPoolTokens(poolId); uint256 inIndex = type(uint256).max; uint256 outIndex = type(uint256).max; for (uint256 i = 0; i < tokens.length; i++) { if (tokens[i] == leg.tokenIn) inIndex = i; if (tokens[i] == leg.tokenOut) outIndex = i; } if (inIndex != type(uint256).max && outIndex != type(uint256).max && balances[inIndex] > 0) { uint256 grossOut = (amountIn * balances[outIndex]) / balances[inIndex]; amountOut = (grossOut * 9950) / 10000; } gasEstimate = 230000; } function execute( RouteTypesV2.RouteLeg calldata leg, uint256 amountIn ) external override returns (uint256 amountOut) { bytes32 poolId = abi.decode(leg.providerData, (bytes32)); IERC20(leg.tokenIn).forceApprove(leg.target, 0); IERC20(leg.tokenIn).forceApprove(leg.target, amountIn); amountOut = IBalancerVault(leg.target).swap( IBalancerVault.SingleSwap({ poolId: poolId, kind: IBalancerVault.SwapKind.GIVEN_IN, assetIn: leg.tokenIn, assetOut: leg.tokenOut, amount: amountIn, userData: "" }), IBalancerVault.FundManagement({ sender: address(this), fromInternalBalance: false, recipient: payable(msg.sender), toInternalBalance: false }), leg.minAmountOut, block.timestamp + 300 ); } }