// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;

import "../utils/Context.sol";
/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

// File: contracts/helpers/converter/ConverterRegistry.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.7.6;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

import { IConverterRegistry } from "./IConverterRegistry.sol";
import { ITokenConverter } from "./ITokenConverter.sol";

contract ConverterRegistry is Ownable, IConverterRegistry {
  /*************
   * Variables *
   *************/

  /// @dev Mapping from pool type to the address of converter.
  mapping(uint256 => address) private converters;

  /// @dev Mapping from tokenIn to tokenOut to routes.
  /// @dev See {ITokenConverter-convert} for the meaning.
  mapping(address => mapping(address => uint256[])) private routes;

  /*************************
   * Public View Functions *
   *************************/

  /// @inheritdoc IConverterRegistry
  function getRoutes(address src, address dst) external view override returns (uint256[] memory) {
    return routes[src][dst];
  }

  /// @inheritdoc IConverterRegistry
  function getTokenPair(uint256 _route) external view override returns (address, address) {
    uint256 _poolType = _route & 255;
    return ITokenConverter(converters[_poolType]).getTokenPair(_route);
  }

  /// @inheritdoc IConverterRegistry
  function getConverter(uint256 _poolType) external view override returns (address) {
    return converters[_poolType];
  }

  /*******************************
   * Public Restricted Functions *
   *******************************/

  /// @notice Register a converter or update the converter.
  ///
  /// @param _poolType The pool type to update.
  /// @param _newConverter The address of converter to update.
  function register(uint256 _poolType, address _newConverter) external onlyOwner {
    address _oldConverter = converters[_poolType];
    converters[_poolType] = _newConverter;

    emit UpdateConverter(_poolType, _oldConverter, _newConverter);
  }

  /// @notice Update the routes for converting from source token to destination token.
  ///
  /// @param _src The address of source token.
  /// @param _dst The address of destination token.
  /// @param _routes The list of route encodings.
  function updateRoute(
    address _src,
    address _dst,
    uint256[] memory _routes
  ) external onlyOwner {
    delete routes[_src][_dst];

    routes[_src][_dst] = _routes;

    emit UpdateRoute(_src, _dst, _routes);
  }

  /// @notice Withdraw dust assets from a converter contract.
  /// @param _converter The address of converter contract.
  /// @param _token The address of token to withdraw.
  /// @param _recipient The address of token receiver.
  function withdrawFund(
    address _converter,
    address _token,
    address _recipient
  ) external onlyOwner {
    ITokenConverter(_converter).withdrawFund(_token, _recipient);
  }
}

// File: contracts/helpers/converter/IConverterRegistry.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.7.0 || ^0.8.0;

interface IConverterRegistry {
  /**********
   * Events *
   **********/

  /// @notice Emitted when the converter route is updated.
  /// @param src The address of source token.
  /// @param dst The address of destination token.
  /// @param routes The list of route encodings.
  event UpdateRoute(address indexed src, address indexed dst, uint256[] routes);

  /// @notice Emitted when the token converter is updated for some pool type.
  /// @param poolType The pool type updated.
  /// @param oldConverter The address of previous converter.
  /// @param newConverter The address of current converter.
  event UpdateConverter(uint256 indexed poolType, address indexed oldConverter, address indexed newConverter);

  /*************************
   * Public View Functions *
   *************************/

  /// @notice Return the routes used to convert source token to destination token.
  /// @param src The address of source token.
  /// @param dst The address of destination token.
  /// @return routes The list of route encodings.
  function getRoutes(address src, address dst) external view returns (uint256[] memory routes);

  /// @notice Return the input token and output token for the route.
  /// @param route The encoding of the route.
  /// @return tokenIn The address of input token.
  /// @return tokenOut The address of output token.
  function getTokenPair(uint256 route) external view returns (address tokenIn, address tokenOut);

  /// @notice Return the address of converter for a specific pool type.
  /// @param poolType The type of converter.
  /// @return converter The address of converter.
  function getConverter(uint256 poolType) external view returns (address converter);
}

// File: contracts/helpers/converter/ITokenConverter.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.7.0 || ^0.8.0;

interface ITokenConverter {
  /*************************
   * Public View Functions *
   *************************/

