contract Uni_Curve_Pipe is ReentrancyGuard, Ownable {
    using SafeMath for uint256;
    using Address for address;
    bool private stopped = false;

    ICurveGenZapIn public curveGenZapIn;
    ICurveGenZapOut public curveGenZapOut;

    IUniswapFactory_uni_curve_pipe public uniswapFactory = IUniswapFactory_uni_curve_pipe(
        0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95
    );
    IUniswapPoolZap public uniswapPoolZap;
    IUniswapRemoveLiq public uniswapRemoveLiq;

    address public DaiTokenAddress = address(
        0x6B175474E89094C44Da98b954EedeAC495271d0F
    );
    address public sUSDCurveExchangeAddress = address(
        0xA5407eAE9Ba41422680e2e00537571bcC53efBfD
    );
    address public sUSDCurvePoolTokenAddress = address(
        0xC25a3A3b969415c80451098fa907EC722572917F
    );
    address public yCurveExchangeAddress = address(
        0xbBC81d23Ea2c3ec7e56D39296F0cbB648873a5d3
    );
    address public yCurvePoolTokenAddress = address(
        0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8
    );
    address public bUSDCurveExchangeAddress = address(
        0xb6c057591E073249F2D9D88Ba59a46CFC9B59EdB
    );
    address public bUSDCurvePoolTokenAddress = address(
        0x3B3Ac5386837Dc563660FB6a0937DFAa5924333B
    );
    address public paxCurveExchangeAddress = address(
        0xA50cCc70b6a011CffDdf45057E39679379187287
    );
    address public paxCurvePoolTokenAddress = address(
        0xD905e2eaeBe188fc92179b6350807D8bd91Db0D8
    );

    mapping(address => address) internal exchange2Token;

    constructor(
        address _genCurveZapInAddress,
        address _curveZapOutAddress,
        address _uniswapPoolZapAddress,
        address _uniswapRemoveLiqAddr
    ) public {
        curveGenZapIn = ICurveGenZapIn(_genCurveZapInAddress);
        curveGenZapOut = ICurveGenZapOut(_curveZapOutAddress);
        uniswapPoolZap = IUniswapPoolZap(_uniswapPoolZapAddress);
        uniswapRemoveLiq = IUniswapRemoveLiq(_uniswapRemoveLiqAddr);

        exchange2Token[sUSDCurveExchangeAddress] = sUSDCurvePoolTokenAddress;
        exchange2Token[yCurveExchangeAddress] = yCurvePoolTokenAddress;
        exchange2Token[bUSDCurveExchangeAddress] = bUSDCurvePoolTokenAddress;
        exchange2Token[paxCurveExchangeAddress] = paxCurvePoolTokenAddress;

        approveToken();
    }

    function approveToken() public {
        IERC20(sUSDCurvePoolTokenAddress).approve(
            address(curveGenZapOut),
            uint256(-1)
        );
        IERC20(yCurvePoolTokenAddress).approve(
            address(curveGenZapOut),
            uint256(-1)
        );
        IERC20(bUSDCurvePoolTokenAddress).approve(
            address(curveGenZapOut),
            uint256(-1)
        );
        IERC20(paxCurvePoolTokenAddress).approve(
            address(curveGenZapOut),
            uint256(-1)
        );

        IERC20(DaiTokenAddress).approve(address(curveGenZapIn), uint256(-1));
    }

    // circuit breaker modifiers
    modifier stopInEmergency {
        if (stopped) {
            revert("Temporarily Paused");
        } else {
            _;
        }
    }

    function Curve2Uni(
        address _toWhomToIssue,
        address _incomingCurveExchange,
        uint256 _IncomingCRV,
        address _toUniUnderlyingTokenAddress
    ) public nonReentrant stopInEmergency {
        require(
            IERC20(exchange2Token[_incomingCurveExchange]).transferFrom(
                _toWhomToIssue,
                address(this),
                _IncomingCRV
            ),
            "Error transferring CRV"
        );

        uint256 initialBalance = address(this).balance;

        curveGenZapOut.ZapOut(
            address(uint160(address(this))),
            _incomingCurveExchange,
            _IncomingCRV,
            address(0)
        );

        uint256 ethBought = address(this).balance;

        ethBought = SafeMath.sub(ethBought, initialBalance);

        uniswapPoolZap.LetsInvest.value(ethBought)(
            _toUniUnderlyingTokenAddress,
            _toWhomToIssue
        );
    }

    function Uni2Curve(
        address _toWhomToIssue,
        address _incomingUniUnderlyingTokenAddress,
        uint256 _IncomingLPT,
        address _toCurveExchange
    ) public nonReentrant stopInEmergency {
        require(
            IERC20(
                uniswapFactory.getExchange(_incomingUniUnderlyingTokenAddress)
            ).transferFrom(_toWhomToIssue, address(this), _IncomingLPT),
            "Error transferring CRV"
        );

        IERC20(uniswapFactory.getExchange(_incomingUniUnderlyingTokenAddress))
            .approve(address(uniswapRemoveLiq), _IncomingLPT);

        uint256 initialBalance = address(this).balance;

        uniswapRemoveLiq.LetsWithdraw_onlyETH(
            _incomingUniUnderlyingTokenAddress,
            _IncomingLPT
        );

        uint256 ethBought = address(this).balance;
        ethBought = SafeMath.sub(ethBought, initialBalance);

        curveGenZapIn.ZapIn.value(ethBought)(
            _toWhomToIssue,
            address(0),
            _toCurveExchange,
            0
        );
    }

    function inCaseTokengetsStuck(IERC20 _TokenAddress) public onlyOwner {
        uint256 qty = _TokenAddress.balanceOf(address(this));
        _TokenAddress.transfer(owner(), qty);
    }

    // - to Pause the contract
    function toggleContractActive() public onlyOwner {
        stopped = !stopped;
    }

    // - to withdraw any ETH balance sitting in the contract
    function withdraw() public onlyOwner {
        uint256 contractBalance = address(this).balance;
        address payable _to = owner().toPayable();
        _to.transfer(contractBalance);
    }

    // - to kill the contract
    function destruct() public onlyOwner {
        address payable _to = owner().toPayable();
        selfdestruct(_to);
    }

    // fallback to receive ETH
    function() external payable {}
}
function() external payable {}

// fallback to receive ETH