diff --git a/contracts/TridentRouter.sol b/contracts/TridentRouter.sol index fbc20a06..282fcb13 100644 --- a/contracts/TridentRouter.sol +++ b/contracts/TridentRouter.sol @@ -10,6 +10,7 @@ import {IMasterDeployer} from "./interfaces/IMasterDeployer.sol"; import {IPool} from "./interfaces/IPool.sol"; import {ITridentRouter} from "./interfaces/ITridentRouter.sol"; import {IWETH9} from "./interfaces/IWETH9.sol"; +import {TridentRouterLibrary} from "./libraries/TridentRouterLibrary.sol"; /// @dev Custom Errors error NotWethSender(); @@ -64,6 +65,22 @@ contract TridentRouter is ITridentRouter, SelfPermit, Multicall { if (amountOut < params.amountOutMinimum) revert TooLittleReceived(); } + /// @notice Swaps token B to token A directly. Swaps are done on `bento` tokens. + /// @param params This includes the address of token B, pool, amount of token B to swap, + /// maximum amount of token A for the swap and data required by the pool for the swap. + /// @dev Ensure that the pool is trusted before calling this function. The pool can steal users' tokens. + function exactOutputSingle(ExactOutputSingleParams calldata params) public payable returns (uint256 amountIn) { + amountIn = TridentRouterLibrary.getAmountIn(params.pool, params.amountOut, params.tokenOut); + + if (amountIn > params.amountInMaximum) revert TooLittleReceived(); + + address tokenIn = abi.decode(params.data, (address)); + + bento.transfer(tokenIn, msg.sender, params.pool, amountIn); + + IPool(params.pool).swap(params.data); + } + /// @notice Swaps token A to token B indirectly by using multiple hops. /// @param params This includes the addresses of the tokens, pools, amount of token A to swap, /// minimum amount of token B after the swap and data required by the pools for the swaps. @@ -84,6 +101,26 @@ contract TridentRouter is ITridentRouter, SelfPermit, Multicall { if (amountOut < params.amountOutMinimum) revert TooLittleReceived(); } + /// @notice Swaps token B to token A indirectly by using multiple hops. + /// @param params This includes the addresses of the tokens, pools, amount of token B to swap, + /// maximum amount of token A for the swap and data required by the pools for the swaps. + /// @dev Ensure that the pools are trusted before calling this function. The pools can steal users' tokens. + function exactOutput(ExactOutputParams calldata params) public payable returns (uint256 amountIn) { + amountIn = TridentRouterLibrary.getAmountsIn(params.path, params.tokenOut, params.amountOut)[0]; + + if (amountIn > params.amountInMaximum) revert TooLittleReceived(); + + address tokenIn = abi.decode(params.path[0].data, (address)); + + bento.transfer(tokenIn, msg.sender, params.path[0].pool, amountIn); + + uint256 n = params.path.length; + + for (uint256 i = 0; i < n; i = _increment(i)) { + IPool(params.path[i].pool).swap(params.path[i].data); + } + } + /// @notice Swaps token A to token B directly. It's the same as `exactInputSingle` except /// it takes raw ERC-20 tokens from the users and deposits them into `bento`. /// @param params This includes the address of token A, pool, amount of token A to swap, @@ -98,6 +135,23 @@ contract TridentRouter is ITridentRouter, SelfPermit, Multicall { if (amountOut < params.amountOutMinimum) revert TooLittleReceived(); } + /// @notice Swaps token B to token A directly. It's the same as `exactOutputSingle` except + /// it takes raw ERC-20 tokens from the users and deposits them into `bento`. + /// @param params This includes the address of token B, pool, amount of token B to swap, + /// minimum amount of token A for the swap and data required by the pool for the swap. + /// @dev Ensure that the pool is trusted before calling this function. The pool can steal users' tokens. + function exactOutputSingleWithNativeTokens(ExactOutputSingleParams calldata params) public payable returns (uint256 amountIn) { + amountIn = TridentRouterLibrary.getAmountIn(params.pool, params.amountOut, params.tokenOut); + + if (amountIn > params.amountInMaximum) revert TooLittleReceived(); + + address tokenIn = abi.decode(params.data, (address)); + + _depositToBentoBox(tokenIn, params.pool, amountIn); + + IPool(params.pool).swap(params.data); + } + /// @notice Swaps token A to token B indirectly by using multiple hops. It's the same as `exactInput` except /// it takes raw ERC-20 tokens from the users and deposits them into `bento`. /// @param params This includes the addresses of the tokens, pools, amount of token A to swap, @@ -117,6 +171,27 @@ contract TridentRouter is ITridentRouter, SelfPermit, Multicall { if (amountOut < params.amountOutMinimum) revert TooLittleReceived(); } + /// @notice Swaps token B to token A indirectly by using multiple hops. It's the same as `exactOutput` except + /// it takes raw ERC-20 tokens from the users and deposits them into `bento`. + /// @param params This includes the addresses of the tokens, pools, amount of token B to swap, + /// maximum amount of token A for the swap and data required by the pools for the swaps. + /// @dev Ensure that the pools are trusted before calling this function. The pools can steal users' tokens. + function exactOutputWithNativeToken(ExactOutputParams calldata params) public payable returns (uint256 amountIn) { + amountIn = TridentRouterLibrary.getAmountsIn(params.path, params.tokenOut, params.amountOut)[0]; + + if (amountIn > params.amountInMaximum) revert TooLittleReceived(); + + address tokenIn = abi.decode(params.path[0].data, (address)); + + _depositToBentoBox(tokenIn, params.path[0].pool, amountIn); + + uint256 n = params.path.length; + + for (uint256 i = 0; i < n; i = _increment(i)) { + IPool(params.path[i].pool).swap(params.path[i].data); + } + } + /// @notice Swaps multiple input tokens to multiple output tokens using multiple paths, in different percentages. /// For example, you can swap 50 DAI + 100 USDC into 60% ETH and 40% BTC. /// @param params This includes everything needed for the swap. Look at the `ComplexPathParams` struct for more details. diff --git a/contracts/interfaces/ITridentRouter.sol b/contracts/interfaces/ITridentRouter.sol index 60d58474..b78f32d4 100644 --- a/contracts/interfaces/ITridentRouter.sol +++ b/contracts/interfaces/ITridentRouter.sol @@ -24,6 +24,21 @@ interface ITridentRouter { Path[] path; } + struct ExactOutputSingleParams { + uint256 amountOut; + uint256 amountInMaximum; + address pool; + address tokenOut; + bytes data; + } + + struct ExactOutputParams { + address tokenOut; + uint256 amountOut; + uint256 amountInMaximum; + Path[] path; + } + struct TokenInput { address token; bool native; diff --git a/contracts/libraries/TridentRouterLibrary.sol b/contracts/libraries/TridentRouterLibrary.sol new file mode 100644 index 00000000..4f4e4187 --- /dev/null +++ b/contracts/libraries/TridentRouterLibrary.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity >=0.8.0; + +import "../interfaces/ITridentRouter.sol"; +import "../interfaces/IPool.sol"; + +library TridentRouterLibrary { + /// @notice Get Amount In from the pool + /// @param pool Pool address + /// @param amountOut Amount out required + /// @param tokenOut Token out required + function getAmountIn( + address pool, + uint256 amountOut, + address tokenOut + ) internal view returns (uint256 amountIn) { + bytes memory data = abi.encode(tokenOut, amountOut); + amountIn = IPool(pool).getAmountIn(data); + } + + /// @notice Get Amount In multihop + /// @param path Path for the hops (pool addresses) + /// @param tokenOut Token out required + /// @param amountOut Amount out required + function getAmountsIn( + ITridentRouter.Path[] memory path, + address tokenOut, + uint256 amountOut + ) internal view returns (uint256[] memory amounts) { + amounts = new uint256[](path.length + 1); + amounts[amounts.length - 1] = amountOut; + + for (uint256 i = path.length; i > 0; i--) { + uint256 amountIn = getAmountIn(path[i - 1].pool, amounts[i], tokenOut); + amounts[i - 1] = amountIn; + if (i > 1) { + (tokenOut) = abi.decode(path[i - 1].data, (address)); + } + } + } +}