// File: @uniswap/lib/contracts/libraries/TransferHelper.sol



// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity >=0.6.0;

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeApprove: approve failed'
        );
    }

    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeTransfer: transfer failed'
        );
    }

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::transferFrom: transferFrom failed'
        );
    }

    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
    }
}

// File: @uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
    /// @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
    ) external;
}

// File: @uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;

import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol';

/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);

    struct ExactInputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);

    struct ExactOutputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);

    struct ExactOutputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}

// File: src/contracts/EtherHolder.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

// Imports
import "../libraries/UniswapV3Trading.sol";

/**
 * An abstract contract which enables receiving Ethers.
 */
abstract contract EtherHolder {
  // Events
  event Deposit(address indexed from, uint256 value);

  /**
   * Handles incoming Ethers by accepting those and trigerring Deposit() event.
   */
  receive() external payable {
    emit Deposit(msg.sender, msg.value);
  }
}

// File: src/contracts/Ownable.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

/**
 * Abstract contract implementing ownership handling and verification.
 *
 * The owner address gets stored in the contract's _owner slot and is
 * used to verify both implicit (direct method calls) and explicit
 * (passed in as an argument from arbitrary address, on owner's
 * behalf) signatures.
 *
 * The proposed owner address gets stored in _nextOwner slot and is only
 * promoted to the _owner slot upon receiving a signed approval.
 *
 * The reusable modifiers are utilized to validate that certain functions get
 * invoked by the current owner of the contract.
 */
abstract contract Ownable {
  /**
   * A struct holding the ECDSA explicit signature and the nonce used to
   * produce it.  Nonce is strictly increasing to prevent replay attacks.
   *
   * The recovery id "v" should be 27 or 28.
   */
  struct Signature {
    uint8 v;
    bytes32 r;
    bytes32 s;
    uint96 nonce;
  }

  // Current owner of the contract
  address public _owner;
  // Last explicit signature's nonce
  uint96 public _lastNonce;

  // Proposed owner of the contract
  address public _nextOwner;

  /**
   * Validates that the caller of the method matches the requiredOwner parameter.
   *
   * @param requiredOwner the required address
   */
  modifier onlyBy(address requiredOwner) {
    require(msg.sender == requiredOwner, "Only owner can do that");
    _;
  }

  /**
   * Validates that the provided signature was signed by an address matching requiredOwner parameter.
   *
   * @param requiredOwner the required address for the explicit signature
   * @param argsHash an opaque digest of whatever parameters are authorized by the explicit signature
   * @param signature an explicit ECDSA signature over argHash
   */
  modifier onlySignedBy(address requiredOwner, bytes32 argsHash, Signature calldata signature) {
    // Pack the hash of the arguments along with the nonce and contract address to prevent replay attacks.
    bytes32 hash = keccak256(abi.encodePacked(address(this), argsHash, signature.nonce));
    // Check the explicit ECDSA signature by recovering the signer address
    address signer = ecrecover(hash, signature.v, signature.r, signature.s);

    // Make sure the nonce is strictly greater to avoid re-use
    require(signature.nonce > _lastNonce, "The nonce is too old");
    // Make sure the signer matches the one requested
    require(signer == requiredOwner, "Only owner can do that");

    // Update the nonce
    _lastNonce = signature.nonce;

    _;
  }

  /**
   * Updates the proposed owner to a new address. To be called directly by the Owner.
   *
   * Requires the transaction to be signed by current owner.
   *
   * @param nextOwner the proposed address of the new owner
   */
  function setNextOwner(address nextOwner) public onlyBy(_owner) {
    _nextOwner = nextOwner;
  }

  /**
   * Updates the proposed owner to a new address. To be called on Owner's behalf.
   *
   * Requires the signature from the current owner to be passed.
   *
   * @param nextOwner the proposed address of the new owner
   * @param signature an explicit ECDSA signature of the authorization with the Owner's public key
   */
  function setNextOwner(address nextOwner, Signature calldata signature) public onlySignedBy(_owner, keccak256(abi.encodePacked(nextOwner)), signature) {
    _nextOwner = nextOwner;
  }

  /**
   * Accepts the new owner. To be called by the proposed Owner directly.
   */
  function acceptNextOwner() public onlyBy(_nextOwner) {
    _owner = _nextOwner;
  }

  /**
   * Accepts the new owner. To be called on the proposed Owner's behalf.
   *
   * @param signature an explicit ECDSA signature of the authorization with the Owner's public key
   */
  function acceptNextOwner(Signature calldata signature) public onlySignedBy(_nextOwner, keccak256(abi.encodePacked()), signature) {
    _owner = _nextOwner;
  }
}

// File: src/contracts/Trading.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

// Imports
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';
import "../libraries/UniswapV3Trading.sol";

/**
 * This is an abstract contract handling interaction with different DEXs ("brokers").
 *
 * It primary purpose is to properly route trading request to the implementation,
 * receive the result and log corresponding events.
 *
 * It also takes care of gathering trade fees: these are forced to never exceed 3%,
 * but can be as low as 0 (depending on amountFee) parameter.
 */
