Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Exact Output for Trident Router #356

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions contracts/TridentRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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.
Expand Down
15 changes: 15 additions & 0 deletions contracts/interfaces/ITridentRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
42 changes: 42 additions & 0 deletions contracts/libraries/TridentRouterLibrary.sol
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
}