contract WETHGateway is IWETHGateway {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;
    using Address for address payable;

    address public wethAddress;
    ContractsRegister internal _contractsRegister;

    // Contract version
    uint256 public constant version = 1;

    event WithdrawETH(address indexed pool, address indexed to);

    /// @dev Checks that pool is registered and underlying token is WETH
    modifier wethPoolOnly(address pool) {
        // Could be optimised by adding internal list of pools
        require(_contractsRegister.isPool(pool), Errors.REGISTERED_POOLS_ONLY); // T:[WG-1]

        require(
            IPoolService(pool).underlyingToken() == wethAddress,
            Errors.WG_DESTINATION_IS_NOT_WETH_COMPATIBLE
        ); // T:[WG-2]
        _;
    }

    /// @dev Checks that credit manager is registered and underlying token is WETH
    modifier wethCreditManagerOnly(address creditManager) {
        // Could be optimised by adding internal list of creditManagers

        require(
            _contractsRegister.isCreditManager(creditManager),
            Errors.REGISTERED_CREDIT_ACCOUNT_MANAGERS_ONLY
        ); // T:[WG-3]

        require(
            ICreditManager(creditManager).underlyingToken() == wethAddress,
            Errors.WG_DESTINATION_IS_NOT_WETH_COMPATIBLE
        ); // T:[WG-4]

        _;
    }

    /// @dev Checks that credit manager is registered
    modifier creditManagerOnly(address creditManager) {
        // Could be optimised by adding internal list of creditManagers

        require(
            _contractsRegister.isCreditManager(creditManager),
            Errors.REGISTERED_CREDIT_ACCOUNT_MANAGERS_ONLY
        ); // T:[WG-3]

        _;
    }

    //
    // CONSTRUCTOR
    //

    /// @dev Constructor
    /// @param addressProvider Address Repository for upgradable contract model
    constructor(address addressProvider) {
        require(
            addressProvider != address(0),
            Errors.ZERO_ADDRESS_IS_NOT_ALLOWED
        );
        wethAddress = AddressProvider(addressProvider).getWethToken();
        _contractsRegister = ContractsRegister(
            AddressProvider(addressProvider).getContractsRegister()
        );
    }

    /// @dev convert ETH to WETH and add liqudity to pool
    /// @param pool Address of PoolService contract which where user wants to add liquidity. This pool should has WETH as underlying asset
    /// @param onBehalfOf The address that will receive the diesel tokens, same as msg.sender if the user  wants to receive them on his
    ///                   own wallet, or a different address if the beneficiary of diesel tokens is a different wallet
    /// @param referralCode Code used to register the integrator originating the operation, for potential rewards.
    /// 0 if the action is executed directly by the user, without any middle-man
    function addLiquidityETH(
        address pool,
        address onBehalfOf,
        uint16 referralCode
    )
        external
        payable
        override
        wethPoolOnly(pool) // T:[WG-1, 2]
    {
        IWETH(wethAddress).deposit{value: msg.value}(); // T:[WG-8]

        _checkAllowance(pool, msg.value); // T:[WG-8]
        IPoolService(pool).addLiquidity(msg.value, onBehalfOf, referralCode); // T:[WG-8]
    }

    /// @dev Removes liquidity from pool and convert WETH to ETH
    ///       - burns lp's diesel (LP) tokens
    ///       - returns underlying tokens to lp
    /// @param pool Address of PoolService contract which where user wants to withdraw liquidity. This pool should has WETH as underlying asset
    /// @param amount Amount of tokens to be transfer
    /// @param to Address to transfer liquidity
    function removeLiquidityETH(
        address pool,
        uint256 amount,
        address payable to
    )
        external
        override
        wethPoolOnly(pool) // T:[WG-1, 2]
    {
        IERC20(IPoolService(pool).dieselToken()).safeTransferFrom(
            msg.sender,
            address(this),
            amount
        ); // T: [WG-9]

        uint256 amountGet = IPoolService(pool).removeLiquidity(
            amount,
            address(this)
        ); // T: [WG-9]
        _unwrapWETH(to, amountGet); // T: [WG-9]

        emit WithdrawETH(pool, to);
    }

    /// @dev Opens credit account in ETH
    /// @param creditManager Address of credit Manager. Should used WETH as underlying asset
    /// @param onBehalfOf The address that we open credit account. Same as msg.sender if the user wants to open it for  his own wallet,
    ///                   or a different address if the beneficiary is a different wallet
    /// @param leverageFactor Multiplier to borrowers own funds
    /// @param referralCode Code used to register the integrator originating the operation, for potential rewards.
    ///                     0 if the action is executed directly by the user, without any middle-man
    function openCreditAccountETH(
        address creditManager,
        address payable onBehalfOf,
        uint256 leverageFactor,
        uint256 referralCode
    )
        external
        payable
        override
        wethCreditManagerOnly(creditManager) // T:[WG-3, 4]
    {
        _checkAllowance(creditManager, msg.value); // T:[WG-10]

        IWETH(wethAddress).deposit{value: msg.value}(); // T:[WG-10]
        ICreditManager(creditManager).openCreditAccount(
            msg.value,
            onBehalfOf,
            leverageFactor,
            referralCode
        ); // T:[WG-10]
    }

    /// @dev Repays credit account in ETH
    ///       - transfer borrowed money with interest + fee from borrower account to pool
    ///       - transfer all assets to "to" account
    /// @param creditManager Address of credit Manager. Should used WETH as underlying asset
    /// @param to Address to send credit account assets
    function repayCreditAccountETH(address creditManager, address to)
        external
        payable
        override
        wethCreditManagerOnly(creditManager) // T:[WG-3, 4]
    {
        uint256 amount = msg.value; // T: [WG-11]

        IWETH(wethAddress).deposit{value: amount}(); // T: [WG-11]
        _checkAllowance(creditManager, amount); // T: [WG-11]

        // This function is protected from reentrant attack
        uint256 repayAmount = ICreditManager(creditManager)
        .repayCreditAccountETH(msg.sender, to); // T: [WG-11, 13]

        if (amount > repayAmount) {
            IWETH(wethAddress).withdraw(amount - repayAmount);
            msg.sender.sendValue(amount.sub(repayAmount)); // T: [WG-12]
        } else {
            require(amount == repayAmount, Errors.WG_NOT_ENOUGH_FUNDS);
        }
    }

    function addCollateralETH(address creditManager, address onBehalfOf)
        external
        payable
        override
        creditManagerOnly(creditManager)
    {
        uint256 amount = msg.value; // T:[WG-14]

        IWETH(wethAddress).deposit{value: amount}(); // T:[WG-14]
        _checkAllowance(creditManager, amount); // T:[WG-14]
        ICreditManager(creditManager).addCollateral(
            onBehalfOf,
            wethAddress,
            amount
        ); // T:[WG-14]
    }

    /// @dev Converts WETH to ETH, it's used when credit manager sends tokens, and one of them is WETH
    function unwrapWETH(address to, uint256 amount)
        external
        override
        creditManagerOnly(msg.sender) // T:[WG-5]
    {
        _unwrapWETH(to, amount); // T: [WG-7]
    }

    function _unwrapWETH(address to, uint256 amount) internal {
        IWETH(wethAddress).withdraw(amount); // T: [WG-7]
        payable(to).sendValue(amount); // T: [WG-7]
    }

    function _checkAllowance(address spender, uint256 amount) internal {
        if (IERC20(wethAddress).allowance(address(this), spender) < amount) {
            IERC20(wethAddress).approve(spender, Constants.MAX_INT);
        }
    }

    /// @dev Only WETH contract is allowed to transfer ETH here. Prevent other addresses to send Ether to this contract.
    receive() external payable {
        require(
            msg.sender == address(wethAddress),
            Errors.WG_RECEIVE_IS_NOT_ALLOWED
        ); // T:[WG-6]
    }
}
/// @title WETHGateway
/// @notice Used for converting ETH <> WETH
function openCreditAccountETH(
    address creditManager,
    address payable onBehalfOf,
    uint256 leverageFactor,
    uint256 referralCode
)
    external
    payable
    override
    wethCreditManagerOnly(creditManager) // T:[WG-3, 4]
{
    _checkAllowance(creditManager, msg.value); // T:[WG-10]

    IWETH(wethAddress).deposit{value: msg.value}(); // T:[WG-10]
    ICreditManager(creditManager).openCreditAccount(
        msg.value,
        onBehalfOf,
        leverageFactor,
        referralCode
    ); // T:[WG-10]
}

/// @dev Opens credit account in ETH
/// @param creditManager Address of credit Manager. Should used WETH as underlying asset
/// @param onBehalfOf The address that we open credit account. Same as msg.sender if the user wants to open it for  his own wallet,
///                   or a different address if the beneficiary is a different wallet
/// @param leverageFactor Multiplier to borrowers own funds
/// @param referralCode Code used to register the integrator originating the operation, for potential rewards.
///                     0 if the action is executed directly by the user, without any middle-man