abstract contract Trading {
  // Constants
  uint8 constant internal UNISWAP_V3_BROKER = 0x1;
  uint16 constant internal MAX_FEE_BASE_1000 = 30;

  /**
   * An event emitted on a successful trade. All values are actually traded amounts.
   *
   * @param tradeId a unique identifier of the trade for SlickSwap internal bookkeeping
   * @param amountIn actually spent source token amount
   * @param amountOut actually obtained destination token amount
   * @param amountFee actually spent fee
   */
  event Trade(uint256 indexed tradeId, uint256 amountIn, uint256 amountOut, uint256 amountFee);

  /**
   * An event emitted on a failed trade. Various reasons include, but are not limited to:
   *  - insufficient balances
   *  - low liquidity
   *  - slippage overshoot (frontrunning)
   *
   * @param tradeId a unique identifier of the trade for SlickSwap internal bookkeeping
   */
  event TradeFailed(uint256 indexed tradeId);

  /**
   * Perform a trade.
   *
   * @param tradeId a unique identifier of the trade for SlickSwap internal bookkeeping
   * @param broker the type of the broker (the only supported value is 1, meaning Uniswap V3)
   * @param path token exchange path, in the format of a specific broker (see UniswapV3Trading for example)
   * @param flags a bitmask representing trade settings of a specific broker (see UniswapV3Trading for example)
   * @param amountIn amount of the "source" token to trade
   * @param amountOut amount of the "destination" token to trade
   * @param amountFee amount of the "destination" taken by SlickSwap as a fee. Enforced to be less than 3%.
   */
  function _trade(uint256 tradeId, uint8 broker, bytes memory path, uint8 flags, uint256 amountIn, uint256 amountOut, uint256 amountFee, address feeRecipient) internal returns (bool success) {
    // Invoke the implementation dispatch
    (bool tradeSuccess, uint256 actualIn, uint256 actualOut, uint256 actualFee) = _performTrade(broker, path, flags, amountIn, amountOut, amountFee, feeRecipient);

    // Emit events
    if (tradeSuccess) {
      emit Trade(tradeId, actualIn, actualOut, actualFee);
    } else {
      emit TradeFailed(tradeId);
    }

    return tradeSuccess;
  }

  /**
   * The actual trading logic implementation.
   */
  function _performTrade(uint8 broker, bytes memory path, uint8 flags, uint256 amountIn, uint256 amountOut, uint256 amountFee, address feeRecipient) internal returns (bool success, uint256 actualIn, uint256 actualOut, uint256 actualFee) {
    // Uniswap V3
    if (broker == UNISWAP_V3_BROKER) {
      address tokenIn;
      address tokenOut;
      bool takeFeeInEther;

      // Perform the trade
      (success, actualIn, actualOut, tokenIn, tokenOut, takeFeeInEther) = UniswapV3Trading.performTrade(path, flags, amountIn, amountOut);

      if (!success) {
        return (false, 0, 0, 0);
      }

      // Important: limit fee to 3% of the actual output amount
      uint256 maximumFee = actualOut * MAX_FEE_BASE_1000 / 1000;
      actualFee = amountFee > maximumFee ? maximumFee : amountFee;

      // Collect the fee
      if (actualFee > 0) {
        if (takeFeeInEther) {
          (bool transferSuccess,) = feeRecipient.call{ value: actualFee }("");
          require(transferSuccess, "Ether fee capture failed");
        } else {
          TransferHelper.safeTransfer(tokenOut, feeRecipient, actualFee);
        }
      }
    } else {
      // Revert on unknown broker
      require(false, "Unknown broker id");
    }
  }
}

// File: src/contracts/Upgradable.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

/**
 * This is an abstract contract implementing updates to the EIP-1967
 * implementation slot, as used by proxy.
 */
abstract contract Upgradable {
  // EIP-1967 defines implementation slot as uint256(keccak256('eip1967.proxy.implementation')) - 1
  bytes32 constant EIP_1967_SLOT = hex'360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc';

  // Storage - a set of logic contracts already migrated to, used to prevent duplicate updates.
  mapping(address => bool) migratedTo;

  /**
   * A modifier that enforces that the method it's attached to gets invoked just
   * once during the specific logic contract tenure. It is used to enforce that
   * the migrate() method that is run right after the upgrade takes place cannot
   * be run anymore by the external caller.
   */
  modifier onlyOnce() {
    address implementationAddress;

    assembly {
      implementationAddress := sload(EIP_1967_SLOT)
    }

    require(!migratedTo[implementationAddress], "This should only be called once");
    migratedTo[implementationAddress] = true;

    _;
  }

  /**
   * Virtual logic migration method - intended to be called right after the
   * wallet is upgraded to a new logic version.
   *
   * @param previousImplementation the logic contract address before the upgrade
   * @return success a flag which is true if the migration was succesful
   */
  function migrate(address previousImplementation) virtual external returns (bool);

  /**
   * Upgrade implementation proper. Overwrites the implementation slot and runs migrate().
   *
   * @param newImplementation new logic contract address
   */
  function _upgrade(address newImplementation) internal {
    address previousImplementation;

    // Update the EIP-1967 implementation slot
    assembly {
      previousImplementation := sload(EIP_1967_SLOT)
      sstore(EIP_1967_SLOT, newImplementation)
    }

    // Run the migrate() method via chained DELEGATECALL to self
    (bool success, bytes memory result) = newImplementation.delegatecall(abi.encodeCall(Upgradable.migrate, (previousImplementation)));
    require(success, "migrate() call reverted");
    require(abi.decode(result, (bool)), "migrate() did not return true");
  }
}

