Untitled

 avatar
unknown
plain_text
5 months ago
23 kB
2
Indexable
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.10;

import '../interfaces/IUniswapV2Pair.sol';
import '../interfaces/IUniswapV3Pool.sol';
import '../interfaces/ITridentCLPool.sol';
import '../interfaces/IBentoBoxMinimal.sol';
import '../interfaces/IPool.sol';
import '../interfaces/IWETH.sol';
import '../interfaces/ICurve.sol';
import './InputStream.sol';
import './Utils.sol';
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import "@openzeppelin/contracts/access/Ownable.sol";

address constant IMPOSSIBLE_POOL_ADDRESS = 0x0000000000000000000000000000000000000001;
address constant INTERNAL_INPUT_SOURCE = 0x0000000000000000000000000000000000000000;

uint8 constant LOCKED = 2;
uint8 constant NOT_LOCKED = 1;
uint8 constant PAUSED = 2;
uint8 constant NOT_PAUSED = 1;

/// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
uint160 constant MIN_SQRT_RATIO = 4295128739;
/// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
uint160 constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;

/// @title A route processor for the Sushi Aggregator
/// @author Ilya Lyalin
contract RouteProcessor5 is Ownable {
  using SafeERC20 for IERC20;
  using Utils for IERC20;
  using Utils for address;
  using SafeERC20 for IERC20Permit;
  using InputStream for uint256;

  event Route(
    address indexed from, 
    address to, 
    address indexed tokenIn, 
    address indexed tokenOut, 
    uint256 amountIn, 
    uint256 amountOutMin,
    uint256 amountOut
  );

  error MinimalOutputBalanceViolation(uint256 amountOut);

  IBentoBoxMinimal public immutable bentoBox;
  mapping (address => bool) public priviledgedUsers;
  address private lastCalledPool;

  uint8 private unlocked = NOT_LOCKED;
  uint8 private paused = NOT_PAUSED;
  modifier lock() {
      require(unlocked == NOT_LOCKED, 'RouteProcessor is locked');
      require(paused == NOT_PAUSED, 'RouteProcessor is paused');
      unlocked = LOCKED;
      _;
      unlocked = NOT_LOCKED;
  }

  modifier onlyOwnerOrPriviledgedUser() {
    require(msg.sender == owner() || priviledgedUsers[msg.sender], "RP: caller is not the owner or a privileged user");
    _;
  }

  constructor(address _bentoBox, address[] memory priviledgedUserList) {
    bentoBox = IBentoBoxMinimal(_bentoBox);
    lastCalledPool = IMPOSSIBLE_POOL_ADDRESS;

    for (uint256 i = 0; i < priviledgedUserList.length; i++) {
      priviledgedUsers[priviledgedUserList[i]] = true;
    }
  }

  function setPriviledge(address user, bool priviledge) external onlyOwner {
    priviledgedUsers[user] = priviledge;
  }

  function pause() external onlyOwnerOrPriviledgedUser {
    paused = PAUSED;
  }

  function resume() external onlyOwnerOrPriviledgedUser {
    paused = NOT_PAUSED;
  }

  /// @notice For native unwrapping
  receive() external payable {}

  /// @notice Processes the route generated off-chain. Has a lock
  /// @param tokenIn Address of the input token
  /// @param amountIn Amount of the input token
  /// @param tokenOut Address of the output token
  /// @param amountOutMin Minimum amount of the output token
  /// @param to Where to transfer output tokens
  /// @param route Route to process
  /// @return amountOut Actual amount of the output token
  function processRoute(
    address tokenIn,
    uint256 amountIn,
    address tokenOut,
    uint256 amountOutMin,
    address to,
    bytes memory route
  ) external payable lock returns (uint256 amountOut) {
    return processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route);
  }

  /// @notice Transfers some value to <transferValueTo> and then processes the route
  /// @param transferValueTo Address where the value should be transferred
  /// @param amountValueTransfer How much value to transfer
  /// @param tokenIn Address of the input token
  /// @param amountIn Amount of the input token
  /// @param tokenOut Address of the output token
  /// @param amountOutMin Minimum amount of the output token
  /// @return amountOut Actual amount of the output token
  function transferValueAndprocessRoute(
    address transferValueTo,
    uint256 amountValueTransfer,
    address tokenIn,
    uint256 amountIn,
    address tokenOut,
    uint256 amountOutMin,
    address to,
    bytes memory route
  ) external payable lock returns (uint256 amountOut) {
    transferValueTo.transferNative(amountValueTransfer);
    return processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route);
  }

  /// @notice Transfers some value of input tokens to <transferValueTo> and then processes the route
  /// @param transferValueTo Address where the value should be transferred
  /// @param amountValueTransfer How much value to transfer
  /// @param tokenIn Address of the input token
  /// @param amountIn Amount of the input token
  /// @param tokenOut Address of the output token
  /// @param amountOutMin Minimum amount of the output token
  /// @return amountOut Actual amount of the output token
  function processRouteWithTransferValueInput(
    address payable transferValueTo,
    uint256 amountValueTransfer,
    address tokenIn,
    uint256 amountIn,
    address tokenOut,
    uint256 amountOutMin,
    address to,
    bytes memory route
  ) external payable lock returns (uint256 amountOut) {
    tokenIn.transferAnyFromSender(transferValueTo, amountValueTransfer);
    return processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route);
  }
  
  /// @notice processes the route and sends <amountValueTransfer> amount of output token to <transferValueTo>
  /// @param transferValueTo Address where the value should be transferred
  /// @param amountValueTransfer How much value to transfer
  /// @param tokenIn Address of the input token
  /// @param amountIn Amount of the input token
  /// @param tokenOut Address of the output token
  /// @param amountOutMin Minimum amount of the output token
  /// @return amountOut Actual amount of the output token
  function processRouteWithTransferValueOutput(
    address payable transferValueTo,
    uint256 amountValueTransfer,
    address tokenIn,
    uint256 amountIn,
    address tokenOut,
    uint256 amountOutMin,
    address to,
    bytes memory route
  ) external payable lock returns (uint256 amountOut) {
    amountOut = processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, address(this), route);
    tokenOut.transferAny(transferValueTo, amountValueTransfer);
    tokenOut.transferAny(to, amountOut - amountValueTransfer);
  }

  /// @notice Processes the route generated off-chain
  /// @param tokenIn Address of the input token
  /// @param amountIn Amount of the input token
  /// @param tokenOut Address of the output token
  /// @param amountOutMin Minimum amount of the output token
  /// @return amountOut Actual amount of the output token
  function processRouteInternal(
    address tokenIn,
    uint256 amountIn,
    address tokenOut,
    uint256 amountOutMin,
    address to,
    bytes memory route
  ) private returns (uint256 amountOut) {
    uint256 balanceInInitial = tokenIn.anyBalanceOf(msg.sender);
    uint256 balanceOutInitial = tokenOut.anyBalanceOf(to);

    uint256 realAmountIn = amountIn;
    {
      uint256 step = 0;
      uint256 stream = InputStream.createStream(route);
      while (stream.isNotEmpty()) {
        uint8 commandCode = stream.readUint8();
        if (commandCode == 1) {
          uint256 usedAmount = processMyERC20(stream); 
          if (step == 0) realAmountIn = usedAmount;
        } 
        else if (commandCode == 2) processUserERC20(stream, amountIn);
        else if (commandCode == 3) {
          uint256 usedAmount = processNative(stream); 
          if (step == 0) realAmountIn = usedAmount;
        } 
        else if (commandCode == 4) processOnePool(stream);
        else if (commandCode == 5) processInsideBento(stream);
        else if (commandCode == 6) applyPermit(tokenIn, stream);
        else revert('RouteProcessor: Unknown command code');
        ++step;
      }
    }

    uint256 balanceInFinal = tokenIn.anyBalanceOf(msg.sender);
    if (tokenIn != Utils.NATIVE_ADDRESS)
      require(balanceInFinal + amountIn + 10 >= balanceInInitial, 'RouteProcessor: Minimal input balance violation');
    
    uint256 balanceOutFinal = tokenOut.anyBalanceOf(to);
    if (balanceOutFinal < balanceOutInitial + amountOutMin)
      revert MinimalOutputBalanceViolation(balanceOutFinal - balanceOutInitial);

    amountOut = balanceOutFinal - balanceOutInitial;

    emit Route(msg.sender, to, tokenIn, tokenOut, realAmountIn, amountOutMin, amountOut);
  }

  /// @notice Applies ERC-2612 permit
  /// @param tokenIn permitted token
  /// @param stream Streamed program
  function applyPermit(address tokenIn, uint256 stream) private {
    uint256 value = stream.readUint();
    uint256 deadline = stream.readUint();
    uint8 v = stream.readUint8();
    bytes32 r = stream.readBytes32();
    bytes32 s = stream.readBytes32();
    if (IERC20(tokenIn).allowance(msg.sender, address(this)) < value) {
      IERC20Permit(tokenIn).safePermit(msg.sender, address(this), value, deadline, v, r, s);
    }
  }

  /// @notice Processes native coin: call swap for all pools that swap from native coin
  /// @param stream Streamed program
  function processNative(uint256 stream) private returns (uint256 amountTotal) {
    amountTotal = address(this).balance;
    distributeAndSwap(stream, address(this), Utils.NATIVE_ADDRESS, amountTotal);
  }

  /// @notice Processes ERC20 token from this contract balance:
  /// @notice Call swap for all pools that swap from this token
  /// @param stream Streamed program
  function processMyERC20(uint256 stream) private returns (uint256 amountTotal) {
    address token = stream.readAddress();
    amountTotal = IERC20(token).balanceOf(address(this));
    unchecked {
      if (amountTotal > 0) amountTotal -= 1;     // slot undrain protection
    }
    distributeAndSwap(stream, address(this), token, amountTotal);
  }
  
  /// @notice Processes ERC20 token from msg.sender balance:
  /// @notice Call swap for all pools that swap from this token
  /// @param stream Streamed program
  /// @param amountTotal Amount of tokens to take from msg.sender
  function processUserERC20(uint256 stream, uint256 amountTotal) private {
    address token = stream.readAddress();
    distributeAndSwap(stream, msg.sender, token, amountTotal);
  }

  /// @notice Processes ERC20 token for cases when the token has only one output pool
  /// @notice In this case liquidity is already at pool balance. This is an optimization
  /// @notice Call swap for all pools that swap from this token
  /// @param stream Streamed program
  function processOnePool(uint256 stream) private {
    address token = stream.readAddress();
    swap(stream, INTERNAL_INPUT_SOURCE, token, 0);
  }

  /// @notice Processes Bento tokens 
  /// @notice Call swap for all pools that swap from this token
  /// @param stream Streamed program
  function processInsideBento(uint256 stream) private {
    address token = stream.readAddress();
    uint256 amountTotal = bentoBox.balanceOf(token, address(this));
    unchecked {
      if (amountTotal > 0) amountTotal -= 1;     // slot undrain protection
    }
    distributeAndSwap(stream, address(this), token, amountTotal);
  }

  /// @notice Distributes amountTotal to several pools according to their shares and calls swap for each pool
  /// @param stream Streamed program
  /// @param from Where to take liquidity for swap
  /// @param tokenIn Input token
  /// @param amountTotal Total amount of tokenIn for swaps 
  function distributeAndSwap(uint256 stream, address from, address tokenIn, uint256 amountTotal) private {
    uint8 num = stream.readUint8();
    unchecked {
      for (uint256 i = 0; i < num; ++i) {
        uint16 share = stream.readUint16();
        uint256 amount = (amountTotal * share) / type(uint16).max /*65535*/;
        amountTotal -= amount;
        swap(stream, from, tokenIn, amount);
      }
    }
  }

  /// @notice Makes swap
  /// @param stream Streamed program
  /// @param from Where to take liquidity for swap
  /// @param tokenIn Input token
  /// @param amountIn Amount of tokenIn to take for swap
  function swap(uint256 stream, address from, address tokenIn, uint256 amountIn) private {
    uint8 poolType = stream.readUint8();
    if (poolType == 0) swapUniV2(stream, from, tokenIn, amountIn);
    else if (poolType == 1) swapUniV3(stream, from, tokenIn, amountIn);
    else if (poolType == 2) wrapNative(stream, from, tokenIn, amountIn);
    else if (poolType == 3) bentoBridge(stream, from, tokenIn, amountIn);
    else if (poolType == 4) swapTrident(stream, from, tokenIn, amountIn);
    else if (poolType == 5) swapCurve(stream, from, tokenIn, amountIn);
    else revert('RouteProcessor: Unknown pool type');
  }

  /// @notice Wraps/unwraps native token
  /// @param stream [direction & fake, recipient, wrapToken?]
  /// @param from Where to take liquidity for swap
  /// @param tokenIn Input token
  /// @param amountIn Amount of tokenIn to take for swap
  function wrapNative(uint256 stream, address from, address tokenIn, uint256 amountIn) private {
    uint8 directionAndFake = stream.readUint8();
    address to = stream.readAddress();

    if (directionAndFake & 1 == 1) {  // wrap native
      address wrapToken = stream.readAddress();
      if (directionAndFake & 2 == 0) IWETH(wrapToken).deposit{value: amountIn}();
      if (to != address(this)) IERC20(wrapToken).safeTransfer(to, amountIn);
    } else { // unwrap native
      if (directionAndFake & 2 == 0) {
        if (from == msg.sender) IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
        IWETH(tokenIn).withdraw(amountIn);
      }
      to.transferNative(amountIn);
    }
  }

  /// @notice Bridge/unbridge tokens to/from Bento
  /// @param stream [direction, recipient]
  /// @param from Where to take liquidity for swap
  /// @param tokenIn Input token
  /// @param amountIn Amount of tokenIn to take for swap
  function bentoBridge(uint256 stream, address from, address tokenIn, uint256 amountIn) private {
    uint8 direction = stream.readUint8();
    address to = stream.readAddress();

    if (direction > 0) {  // outside to Bento
      // deposit to arbitrary recipient is possible only from address(bentoBox)
      if (from == address(this)) IERC20(tokenIn).safeTransfer(address(bentoBox), amountIn);
      else if (from == msg.sender) IERC20(tokenIn).safeTransferFrom(msg.sender, address(bentoBox), amountIn);
      else {
        // tokens already are at address(bentoBox)
        amountIn = IERC20(tokenIn).balanceOf(address(bentoBox)) +
        bentoBox.strategyData(tokenIn).balance -
        bentoBox.totals(tokenIn).elastic;
      }
      bentoBox.deposit(tokenIn, address(bentoBox), to, amountIn, 0);
    } else { // Bento to outside
      if (from != INTERNAL_INPUT_SOURCE) {
        bentoBox.transfer(tokenIn, from, address(this), amountIn);
      } else amountIn = bentoBox.balanceOf(tokenIn, address(this));
      bentoBox.withdraw(tokenIn, address(this), to, 0, amountIn);
    }
  }

  /// @notice UniswapV2 pool swap
  /// @param stream [pool, direction, recipient, fee]
  /// @param from Where to take liquidity for swap
  /// @param tokenIn Input token
  /// @param amountIn Amount of tokenIn to take for swap
  function swapUniV2(uint256 stream, address from, address tokenIn, uint256 amountIn) private {
    address pool = stream.readAddress();
    uint8 direction = stream.readUint8();
    address to = stream.readAddress();
    uint24 fee = stream.readUint24();   // pool fee in 1/1_000_000

    if (from == address(this)) IERC20(tokenIn).safeTransfer(pool, amountIn);
    else if (from == msg.sender) IERC20(tokenIn).safeTransferFrom(msg.sender, pool, amountIn);

    (uint256 r0, uint256 r1, ) = IUniswapV2Pair(pool).getReserves();
    require(r0 > 0 && r1 > 0, 'Wrong pool reserves');
    (uint256 reserveIn, uint256 reserveOut) = direction == 1 ? (r0, r1) : (r1, r0);
    amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn;  // tokens already were transferred

    uint256 amountInWithFee = amountIn * (1_000_000 - fee);
    uint256 amountOut = (amountInWithFee * reserveOut) / (reserveIn * 1_000_000 + amountInWithFee);
    (uint256 amount0Out, uint256 amount1Out) = direction == 1 ? (uint256(0), amountOut) : (amountOut, uint256(0));
    IUniswapV2Pair(pool).swap(amount0Out, amount1Out, to, new bytes(0));
  }

  /// @notice Trident pool swap
  /// @param stream [pool, swapData]
  /// @param from Where to take liquidity for swap
  /// @param tokenIn Input token
  /// @param amountIn Amount of tokenIn to take for swap
  function swapTrident(uint256 stream, address from, address tokenIn, uint256 amountIn) private {
    address pool = stream.readAddress();
    bytes memory swapData = stream.readBytes();

    if (from != INTERNAL_INPUT_SOURCE) {
      bentoBox.transfer(tokenIn, from, pool, amountIn);
    }
    
    IPool(pool).swap(swapData);
  }

  /// @notice UniswapV3 pool swap
  /// @param stream [pool, direction, recipient]
  /// @param from Where to take liquidity for swap
  /// @param tokenIn Input token
  /// @param amountIn Amount of tokenIn to take for swap
  function swapUniV3(uint256 stream, address from, address tokenIn, uint256 amountIn) private {
    address pool = stream.readAddress();
    bool zeroForOne = stream.readUint8() > 0;
    address recipient = stream.readAddress();

    if (from == msg.sender) IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), uint256(amountIn));

    lastCalledPool = pool;
    IUniswapV3Pool(pool).swap(
      recipient,
      zeroForOne,
      int256(amountIn),
      zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,
      abi.encode(tokenIn)
    );
    require(lastCalledPool == IMPOSSIBLE_POOL_ADDRESS, 'RouteProcessor.swapUniV3: unexpected'); // Just to be sure
  }

  /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
  /// @dev In the implementation you must pay the pool tokens owed for the swap.
  /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
  /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
  /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
  /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
  /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
  /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
  /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
  function uniswapV3SwapCallback(
    int256 amount0Delta,
    int256 amount1Delta,
    bytes calldata data
  ) public {
    require(msg.sender == lastCalledPool, 'RouteProcessor.uniswapV3SwapCallback: call from unknown source');
    int256 amount = amount0Delta > 0 ? amount0Delta : amount1Delta;
    require(amount > 0, 'RouteProcessor.uniswapV3SwapCallback: not positive amount');
    
    lastCalledPool = IMPOSSIBLE_POOL_ADDRESS;
    (address tokenIn) = abi.decode(data, (address));
    IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount));
  }

  /// @notice Called to `msg.sender` after executing a swap via IAlgebraPool#swap.
  /// @dev In the implementation you must pay the pool tokens owed for the swap.
  /// The caller of this method _must_ be checked to be a AlgebraPool deployed by the canonical AlgebraFactory.
  /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
  /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
  /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
  /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
  /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
  /// @param data Any data passed through by the caller via the IAlgebraPoolActions#swap call
  function algebraSwapCallback(
    int256 amount0Delta,
    int256 amount1Delta,
    bytes calldata data
  ) external {
    uniswapV3SwapCallback(amount0Delta, amount1Delta, data);
  }

  /// @notice Called to `msg.sender` after executing a swap via PancakeV3Pool#swap.
  /// @dev In the implementation you must pay the pool tokens owed for the swap.
  /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
  /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
  /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
  /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
  /// @param data Any data passed through by the caller via the PancakeV3Pool#swap call
  function pancakeV3SwapCallback(
    int256 amount0Delta,
    int256 amount1Delta,
    bytes calldata data
  ) external {
    uniswapV3SwapCallback(amount0Delta, amount1Delta, data);
  }

  /// @notice Curve pool swap. Legacy pools that don't return amountOut and have native coins are not supported
  /// @param stream [pool, poolType, fromIndex, toIndex, recipient, output token]
  /// @param from Where to take liquidity for swap
  /// @param tokenIn Input token
  /// @param amountIn Amount of tokenIn to take for swap
  function swapCurve(uint256 stream, address from, address tokenIn, uint256 amountIn) private {
    address pool = stream.readAddress();
    uint8 poolType = stream.readUint8();
    int128 fromIndex = int8(stream.readUint8());
    int128 toIndex = int8(stream.readUint8());
    address to = stream.readAddress();
    address tokenOut = stream.readAddress();

    uint256 amountOut;
    if (tokenIn == Utils.NATIVE_ADDRESS) {
      amountOut = ICurve(pool).exchange{value: amountIn}(fromIndex, toIndex, amountIn, 0);
    } else {
      if (from == msg.sender) IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
      IERC20(tokenIn).approveSafe(pool, amountIn);
      if (poolType == 0) amountOut = ICurve(pool).exchange(fromIndex, toIndex, amountIn, 0);
      else {
        uint256 balanceBefore = tokenOut.anyBalanceOf(address(this));
        ICurveLegacy(pool).exchange(fromIndex, toIndex, amountIn, 0);
        uint256 balanceAfter = tokenOut.anyBalanceOf(address(this));
        amountOut = balanceAfter - balanceBefore;
      }
    }

    if (to != address(this)) tokenOut.transferAny(to, amountOut);
  }
}
Editor is loading...
Leave a Comment