  /// @notice The address of Converter Registry.
  function registry() external view returns (address);

  /// @notice Return the input token and output token for the route.
  /// @param route The encoding of the route.
  /// @return tokenIn The address of input token.
  /// @return tokenOut The address of output token.
  function getTokenPair(uint256 route) external view returns (address tokenIn, address tokenOut);

  /// @notice Query the output token amount according to the encoding.
  ///
  /// @dev See the comments in `convert` for the meaning of encoding.
  ///
  /// @param encoding The encoding used to convert.
  /// @param amountIn The amount of input token.
  /// @param amountOut The amount of output token received.
  function queryConvert(uint256 encoding, uint256 amountIn) external returns (uint256 amountOut);

  /****************************
   * Public Mutated Functions *
   ****************************/

  /// @notice Convert input token to output token according to the encoding.
  /// Assuming that the input token is already in the contract.
  ///
  /// @dev encoding for single route
  /// |   8 bits  | 2 bits |  246 bits  |
  /// | pool_type | action | customized |
  ///
  /// + pool_type = 0: UniswapV2, only action = 0
  ///   customized = |   160 bits   | 24 bits |     1 bit    | 1 bit | ... |
  ///                | pool address | fee_num | zero_for_one | twamm | ... |
  /// + pool_type = 1: UniswapV3, only action = 0
  ///   customized = |   160 bits   | 24 bits |     1 bit    | ... |
  ///                | pool address | fee_num | zero_for_one | ... |
  /// + pool_type = 2: BalancerV1, only action = 0
  ///   customized = |   160 bits   | 3 bits |  3 bits  |   3 bits  | ... |
  ///                | pool address | tokens | index in | index out | ... |
  /// + pool_type = 3: BalancerV2, only action = 0
  ///   customized = |   160 bits   | 3 bits |  3 bits  |   3 bits  | ... |
  ///                | pool address | tokens | index in | index out | ... |
  /// + pool_type = 4: CurvePlainPool or CurveFactoryPlainPool
  ///   customized = |   160 bits   | 3 bits |  3 bits  |   3 bits  |  1 bit  | ... |
  ///                | pool address | tokens | index in | index out | use_eth | ... |
  /// + pool_type = 5: CurveAPool
  ///   customized = |   160 bits   | 3 bits |  3 bits  |   3 bits  |     1 bits     | ... |
  ///                | pool address | tokens | index in | index out | use_underlying | ... |
  /// + pool_type = 6: CurveYPool
  ///   customized = |   160 bits   | 3 bits |  3 bits  |   3 bits  |     1 bits     | ... |
  ///                | pool address | tokens | index in | index out | use_underlying | ... |
  /// + pool_type = 7: CurveMetaPool or CurveFactoryMetaPool
  ///   customized = |   160 bits   | 3 bits |  3 bits  |   3 bits  | ... |
  ///                | pool address | tokens | index in | index out | ... |
  /// + pool_type = 8: CurveCryptoPool or CurveFactoryCryptoPool
  ///   customized = |   160 bits   | 3 bits |  3 bits  |   3 bits  |  1 bit  | ... |
  ///                | pool address | tokens | index in | index out | use_eth | ... |
  /// + pool_type = 9: ERC4626, no action 0
  ///   customized = |   160 bits   | ... |
  ///                | pool address | ... |
  /// + pool_type = 10: Lido, no action 0
  ///   customized = |   160 bits   | ... |
  ///                | pool address | ... |
  ///
  /// Note: tokens + 1 is the number of tokens of the pool
  ///
  /// + action = 0: swap
  /// + action = 1: add liquidity / wrap / stake
  /// + action = 2: remove liquidity / unwrap / unstake
  ///
  /// @param encoding The encoding used to convert.
  /// @param amountIn The amount of input token.
  /// @param recipient The address of token receiver.
  /// @return amountOut The amount of output token received.
  function convert(
    uint256 encoding,
    uint256 amountIn,
    address recipient
  ) external payable returns (uint256 amountOut);

  /// @notice Withdraw dust assets in this contract.
  /// @param token The address of token to withdraw.
  /// @param recipient The address of token receiver.
  function withdrawFund(address token, address recipient) external;
}