// File: src/contracts/Withdrawable.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

// Imports
import "../interfaces/IERC20.sol";
import "@uniswap/lib/contracts/libraries/TransferHelper.sol";

/**
 * This is an abstract contract implementing withdraw functions allowing
 * to take tokens or ethers out of the contract.
 */
abstract contract Withdrawable {
  /**
   * An event emitted on a successful withdrawal.
   *
   * @param to withdrawal destination address
   * @param token contract address of the token being withdrawn (address(0) for Ether)
   * @param amount withdrawal amount
   * @param fee withdrawal fee paid to SlickSwap
   */
  event Withdraw(address indexed to, address token, uint256 amount, uint256 fee);

  /**
   * An event emitted on a failed withdrawal.
   *
   * @param to withdrawal destination address
   * @param token contract address of the token being withdrawn (zero for Ether)
   * @param amount withdrawal amount
   * @param fee withdrawal fee paid to SlickSwap
   */
  event WithdrawFailed(address indexed to, address token, uint256 amount, uint256 fee);

  /**
   * Attempts to withdraw tokens (or Ether if token is address(0)) in the given amount, transferring
   * the fee out to the fee recipient.
   *
   * @param to withdrawal destination address
   * @param token contract address of the token being withdrawn (address(0) for Ether)
   * @param amount withdrawal amount
   * @param fee withdrawal fee paid to SlickSwap
   * @param feeRecipient address to send the fee to
   *
   * @return a flag that is true on a successful withdrawal
   */
  function _withdraw(address to, address token, uint256 amount, uint256 fee, address feeRecipient) internal returns (bool) {
    if (token == address(0)) {
      return _withdrawEther(to, amount, fee, feeRecipient);
    } else {
      return _withdrawToken(to, token, amount, fee, feeRecipient);
    }
  }

  /**
   * Attempts to withdraw tokens (and fee, if nonzero) to the address provided
   * using IERC-20 transfer() function.
   *
   * @param to withdrawal destination address
   * @param token contract address of the token being withdrawn (address(0) for Ether)
   * @param amount withdrawal amount
   * @param fee withdrawal fee paid to SlickSwap
   * @param feeRecipient address to send the fee to
   *
   * @return a flag that is true on a successful withdrawal
   */
  function _withdrawToken(address to, address token, uint256 amount, uint256 fee, address feeRecipient) internal returns (bool) {
    // invoke the transfer() function on the IERC-20 contract
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, amount));

    // Special handling around noncompliant tokens (namely Tether)
    if (!success || (data.length > 0 && !abi.decode(data, (bool)))) {
      // log the event about failed withdrawal – maybe the user hasn't got enough tokens?
      emit WithdrawFailed(to, token, amount, fee);
      return false;
    }

    // Take a fee if it is specified
    if (fee > 0) {
      TransferHelper.safeTransfer(token, feeRecipient, fee);
    }

    // Emit successful withdrawal event
    emit Withdraw(to, token, amount, fee);

    return true;
  }

  /**
   * Attempts to withdraw ethers (and fee, if nonzero) to the address provided.
   *
   * @param to withdrawal destination address
   * @param amount withdrawal amount
   * @param fee withdrawal fee paid to SlickSwap
   * @param feeRecipient address to send the fee to
   *
   * @return a flag that is true on a successful withdrawal
   */
  function _withdrawEther(address to, uint256 amount, uint256 fee, address feeRecipient) internal returns (bool) {
    // send Ethers to the address
    (bool success,) = to.call{ value: amount }("");

    if (!success) {
      // log the event about failed withdrawal – maybe the user hasn't got enough Ether?
      emit WithdrawFailed(to, address(0), amount, fee);
      return false;
    }

    // Take a fee if it is specified
    if (fee > 0) {
      (success,) = feeRecipient.call{ value: fee }("");
      require(success, "Unable to capture withdrawal fee");
    }

    // Emit successful withdrawal event
    emit Withdraw(to, address(0), amount, fee);

    return true;
  }
}

// File: src/DummyLogic.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract DummyLogic {
  address ownerAddr;

  function initialize(address owner) external {
    ownerAddr = owner;
  }

  function readOwnerAddrViaDelegateCall() external view returns (address) {
    return ownerAddr;
  }
}

// File: src/interfaces/IERC20.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

/**
 * ERC-20 contract interface. See the ERC for method definitions and invariants.
 */
