From 18ffc6bc8101d2da4c66bf2e7bc947f2b62c8e37 Mon Sep 17 00:00:00 2001 From: Manu Date: Thu, 15 Aug 2024 19:19:59 +0200 Subject: [PATCH] New router library --- .vscode/settings.json | 1 + AxelarHandler/.gitmodules | 12 - .../1/dry-run/run-1723057712.json | 31 +++ .../1/dry-run/run-1723057941.json | 31 +++ .../1/dry-run/run-latest.json | 31 +++ AxelarHandler/src/AxelarHandler.sol | 247 ++---------------- .../src/libraries/SkipSwapRouter.sol | 140 ++++++++++ 7 files changed, 259 insertions(+), 234 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 AxelarHandler/.gitmodules create mode 100644 AxelarHandler/broadcast/SimulateExecuteWithToken.s.sol/1/dry-run/run-1723057712.json create mode 100644 AxelarHandler/broadcast/SimulateExecuteWithToken.s.sol/1/dry-run/run-1723057941.json create mode 100644 AxelarHandler/broadcast/SimulateExecuteWithToken.s.sol/1/dry-run/run-latest.json create mode 100644 AxelarHandler/src/libraries/SkipSwapRouter.sol diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/AxelarHandler/.gitmodules b/AxelarHandler/.gitmodules deleted file mode 100644 index 39adc4b..0000000 --- a/AxelarHandler/.gitmodules +++ /dev/null @@ -1,12 +0,0 @@ -[submodule "lib/forge-std"] - path = lib/forge-std - url = https://github.com/foundry-rs/forge-std -[submodule "lib/axelar-gmp-sdk-solidity"] - path = lib/axelar-gmp-sdk-solidity - url = https://github.com/axelarnetwork/axelar-gmp-sdk-solidity -[submodule "lib/openzeppelin-contracts-upgradeable"] - path = lib/openzeppelin-contracts-upgradeable - url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable -[submodule "lib/openzeppelin-contracts"] - path = lib/openzeppelin-contracts - url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/AxelarHandler/broadcast/SimulateExecuteWithToken.s.sol/1/dry-run/run-1723057712.json b/AxelarHandler/broadcast/SimulateExecuteWithToken.s.sol/1/dry-run/run-1723057712.json new file mode 100644 index 0000000..2760f89 --- /dev/null +++ b/AxelarHandler/broadcast/SimulateExecuteWithToken.s.sol/1/dry-run/run-1723057712.json @@ -0,0 +1,31 @@ +{ + "transactions": [ + { + "hash": null, + "transactionType": "CREATE2", + "contractName": "BytesLib", + "contractAddress": "0xbfb201a128d08c99a6228938415b02d20b6ec4ad", + "function": null, + "arguments": null, + "transaction": { + "from": "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x27fab", + "input": "0x000000000000000000000000000000000000000000000000000000000000000061012b61003a600b82828239805160001a60731461002d57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe730000000000000000000000000000000000000000301460806040526004361060335760003560e01c8063593b79fe146038575b600080fd5b60676043366004607b565b604080516001600160a01b0392909216600560a21b18601483015260348201905290565b6040516072919060a9565b60405180910390f35b600060208284031215608c57600080fd5b81356001600160a01b038116811460a257600080fd5b9392505050565b600060208083528351808285015260005b8181101560d45785810183015185820160400152820160ba565b506000604082860101526040601f19601f830116850101925050509291505056fea26469706673582212202001b5e1dbec67ed81daef4e82e5dc66748bca332df0db2a80f4ac32bc7eda7664736f6c63430008120033", + "nonce": "0x0", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [], + "libraries": [ + "src/libraries/Path.sol:BytesLib:0xBfB201a128D08C99a6228938415B02D20b6Ec4ad" + ], + "pending": [], + "returns": {}, + "timestamp": 1723057712, + "chain": 1, + "commit": "502ef75" +} \ No newline at end of file diff --git a/AxelarHandler/broadcast/SimulateExecuteWithToken.s.sol/1/dry-run/run-1723057941.json b/AxelarHandler/broadcast/SimulateExecuteWithToken.s.sol/1/dry-run/run-1723057941.json new file mode 100644 index 0000000..a96fd12 --- /dev/null +++ b/AxelarHandler/broadcast/SimulateExecuteWithToken.s.sol/1/dry-run/run-1723057941.json @@ -0,0 +1,31 @@ +{ + "transactions": [ + { + "hash": null, + "transactionType": "CREATE2", + "contractName": "BytesLib", + "contractAddress": "0xbfb201a128d08c99a6228938415b02d20b6ec4ad", + "function": null, + "arguments": null, + "transaction": { + "from": "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x27fab", + "input": "0x000000000000000000000000000000000000000000000000000000000000000061012b61003a600b82828239805160001a60731461002d57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe730000000000000000000000000000000000000000301460806040526004361060335760003560e01c8063593b79fe146038575b600080fd5b60676043366004607b565b604080516001600160a01b0392909216600560a21b18601483015260348201905290565b6040516072919060a9565b60405180910390f35b600060208284031215608c57600080fd5b81356001600160a01b038116811460a257600080fd5b9392505050565b600060208083528351808285015260005b8181101560d45785810183015185820160400152820160ba565b506000604082860101526040601f19601f830116850101925050509291505056fea26469706673582212202001b5e1dbec67ed81daef4e82e5dc66748bca332df0db2a80f4ac32bc7eda7664736f6c63430008120033", + "nonce": "0x0", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [], + "libraries": [ + "src/libraries/Path.sol:BytesLib:0xBfB201a128D08C99a6228938415B02D20b6Ec4ad" + ], + "pending": [], + "returns": {}, + "timestamp": 1723057941, + "chain": 1, + "commit": "502ef75" +} \ No newline at end of file diff --git a/AxelarHandler/broadcast/SimulateExecuteWithToken.s.sol/1/dry-run/run-latest.json b/AxelarHandler/broadcast/SimulateExecuteWithToken.s.sol/1/dry-run/run-latest.json new file mode 100644 index 0000000..a96fd12 --- /dev/null +++ b/AxelarHandler/broadcast/SimulateExecuteWithToken.s.sol/1/dry-run/run-latest.json @@ -0,0 +1,31 @@ +{ + "transactions": [ + { + "hash": null, + "transactionType": "CREATE2", + "contractName": "BytesLib", + "contractAddress": "0xbfb201a128d08c99a6228938415b02d20b6ec4ad", + "function": null, + "arguments": null, + "transaction": { + "from": "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x27fab", + "input": "0x000000000000000000000000000000000000000000000000000000000000000061012b61003a600b82828239805160001a60731461002d57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe730000000000000000000000000000000000000000301460806040526004361060335760003560e01c8063593b79fe146038575b600080fd5b60676043366004607b565b604080516001600160a01b0392909216600560a21b18601483015260348201905290565b6040516072919060a9565b60405180910390f35b600060208284031215608c57600080fd5b81356001600160a01b038116811460a257600080fd5b9392505050565b600060208083528351808285015260005b8181101560d45785810183015185820160400152820160ba565b506000604082860101526040601f19601f830116850101925050509291505056fea26469706673582212202001b5e1dbec67ed81daef4e82e5dc66748bca332df0db2a80f4ac32bc7eda7664736f6c63430008120033", + "nonce": "0x0", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [], + "libraries": [ + "src/libraries/Path.sol:BytesLib:0xBfB201a128D08C99a6228938415B02D20b6Ec4ad" + ], + "pending": [], + "returns": {}, + "timestamp": 1723057941, + "chain": 1, + "commit": "502ef75" +} \ No newline at end of file diff --git a/AxelarHandler/src/AxelarHandler.sol b/AxelarHandler/src/AxelarHandler.sol index 59fd678..d301cd8 100644 --- a/AxelarHandler/src/AxelarHandler.sol +++ b/AxelarHandler/src/AxelarHandler.sol @@ -14,14 +14,13 @@ import {UUPSUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/ import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; import {ISwapRouter02} from "./interfaces/ISwapRouter02.sol"; -import {BytesLib, Path} from "./libraries/Path.sol"; +import {SkipSwapRouter} from "./libraries/SkipSwapRouter.sol"; /// @title AxelarHandler /// @notice allows to send and receive tokens to/from other chains through axelar gateway while wrapping the native tokens. /// @author Skip Protocol. contract AxelarHandler is AxelarExecutableUpgradeable, Ownable2StepUpgradeable, UUPSUpgradeable { using SafeERC20 for IERC20; - using Path for bytes; error EmptySymbol(); error NativeSentDoesNotMatchAmounts(); @@ -43,7 +42,8 @@ contract AxelarHandler is AxelarExecutableUpgradeable, Ownable2StepUpgradeable, enum Commands { SendToken, SendNative, - Swap + Swap, + MultiSwap } bytes32 private _wETHSymbolHash; @@ -388,34 +388,31 @@ contract AxelarHandler is AxelarExecutableUpgradeable, Ownable2StepUpgradeable, _sendToken(token, amount, destination); } } else if (command == Commands.Swap) { - (address destination, uint256 amountOutMin, bool unwrapOut, bytes[] memory swaps) = - abi.decode(data, (address, uint256, bool, bytes[])); - - uint256 length = swaps.length; - for (uint256 i; i < length; ++i) { - (uint8 swapFunction, bytes memory swapPayload) = abi.decode(swaps[i], (uint8, bytes)); - - if (swapFunction == uint8(0)) { - (token, amount) = _exactInputSingleSwap(token, destination, amount, swapPayload); - } else if (swapFunction == uint8(1)) { - (token, amount) = _exactInputSwap(token, destination, amount, swapPayload); - } else if (swapFunction == uint8(2)) { - (token, amount) = _exactTokensForTokensSwap(token, destination, amount, swapPayload); - } else if (swapFunction == uint8(3)) { - (token, amount) = _exactOutputSingleSwap(token, destination, amount, swapPayload); - } else if (swapFunction == uint8(4)) { - (token, amount) = _exactOutputSwap(token, destination, amount, swapPayload); - } else if (swapFunction == uint8(5)) { - (token, amount) = _tokensForExactTokensSwap(token, destination, amount, swapPayload); + (address destination, bool unwrapOut, bytes memory swap) = abi.decode(data, (address, bool, bytes)); + + try SkipSwapRouter.swap(swapRouter, destination, tokenIn, amount, swap) returns ( + IERC20 tokenOut, uint256 amountOut + ) { + if (unwrapOut && address(tokenOut) == _getTokenAddress(wETHSymbol)) { + _sendNative(address(tokenOut), amountOut, destination); } else { - revert FunctionCodeNotSupported(); + _sendToken(address(tokenOut), amountOut, destination); } + } catch { + _sendToken(token, amount, destination); } - - if (amount < amountOutMin) revert InsufficientSwapOutput(); - if (unwrapOut && token == _getTokenAddress(wETHSymbol)) { - _sendNative(token, amount, destination); - } else { + } else if (command == Commands.MultiSwap) { + (address destination, bool unwrapOut, bytes[] memory swaps) = abi.decode(data, (address, bool, bytes[])); + + try SkipSwapRouter.multiSwap(swapRouter, destination, tokenIn, amount, swaps) returns ( + IERC20 tokenOut, uint256 amountOut + ) { + if (unwrapOut && address(tokenOut) == _getTokenAddress(wETHSymbol)) { + _sendNative(address(tokenOut), amountOut, destination); + } else { + _sendToken(address(tokenOut), amountOut, destination); + } + } catch { _sendToken(token, amount, destination); } } else { @@ -440,199 +437,5 @@ contract AxelarHandler is AxelarExecutableUpgradeable, Ownable2StepUpgradeable, } } - function _exactInputSingleSwap(address token, address destination, uint256 amount, bytes memory data) - internal - returns (address tokenOut, uint256 amountOut) - { - ISwapRouter02.ExactInputSingleParams memory params; - (tokenOut, params.fee, params.sqrtPriceLimitX96) = abi.decode(data, (address, uint24, uint160)); - - params.tokenIn = token; - params.tokenOut = tokenOut; - params.amountIn = amount; - params.recipient = address(this); - - IERC20 tokenSwapIn = IERC20(token); - IERC20 tokenSwapOut = IERC20(tokenOut); - - uint256 preBalIn = tokenSwapIn.balanceOf(address(this)) - amount; - uint256 preBalOut = tokenSwapOut.balanceOf(address(this)); - - tokenSwapIn.safeApprove(address(swapRouter), amount); - swapRouter.exactInputSingle(params); - - uint256 dustIn = tokenSwapIn.balanceOf(address(this)) - preBalIn; - amountOut = tokenSwapOut.balanceOf(address(this)) - preBalOut; - - if (dustIn != 0) { - tokenSwapIn.safeApprove(address(swapRouter), 0); - tokenSwapIn.safeTransfer(destination, dustIn); - } - } - - function _exactInputSwap(address token, address destination, uint256 amount, bytes memory data) - internal - returns (address tokenOut, uint256 amountOut) - { - ISwapRouter02.ExactInputParams memory params; - params.path = data; - - params.recipient = address(this); - params.amountIn = amount; - - (address tokenA,,) = params.path.decodeFirstPool(); - - if (tokenA != token) { - bytes memory tokenReplace = BytesLib.toBytes(token); - params.path = BytesLib.concat(tokenReplace, BytesLib.slice(params.path, 20, params.path.length - 20)); - } - - (, tokenOut,) = params.path.decodeLastPool(); - - IERC20 tokenSwapIn = IERC20(token); - IERC20 tokenSwapOut = IERC20(tokenOut); - - uint256 preBalIn = tokenSwapIn.balanceOf(address(this)) - amount; - uint256 preBalOut = tokenSwapOut.balanceOf(address(this)); - - tokenSwapIn.safeApprove(address(swapRouter), amount); - swapRouter.exactInput(params); - - uint256 dustIn = tokenSwapIn.balanceOf(address(this)) - preBalIn; - amountOut = tokenSwapOut.balanceOf(address(this)) - preBalOut; - - if (dustIn != 0) { - tokenSwapIn.safeApprove(address(swapRouter), 0); - tokenSwapIn.safeTransfer(destination, dustIn); - } - } - - function _exactOutputSingleSwap(address token, address destination, uint256 amount, bytes memory data) - internal - returns (address tokenOut, uint256 amountOut) - { - ISwapRouter02.ExactOutputSingleParams memory params; - (tokenOut, amountOut, params.fee, params.sqrtPriceLimitX96) = - abi.decode(data, (address, uint256, uint24, uint160)); - - params.tokenIn = token; - params.tokenOut = tokenOut; - params.recipient = address(this); - params.amountOut = amountOut; - params.amountInMaximum = amount; - - IERC20 tokenSwapIn = IERC20(token); - IERC20 tokenSwapOut = IERC20(tokenOut); - - uint256 preBalIn = tokenSwapIn.balanceOf(address(this)) - amount; - uint256 preBalOut = tokenSwapOut.balanceOf(address(this)); - - tokenSwapIn.safeApprove(address(swapRouter), amount); - swapRouter.exactOutputSingle(params); - - uint256 dustIn = tokenSwapIn.balanceOf(address(this)) - preBalIn; - amountOut = tokenSwapOut.balanceOf(address(this)) - preBalOut; - - if (dustIn != 0) { - tokenSwapIn.safeApprove(address(swapRouter), 0); - tokenSwapIn.safeTransfer(destination, dustIn); - } - } - - function _exactOutputSwap(address token, address destination, uint256 amount, bytes memory data) - internal - returns (address tokenOut, uint256 amountOut) - { - ISwapRouter02.ExactOutputParams memory params; - (amountOut, params.path) = abi.decode(data, (uint256, bytes)); - - params.recipient = address(this); - params.amountOut = amountOut; - params.amountInMaximum = amount; - - (, address tokenB,) = params.path.decodeLastPool(); - - if (tokenB != token) { - bytes memory tokenReplace = BytesLib.toBytes(token); - params.path = BytesLib.concat(BytesLib.slice(params.path, 0, params.path.length - 20), tokenReplace); - } - - (tokenOut,,) = params.path.decodeFirstPool(); - - IERC20 tokenSwapIn = IERC20(token); - IERC20 tokenSwapOut = IERC20(tokenOut); - - uint256 preBalIn = tokenSwapIn.balanceOf(address(this)) - amount; - uint256 preBalOut = tokenSwapOut.balanceOf(address(this)); - - tokenSwapIn.safeApprove(address(swapRouter), amount); - swapRouter.exactOutput(params); - - uint256 dustIn = tokenSwapIn.balanceOf(address(this)) - preBalIn; - amountOut = tokenSwapOut.balanceOf(address(this)) - preBalOut; - - if (dustIn != 0) { - tokenSwapIn.safeApprove(address(swapRouter), 0); - tokenSwapIn.safeTransfer(destination, dustIn); - } - } - - function _exactTokensForTokensSwap(address token, address destination, uint256 amount, bytes memory data) - internal - returns (address tokenOut, uint256 amountOut) - { - (address[] memory path) = abi.decode(data, (address[])); - - path[0] == token; - - tokenOut = path[path.length - 1]; - - IERC20 tokenSwapIn = IERC20(token); - IERC20 tokenSwapOut = IERC20(tokenOut); - - uint256 preBalIn = tokenSwapIn.balanceOf(address(this)) - amount; - uint256 preBalOut = tokenSwapOut.balanceOf(address(this)); - - tokenSwapIn.safeApprove(address(swapRouter), amount); - swapRouter.swapExactTokensForTokens(amount, 0, path, address(this)); - - uint256 dustIn = tokenSwapIn.balanceOf(address(this)) - preBalIn; - amountOut = tokenSwapOut.balanceOf(address(this)) - preBalOut; - - if (dustIn != 0) { - tokenSwapIn.safeApprove(address(swapRouter), 0); - tokenSwapIn.safeTransfer(destination, dustIn); - } - } - - function _tokensForExactTokensSwap(address token, address destination, uint256 amount, bytes memory data) - internal - returns (address tokenOut, uint256 amountOut) - { - address[] memory path; - (amountOut, path) = abi.decode(data, (uint256, address[])); - - path[0] == token; - - tokenOut = path[path.length - 1]; - - IERC20 tokenSwapIn = IERC20(token); - IERC20 tokenSwapOut = IERC20(tokenOut); - - uint256 preBalIn = tokenSwapIn.balanceOf(address(this)) - amount; - uint256 preBalOut = tokenSwapOut.balanceOf(address(this)); - - tokenSwapIn.safeApprove(address(swapRouter), amount); - swapRouter.swapTokensForExactTokens(amountOut, amount, path, address(this)); - - uint256 dustIn = tokenSwapIn.balanceOf(address(this)) - preBalIn; - amountOut = tokenSwapOut.balanceOf(address(this)) - preBalOut; - - if (dustIn != 0) { - tokenSwapIn.safeApprove(address(swapRouter), 0); - tokenSwapIn.safeTransfer(destination, dustIn); - } - } - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} } diff --git a/AxelarHandler/src/libraries/SkipSwapRouter.sol b/AxelarHandler/src/libraries/SkipSwapRouter.sol new file mode 100644 index 0000000..70e228d --- /dev/null +++ b/AxelarHandler/src/libraries/SkipSwapRouter.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: UNLICENSED +import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; +import {ISwapRouter02} from "../interfaces/ISwapRouter02.sol"; +import {BytesLib, Path} from "./Path.sol"; + +pragma solidity >= 0.8.18; + +library SkipSwapRouter { + using SafeERC20 for IERC20; + using Path for bytes; + + error InsufficientOutputAmount(); + + enum SwapCommands { + ExactInputSingle, + ExactInput, + ExactTokensForTokens, + ExactOutputSingle, + ExactOutput, + TokensForExactTokens + } + + function multiSwap( + ISwapRouter02 router, + address destination, + IERC20 inputToken, + uint256 amountIn, + bytes[] memory swaps + ) external returns (IERC20 outputToken, uint256 amountOut) { + outputToken = inputToken; + amountOut = amountIn; + + uint256 numSwaps = swaps.length; + for (uint256 i; i < numSwaps; i++) { + // The output token and amount of each iteration is the input token and amount of the next. + (outputToken, amountOut) = swap(router, destination, outputToken, amountOut, swaps[i]); + } + } + + function swap(ISwapRouter02 router, address destination, IERC20 inputToken, uint256 amountIn, bytes memory payload) + public + returns (IERC20 outputToken, uint256 amountOut) + { + (SwapCommands command, address tokenOut, uint256 amountOut, bytes memory swapData) = + abi.decode(payload, (SwapCommands, address, uint256, bytes)); + + outputToken = IERC20(tokenOut); + + uint256 preBalIn = inputToken.balanceOf(address(this)) - amountIn; + uint256 preBalOut = outputToken.balanceOf(address(this)); + + inputToken.forceApprove(address(router), amountIn); + + if (command == SwapCommands.ExactInputSingle) { + ISwapRouter02.ExactInputSingleParams memory params; + params.tokenIn = address(inputToken); + params.tokenOut = tokenOut; + params.recipient = address(this); + params.amountIn = amountIn; + params.amountOutMinimum = amountOut; + + (params.fee, params.sqrtPriceLimitX96) = abi.decode(swapData, (uint24, uint160)); + + router.exactInputSingle(params); + } else if (command == SwapCommands.ExactInput) { + ISwapRouter02.ExactInputParams memory params; + params.path = _fixPath(address(inputToken), tokenOut, swapData); + params.recipient = address(this); + params.amountIn = amountIn; + params.amountOutMinimum = amountOut; + + router.exactInput(params); + } else if (command == SwapCommands.ExactTokensForTokens) { + address[] memory path = _fixPath(address(inputToken), tokenOut, abi.decode(swapData, (address[]))); + + router.swapExactTokensForTokens(amountIn, amountOut, path, address(this)); + } else if (command == SwapCommands.ExactOutputSingle) { + ISwapRouter02.ExactOutputSingleParams memory params; + params.tokenIn = address(inputToken); + params.tokenOut = tokenOut; + params.recipient = address(this); + params.amountInMaximum = amountIn; + params.amountOut = amountOut; + + (params.fee, params.sqrtPriceLimitX96) = abi.decode(swapData, (uint24, uint160)); + + router.exactOutputSingle(params); + } else if (command == SwapCommands.ExactOutput) { + ISwapRouter02.ExactOutputParams memory params; + params.path = _fixPath(tokenOut, address(inputToken), swapData); + params.recipient = address(this); + params.amountInMaximum = amountIn; + params.amountOut = amountOut; + + router.exactOutput(params); + } else if (command == SwapCommands.TokensForExactTokens) { + address[] memory path = _fixPath(tokenOut, address(inputToken), abi.decode(swapData, (address[]))); + + router.swapTokensForExactTokens(amountOut, amountIn, path, address(this)); + } + + if (amountOut < outputToken.balanceOf(address(this)) - preBalOut) { + revert InsufficientOutputAmount(); + } + + uint256 dust = inputToken.balanceOf(address(this)) - preBalIn; + if (dust != 0) { + inputToken.forceApprove(address(router), 0); + inputToken.safeTransfer(destination, dust); + } + } + + function _fixPath(address tokenA, address tokenB, bytes memory path) internal pure returns (bytes memory) { + (address decodedA,,) = path.decodeFirstPool(); + if (decodedA != tokenA) { + path = BytesLib.concat(BytesLib.toBytes(tokenA), BytesLib.slice(path, 20, path.length - 20)); + } + + (, address decodedB,) = path.decodeLastPool(); + if (decodedB != tokenB) { + path = BytesLib.concat(BytesLib.slice(path, 0, path.length - 20), BytesLib.toBytes(tokenB)); + } + + return path; + } + + function _fixPath(address tokenA, address tokenB, address[] memory path) internal pure returns (address[] memory) { + if (path[0] != tokenA) { + path[0] = tokenA; + } + + uint256 lastElement = path.length - 1; + if (path[lastElement] != tokenB) { + path[lastElement] = tokenB; + } + + return path; + } +}