interface IERC20 {
  function totalSupply() external view returns (uint);
  function balanceOf(address account) external view returns (uint);
  function transfer(address recipient, uint amount) external returns (bool);
  function allowance(address owner, address spender) external view returns (uint);
  function approve(address spender, uint amount) external returns (bool);
  function transferFrom(address sender, address recipient, uint amount) external returns (bool);

  event Transfer(address indexed from, address indexed to, uint value);
  event Approval(address indexed owner, address indexed spender, uint value);
}

// File: src/interfaces/ISlickSwapRegistryV1.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

/**
 * This interface defines accessors to be present on the Registry contract.
 */
interface ISlickSwapRegistryV1 {
  /**
   * Returns the address that should receive fees from trading operations.
   */
  function getTradingFeeRecipient() external view returns (address);

  /**
   * Returns the address that should receive fees from withdrawal operations.
   */
  function getWithdrawalFeeRecipient() external view returns (address);

  /**
   * Returns the address of the most up-to-date SlickSwap implementation contract.
   */
  function getNextImplementation() external view returns (address);

  /**
   * Reverts unless the provided address is allowed to trade.
   */
  function verifyTrader(address sender) external;
}

// File: src/interfaces/IWETH9.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

import "./IERC20.sol";

/**
 * Wrapped Ether (WETH) minting/burning interface.
 */
interface IWETH9 is IERC20 {
  function deposit() external payable;
  function withdraw(uint256) external;
}

// File: src/libraries/UniswapV3Trading.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

// Imports
import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';
import '../interfaces/IWETH9.sol';
import '../interfaces/IERC20.sol';

/**
 * This library provides routines for handling trading via Uniswap V3 exchange.
 */
library UniswapV3Trading {
  // Ethereum mainnet address of the UniswapV3Router
  address constant internal UNISWAP_V3_ROUTER_ADDRESS = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
  // Ethereum mainnet address of the Wrapped Ether
  address constant internal WETH9_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
  // Flags bit mask bit 0: equals 1 if first token in path is Ether (WETH wrapping/unwrapping is necessary).
  uint8 constant internal FLAGS_FIRST_TOKEN_IS_ETHER = (1 << 0);
  // Flags bit mask bit 1: equals 1 if the last token in path is Ether (WETH wrapping/unwrapping is necessary).
  uint8 constant internal FLAGS_LAST_TOKEN_IS_ETHER = (1 << 1);
  // Flags bit mask bit 2: equals 1 for "exact in" swaps, 0 for "exact out" swaps.
  uint8 constant internal FLAGS_IS_EXACT_IN = (1 << 2);

  /**
   * A method which calls UniswapV3Router and (optionally) WETH9 to actually execute the trade.
   *
   * Paths are represented in UniswapV3 Periphery format - 20-byte token contract addresses interspersed with 3-byte fees.
   * For "exact in" swaps the paths are non-reversed (first token is "in" token and last token is "out" token) and reversed for "exact out" swaps.
   * If one swap side amount is fixed as "exact", the other side acts as an upper/lower bound to permitted slippage.
   *
   * @param path token exchange path in the aforementioned format
   * @param flags a 3-bit OR'ed bitmask of any subset of {FLAGS_FIRST_TOKEN_IS_ETHER, FLAGS_LAST_TOKEN_IS_ETHER, FLAGS_IS_EXACT_IN}
   * @param amountIn the "in" token amount (either exact or upper bound, depending on flags)
   * @param amountOut the "out" token amount (either exact or lower bound, depending on flags)
   *
   * @return success a flag indicating whether a swap was performed successfully
   * @return actualIn actually consumed amount of the "in" token
   * @return actualOut actually obtained amount of the "out" token
   * @return tokenIn contract address of the "in" token
   * @return tokenOut contract address of the "out" token
   * @return takeFeeInEther a flag indicating whether to take the fee in Ether
   */
  function performTrade(bytes memory path, uint8 flags, uint256 amountIn, uint256 amountOut) internal returns (bool success, uint256 actualIn, uint256 actualOut, address tokenIn, address tokenOut, bool takeFeeInEther) {
    takeFeeInEther = false;

    // "In" token is Ether - wrap it as WETH
    if ((flags & FLAGS_FIRST_TOKEN_IS_ETHER) == FLAGS_FIRST_TOKEN_IS_ETHER) {
      (success,) = WETH9_ADDRESS.call{ value: amountIn }(abi.encodeWithSelector(IWETH9.deposit.selector));

      if (!success) {
        return (false, 0, 0, address(0), address(0), false);
      }
    }

    // Dispatch between "exact in" and "exact out" swaps; note the path encoding difference.
    if ((flags & FLAGS_IS_EXACT_IN) == FLAGS_IS_EXACT_IN) {
      (success, actualOut, tokenIn, tokenOut) = UniswapV3Trading.performExactInTrade(path, amountIn, amountOut);
      actualIn = amountIn;
    } else {
      (success, actualIn, tokenIn, tokenOut) = UniswapV3Trading.performExactOutTrade(path, amountIn, amountOut);
      actualOut = amountOut;
    }

    // Handle failure
    if (!success) {
      return (false, 0, 0, address(0), address(0), false);
    }

    // Clear approvals if leftover is present
    if (actualIn < amountIn) {
      TransferHelper.safeApprove(tokenIn, UNISWAP_V3_ROUTER_ADDRESS, 0);

      // If there is leftover WETH, unwrap it to Ether
      if ((flags & FLAGS_FIRST_TOKEN_IS_ETHER) == FLAGS_FIRST_TOKEN_IS_ETHER) {
        IWETH9(WETH9_ADDRESS).withdraw(amountIn - actualIn);
      }
    }

    // If the "out" token is Ether, unwrap WETH.
    if ((flags & FLAGS_LAST_TOKEN_IS_ETHER) == FLAGS_LAST_TOKEN_IS_ETHER) {
      takeFeeInEther = true;
      IWETH9(WETH9_ADDRESS).withdraw(actualOut);
    }
  }

  /**
   * A helper to perform an "exact in" trade.
   *
   * @param path token exchange path in direct UniswapV3 format (first token is "in" token, last token is "out" token)
   * @param amountIn exact "in" amount to spend
   * @param amountOutMinimum lower bound on "out" amount (to limit slippage)
   *
   * @return success a flag indicating whether a swap was performed successfully
   * @return amount actually obtained amount of the "out" token
   * @return tokenIn contract address of the "in" token
   * @return tokenOut contract address of the "out" token
   */
  function performExactInTrade(bytes memory path, uint256 amountIn, uint256 amountOutMinimum) internal returns (bool success, uint256 amount, address tokenIn, address tokenOut) {
    // Extract "endpoints" from direct path
    // Right 96 bit shift is cheap way to get from big-endian uint256 to a 160 bit address at the same offset
    uint256 length = path.length - 20;

    assembly {
      tokenIn := shr(96, mload(add(path, 0x20)))
      tokenOut := shr(96, mload(add(add(path, 0x20), length)))
    }

    // Approve exact input spend
    TransferHelper.safeApprove(tokenIn, UNISWAP_V3_ROUTER_ADDRESS, amountIn);

    // Perform the swap using UniswapV3 SwapRouter
    ISwapRouter swapRouter = ISwapRouter(UNISWAP_V3_ROUTER_ADDRESS);

    ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({
      path: path,
      recipient: address(this),
      deadline: block.timestamp,
      amountIn: amountIn,
      amountOutMinimum: amountOutMinimum
    });

    // Catch the error, if any - do not revert.
    try swapRouter.exactInput(params) returns (uint256 value) {
      success = true;
      amount = value;
    } catch {
      success = false;
      amount = 0;
    }
  }

  /**
   * A helper to perform an "exact out" trade.
   *
   * @param path token exchange path in reversed UniswapV3 format (first token is "out" token, last token is "in" token)
   * @param amountInMaximum upper bound on "in" amount to spend (to limit slippage)
   * @param amountOut exact "out" amount to obtain
   *
   * @return success a flag indicating whether a swap was performed successfully
   * @return amount actually consumed amount of the "in" token
   * @return tokenIn contract address of the "in" token
   * @return tokenOut contract address of the "out" token
   */
  function performExactOutTrade(bytes memory path, uint256 amountInMaximum, uint256 amountOut) internal returns (bool success, uint256 amount, address tokenIn, address tokenOut) {
    // Extract "endpoints" from direct path
    // Right 96 bit shift is cheap way to get from big-endian uint256 to a 160 bit address at the same offset
    uint256 length = path.length - 20;

    assembly {
      tokenOut := shr(96, mload(add(path, 0x20)))
      tokenIn := shr(96, mload(add(add(path, 0x20), length)))
    }

    // Approve "input" spend upper bound
    TransferHelper.safeApprove(tokenIn, UNISWAP_V3_ROUTER_ADDRESS, amountInMaximum);

    // Perform the swap using UniswapV3 SwapRouter
    ISwapRouter swapRouter = ISwapRouter(UNISWAP_V3_ROUTER_ADDRESS);

    ISwapRouter.ExactOutputParams memory params = ISwapRouter.ExactOutputParams({
      path: path,
      recipient: address(this),
      deadline: block.timestamp,
      amountOut: amountOut,
      amountInMaximum: amountInMaximum
    });

    // Catch the error, if any - do not revert.
    try swapRouter.exactOutput(params) returns (uint256 value) {
      success = true;
      amount = value;
    } catch {
      success = false;
      amount = 0;
    }
  }
}

// File: src/SlickSwapProxyDeployer.sol



pragma solidity ^0.8.20;

// SPDX-License-Identifier: MIT

/**
 * SlickSwap Proxy Deployer
 *
 * This is a factory contract which deploys minimal (58 bytes in size) proxy contracts to
 * be used as individual SlickSwap contract wallets.
 *
 * All proxies have identical bytecode, but are initialized with the logic contract address stored in the
 * EIP-1967 implementation slot (see IMPL_SLOT) that makes the implementation discoverable by block explorers.
 *
 * Logic contract address in storage allows contract upgrades; see specific implementation for exact conditions.
 *
 * Proxy bytecode is almost identical to EIP-1167 Minimal Proxy Contract (again, see initCode() below for details). The DELEGATECALL
 * logic is fully transparent - calldata gets forwarded to the logic contract as-is, return data is unaltered as well.
 */
contract SlickSwapProxyDeployer {
  // EIP-1967 defines implementation slot as uint256(keccak256('eip1967.proxy.implementation')) - 1
  bytes32 constant IMPL_SLOT = hex'360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc';

  /**
   * Deploy a new proxy contract with a deterministic address (see computeProxyAddress())
   *
   * @param salt arbitrary 256-bits of entropy provided by the deployer
   * @param implAddr the logic contract address (contains code to delegatecall)
   * @param constructorCalldata the calldata for a "constructor" method to call right away
   */
  function deployProxy(bytes32 salt, address implAddr, bytes calldata constructorCalldata) external {
    // deterministic proxy address
    address proxyAddress;

    // proxy creation code instance
    bytes memory initCodeInst = initCode(implAddr);

    // directly invoking CREATE2 unfortunately still requires assembly in 0.8.20^
    assembly {
      // "bytes memory" is laid out in memory as:
      //   1) mload(initCodeInst)     - 32 byte length
      //   2) add(initCodeInst, 0x20) - a byte array
      proxyAddress := create2(0, add(initCodeInst, 0x20), mload(initCodeInst), salt)

      if iszero(extcodesize(proxyAddress)) {
          revert(0, 0)
      }
    }

    // call the constructor immediately
    (bool success,) = proxyAddress.call(constructorCalldata);
    require(success, "Proxy constructor reverted.");
  }

  /**
   * A way to predict the address of a proxy deployed with specific salt, owner & logic addresses.
   *
   * @param salt arbitrary 256-bits of entropy provided by the deployer
   * @param implAddr the logic contract address (contains code to delegatecall)
   *
   * @return the proxy deterministic address
   */
  function computeProxyAddress(bytes32 salt, address implAddr) external view returns (address) {
    // first compute the init code hash (it's different every time due to owner & impl addresses)
    bytes32 initCodeHash = keccak256(initCode(implAddr));

    // then we may predict the EIP-1014 CREATE2 address
    return address(uint160(uint256(keccak256(abi.encodePacked(hex'ff', this, salt, initCodeHash)))));
  }

  /**
   * Hand-written init code (total size 122 bytes):
   *
   *  === Creation time bytecode (64 bytes, parameterized by implAddr) ===
   *
   *  Offset         Bytecode            Opcode               Stack after               Comment
   *
   *  00..14           73 <20 bytes>     PUSH20 <implAddr>    implAddr
   *  15..35           7F <32 bytes>     PUSH32 <implSlot>    implSlot implAddr
   *  36               55                SSTORE                                         Store implementation address into EIP-1967 slot
   *  37..38           60 3A             PUSH1 <bcSz>         bcSz                      bcSz = 58 (0x3A)
   *  39               80                DUP1                 bcSz bcSz
   *  3A..3B           60 40             PUSH1 <bcOffs>       bcOffs bcSz bcSz          bc0ffs = 64 (0x40)
   *  3C               3D                RETURNDATASIZE       0 bcOffs bcSz bcSz        EIP-211 zero trick
   *  3D               39                CODECOPY             bcSz                      mem[0:bcSz] := code[bcOffs:bcOffs+bcSz]
   *  3E               3D                RETURNDATASIZE       0 bcSz
   *  3F               F3                RETURN                                         mem[0:bcSz] - returns proxy bytecode
   *
   *  === Deployed proxy bytecode (58 bytes, identical for all proxies) ===
   *
   *  Offset  (-0x40)  Bytecode          Opcode               Stack after               Comment
   *
   *  40      00       36                CALLDATASIZE         cds
   *  41      01       3D                RETURNDATASIZE       0 cds                     EIP-211 zero trick
   *  42      02       3D                RETURNDATASIZE       0 0 cds
   *  43      03       37                CALLDATACOPY                                   mem[0:cds] = calldata
   *  44      04       3D                RETURNDATASIZE       0
   *  45      05       3D                RETURNDATASIZE       0 0
   *  46      06       3D                RETURNDATASIZE       0 0 0
   *  47      07       36                CALLDATASIZE         cds 0 0 0
   *  48      08       3D                RETURNDATASIZE       0 cds 0 0 0
   *  49..69  09..29   7F <32 bytes>     PUSH32 <implSlot>    implSlot 0 cds 0 0 0
   *  6A      2A       54                SLOAD                implAddr 0 cds 0 0 0      Read implAddr from EIP-1967 slot
   *  6B      2B       5A                GAS                  gas implAddr 0 cds 0 0 0
   *  6C      2C       F4                DELEGATECALL         suc 0                     Delegatecall implAddr with full calldata and gas
   *  6D      2D       3D                RETURNDATASIZE       rds suc 0                 NB: EIP-211 zero trick doesn't work anymore
   *  6E      2E       82                DUP3                 0 rds suc 0
   *  6F      2F       80                DUP1                 0 0 rds suc 0
   *  70      30       3E                RETURNDATACOPY       suc 0                     mem[0:rds] := returndata[0:rds]
   *  71      31       90                SWAP1                0 suc
   *  72      32       3D                RETURNDATASIZE       rds 0 suc
   *  73      33       91                SWAP2                suc 0 rds
   *  74..75  34..35   60 38             PUSH1 :success:      :success: suc 0 rds
   *  76      36       57                JUMPI                0 rds                     jumps to :success: if suc != 0
   *  77      37       FD                REVERT
   *  78      38       5B                JUMPDEST                                       :success: label
   *  79      39       F3                RETURN
   *
   * NB: Proxy bytecode is identical to EIP-1167 Minimal Proxy Contract with two changes - instead of pushing implAddr on stack
   *     directly we load it from storage at EIP-1967 slot (offsets 09..2A), and adjusted jump destination (offset 35). These changes
   *     were introduced to support individual upgradeability and resulted in proxy size being inflated from 44 bytes to 58 bytes.
   *
   * @param implAddr the logic contract address (contains code to delegatecall)
   *
   * @return the proxy initcode
   */
  function initCode(address implAddr) internal pure returns (bytes memory) {
    return abi.encodePacked(
      // creation time bytecode
      hex'73', implAddr, hex'7f', IMPL_SLOT, hex'55603A8060403d393df3',
      // deployed proxy bytecode
      hex'363d3d373d3d3d363d7f', IMPL_SLOT, hex'545af43d82803e903d91603857fd5bf3'
    );
  }
}

// File: src/SlickSwapRegistryV1.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

// Imports
import './contracts/Ownable.sol';
import './interfaces/ISlickSwapRegistryV1.sol';

/**
 * SlickSwapRegistryV1 - this contract stores various system settings used by
 * the individual SlickSwap wallet contracts.
 *
 * This contract is immutable (unlike wallet contracts, which are individually upgradeable).
 * Newer SlickSwap versions may introduce additional Registry contracts for new settings.
 */
contract SlickSwapRegistryV1 is Ownable, ISlickSwapRegistryV1 {
  // Storage variables
  address public _tradingFeeRecipient;
  address public _withdrawalFeeRecipient;
  address public _nextImplementation;
  mapping(address => bool) public _traderAddresses;

  /**
   * Initializes the contract and assigns the owner to the signer
   * of the deployment transaction.
   */
  constructor() {
    _owner = msg.sender;
  }

  /**
   * Returns the address that should receive fees from trading operations.
   */
  function getTradingFeeRecipient() external view returns (address) {
    return _tradingFeeRecipient;
  }

  /**
   * Returns the address that should receive fees from trading operations.
   */
  function getWithdrawalFeeRecipient() external view returns (address) {
    return _withdrawalFeeRecipient;
  }

  /**
   * Returns the address of the most up-to-date SlickSwap implementation contract.
   */
  function getNextImplementation() external view returns (address) {
    return _nextImplementation;
  }

  /**
   * Validates that the address is indeed authorized to settle SlickSwap trades.
   *
   * @param sender the address calling the trade method
   */
  function verifyTrader(address sender) external view {
    require(_traderAddresses[sender], "The address is not allowed to trade");
  }

  /**
   * Updates the address that should receive fees from trading operations.
   * Called by Registry owner.
   *
   * @param tradingFeeRecipient new trading fee recipient.
   */
  function setTradingFeeRecipient(address tradingFeeRecipient) external onlyBy(_owner) {
    _tradingFeeRecipient = tradingFeeRecipient;
  }

  /**
   * Updates the address that should receive fees from withdrawal operations.
   * Called by Registry owner.
   *
   * @param withdrawalFeeRecipient new withdrawal fee recipient
   */
  function setWithdrawalFeeRecipient(address withdrawalFeeRecipient) external onlyBy(_owner) {
    _withdrawalFeeRecipient = withdrawalFeeRecipient;
  }

  /**
   * Updates the address of the most up-to-date SlickSwap implementation contract.
   * Called by Registry owner.
   *
   * @param nextImplementation new address of the SlickSwap logic contract.
   */
  function setNextImplementation(address nextImplementation) external onlyBy(_owner) {
    _nextImplementation = nextImplementation;
  }

  /**
   * Adds/removes the address of the account authorized to perform the trades.
   * Called by Registry owner.
   *
   * @param traderAddress address in consideration
   * @param enable adding represented by true, removing by false
   */
  function enableTradingFor(address traderAddress, bool enable) external onlyBy(_owner) {
    _traderAddresses[traderAddress] = enable;
  }
}

// File: src/SlickSwapV1.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

// Imports
import './contracts/Ownable.sol';
import './contracts/Trading.sol';
import './contracts/EtherHolder.sol';
import './contracts/Withdrawable.sol';
import './contracts/Upgradable.sol';
import './interfaces/ISlickSwapRegistryV1.sol';

/**
 * SlickSwap smart contract wallet, version 1
 *
 * This is a logic contract intended to be used with the minimal EIP-1967 proxy.
 * See {proxyDeployer} for provisioning details.
 *
 * This wallet can be used to:
 *  - deposit and hold Ether and ERC-20 tokens
 *  - withdraw Ether and ERC-20 tokens anytime by means of wallet Owner signature
 *  - perform Uniswap V3 trades by means of SlickSwap bot
 *
 * Owner keys are managed either by SlickSwap or externally (by a Web3 wallet,
 * for example). In the former case SlickSwap invokes withdrawal and logic
 * upgrade methods on Owner's behalf by explicitly passing in an ECDSA signature
 * as an argument; in the latter case the corresponding methods are invoked
 * directly from the Owner address.
 *
 * Wallet contracts are individually upgradeable - each instance can either explicitly
 * authorize the logic change by means of Owner public key, or opt out.
 */
contract SlickSwapV1 is Ownable, Trading, EtherHolder, Withdrawable, Upgradable {
  // Storage variables
  ISlickSwapRegistryV1 immutable _registry;

  /**
   * Logic contract constructor - creates the common bytecode.
   *
   * @param registry The address of the Registry contract
   */
  constructor(address registry) {
    _registry = ISlickSwapRegistryV1(registry);
  }

  /**
   * Wallet initializer - invoked on EIP-1967 proxy right after its deployment
   * to set up a new instance.
   *
   * @param owner address of the wallet Owner.
   */
  function initialize(address owner) external {
    require(_owner == address(0), "You can only initialize the contract once");
    _owner = owner;
  }

  /**
   * Logic migration method - intended to be called right after the wallet is
   * upgraded to a new logic version. The sole parameter is the address of the
   * previous logic contract. No-op as this is version 1.
   *
   * The very presence of this method is used as a safeguard against upgrades to
   * "dead" logic addresses, such as zero address, self-destructed contracts or
   * contracts with the wrong interface.
   */
  function migrate(address) override external onlyOnce returns (bool) {
    return true;
  }

  /**
   * Upgrade the contract to the latest logic version. To be called directly by the wallet Owner.
   */
  function upgrade() external onlyBy(_owner) {
    _upgrade(_registry.getNextImplementation());
  }

  /**
   * Upgrade the contract to the latest logic version. To be called on Owner's behalf.
   *
   * @param signature an ECDSA signature of the authorization with the Owner's public key
   */

  function upgrade(Signature calldata signature) external onlySignedBy(_owner, keccak256(abi.encodePacked()), signature) {
    _upgrade(_registry.getNextImplementation());
  }

  /**
   * Perform a trade. To be called by a Trader - an address listed in Registry
   * as eligible to settle trades.
   *
   * @param tradeId a unique identifier of the trade for SlickSwap internal bookkeeping
   * @param broker the type of the broker (the only supported value is 1, meaning Uniswap V3)
   * @param path token exchange path, in the format of a specific broker
   * @param flags a bitmask representing trade settings, see Trading contract for details
   * @param amountIn amount of the "source" token to trade
   * @param amountOut amount of the "destination" token to trade
   * @param amountFee amount of the "destination" taken by SlickSwap as a fee. Enforced to be less than 3%.
   *
   * @return success a flag indicating whether a swap was performed successfully
   */
  function trade(uint256 tradeId, uint8 broker, bytes memory path, uint8 flags, uint256 amountIn, uint256 amountOut, uint256 amountFee) external returns (bool success) {
    _registry.verifyTrader(msg.sender);
    return _trade(tradeId, broker, path, flags, amountIn, amountOut, amountFee, _registry.getTradingFeeRecipient());
  }

  /**
   * Token (including Ether) withdrawal. To be called directly by the wallet Owner.
   * No fee is taken.
   *
   * @param to withdrawal destination address
   * @param token the ERC-20 contract address, or zero for Ether
   * @param amount withdrawal amount
   *
   * @return bool withdrawal success flag
   */
  function withdraw(address to, address token, uint256 amount) external onlyBy(_owner) returns (bool) {
    return _withdraw(to, token, amount, 0, address(0));
  }

  /**
   * Token (including Ether) withdrawal. To be called on Owner's behalf.
   * A fixed fee is taken on top of the withdrawal amount.
   *
   * @param to withdrawal destination address
   * @param token the ERC-20 contract address, or zero for Ether
   * @param amount withdrawal amount
   * @param fee withdrawal fee
   * @param signature an ECDSA signature of the authorization with the Owner's public key
   *
   * @return bool withdrawal success flag
   */
  function withdraw(address to, address token, uint256 amount, uint256 fee, Signature calldata signature) external onlySignedBy(_owner, keccak256(abi.encodePacked(to, token, amount, fee)), signature) returns (bool) {
    return _withdraw(to, token, amount, fee, _registry.getWithdrawalFeeRecipient());
  }
}