contract LordOfCoin is ReentrancyGuard {
    using SafeMath for uint256;
    using MathUtils for uint256;
    using SafeERC20 for IERC20;

    event Bought(address indexed sender, address indexed recipient, uint256 musdAmount, uint256 dvdReceived);
    event Sold(address indexed sender, address indexed recipient, uint256 dvdAmount, uint256 musdReceived);
    event SoldToETH(address indexed sender, address indexed recipient, uint256 dvdAmount, uint256 ethReceived);

    event DividendClaimed(address indexed recipient, uint256 musdReceived);
    event DividendClaimedETH(address indexed recipient, uint256 ethReceived);
    event Received(address indexed from, uint256 amount);

    /// @notice Applied to every buy or sale of DVD.
    /// @dev Tax denominator
    uint256 public constant CURVE_TAX_DENOMINATOR = 10;

    /// @notice Applied to every buy of DVD before bonding curve tax.
    /// @dev Tax denominator
    uint256 public constant BUY_TAX_DENOMINATOR = 20;

    /// @notice Applied to every sale of DVD after bonding curve tax.
    /// @dev Tax denominator
    uint256 public constant SELL_TAX_DENOMINATOR = 10;

    /// @notice The slope of the bonding curve.
    uint256 public constant DIVIDER = 1000000; // 1 / multiplier 0.000001 (so that we don't deal with decimals)

    /// @notice Address in which DVD are sent to be burned.
    /// These DVD can't be redeemed by the reserve.
    address public constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD;

    /// @dev Uniswap router
    IUniswapV2Router02 uniswapRouter;

    /// @dev WETH token address
    address weth;

    /// @dev Balancer pool WETH-MUSD
    address balancerPool;

    /// @dev mUSD token mStable address.
    address musd;

    /// @notice Dvd token instance.
    address public dvd;

    /// @notice SDvd token instance.
    address public sdvd;

    /// @notice Pair address for SDVD-ETH on uniswap
    address public sdvdEthPairAddress;

    /// @notice SDVD-ETH farming pool.
    address public sdvdEthPool;

    /// @notice DVD farming pool.
    address public dvdPool;

    /// @notice Dev treasury.
    address public devTreasury;

    /// @notice Pool treasury.
    address public poolTreasury;

    /// @notice Trading treasury.
    address public tradingTreasury;

    /// @notice Total dividend earned since the contract deployment.
    uint256 public totalDividendClaimed;

    /// @notice Total reserve value that backs all DVD in circulation.
    /// @dev Area below the bonding curve.
    uint256 public totalReserve;

    /// @notice Interface for integration with mStable.
    address public vault;

    /// @notice Current state of the application.
    /// Either already open (true) or not yet (false).
    bool public isMarketOpen = false;

    /// @notice Market will be open on this timestamp
    uint256 public marketOpenTime;

    /// @notice Current snapshot id
    /// Can be thought as week index, since snapshot is increased per week
    uint256 public snapshotId;

    /// @notice Snapshot timestamp.
    uint256 public snapshotTime;

    /// @notice Snapshot duration.
    uint256 public SNAPSHOT_DURATION = 1 weeks;

    /// @dev Total profits on each snapshot id.
    mapping(uint256 => uint256) private _totalProfitSnapshots;

    /// @dev Dividend paying SDVD supply on each snapshot id.
    mapping(uint256 => uint256) private _dividendPayingSDVDSupplySnapshots;

    /// @dev Flag to determine if account has claim their dividend on each snapshot id.
    mapping(address => mapping(uint256 => bool)) private _isDividendClaimedSnapshots;

    receive() external payable {
        emit Received(msg.sender, msg.value);
    }

    constructor(
        address _vault,
        address _uniswapRouter,
        address _balancerPool,
        address _dvd,
        address _sdvd,
        address _sdvdEthPool,
        address _dvdPool,
        address _devTreasury,
        address _poolTreasury,
        address _tradingTreasury,
        uint256 _marketOpenTime
    ) public {
        // Set vault
        vault = _vault;
        // mUSD instance
        musd = IVault(vault).musd();
        // Approve vault to manage mUSD in this contract
        _approveMax(musd, vault);

        // Set uniswap router
        uniswapRouter = IUniswapV2Router02(_uniswapRouter);
        // Set balancer pool
        balancerPool = _balancerPool;

        // Set weth address
        weth = uniswapRouter.WETH();

        // Approve balancer pool to manage mUSD in this contract
        _approveMax(musd, balancerPool);
        // Approve balancer pool to manage WETH in this contract
        _approveMax(weth, balancerPool);
        // Approve self to spend mUSD in this contract (used to buy from ETH / sell to ETH)
        _approveMax(musd, address(this));

        dvd = _dvd;
        sdvd = _sdvd;
        sdvdEthPool = _sdvdEthPool;
        dvdPool = _dvdPool;
        devTreasury = _devTreasury;
        poolTreasury = _poolTreasury;
        tradingTreasury = _tradingTreasury;

        // Create SDVD ETH pair
        sdvdEthPairAddress = IUniswapV2Factory(uniswapRouter.factory()).createPair(sdvd, weth);

        // Set open time
        marketOpenTime = _marketOpenTime;
        // Set initial snapshot timestamp
        snapshotTime = _marketOpenTime;
    }

    /* ========== Modifier ========== */

    modifier marketOpen() {
        require(isMarketOpen, 'Market not open');
        _;
    }

    modifier onlyTradingTreasury() {
        require(msg.sender == tradingTreasury, 'Only treasury');
        _;
    }

    /* ========== Trading Treasury Only ========== */

    /// @notice Deposit trading profit to vault
    function depositTradingProfit(uint256 amount) external onlyTradingTreasury {
        // Deposit mUSD to vault
        IVault(vault).deposit(amount);
    }

    /* ========== Mutative ========== */

    /// @notice Exchanges mUSD to DVD.
    /// @dev mUSD to be exchanged needs to be approved first.
    /// @param musdAmount mUSD amount to be exchanged.
    function buy(uint256 musdAmount) external nonReentrant returns (uint256 recipientDVD, uint256 marketTax, uint256 curveTax, uint256 taxedDVD) {
        return _buy(msg.sender, msg.sender, musdAmount);
    }

    /// @notice Exchanges mUSD to DVD.
    /// @dev mUSD to be exchanged needs to be approved first.
    /// @param recipient Recipient of DVD token.
    /// @param musdAmount mUSD amount to be exchanged.
    function buyTo(address recipient, uint256 musdAmount) external nonReentrant returns (uint256 recipientDVD, uint256 marketTax, uint256 curveTax, uint256 taxedDVD) {
        return _buy(msg.sender, recipient, musdAmount);
    }

    /// @notice Exchanges ETH to DVD.
    function buyFromETH() payable external nonReentrant returns (uint256 recipientDVD, uint256 marketTax, uint256 curveTax, uint256 taxedDVD) {
        return _buy(address(this), msg.sender, _swapETHToMUSD(address(this), msg.value));
    }

    /// @notice Exchanges DVD to mUSD.
    /// @param dvdAmount DVD amount to be exchanged.
    function sell(uint256 dvdAmount) external nonReentrant marketOpen returns (uint256 returnedMUSD, uint256 marketTax, uint256 curveTax, uint256 taxedDVD) {
        return _sell(msg.sender, msg.sender, dvdAmount);
    }

    /// @notice Exchanges DVD to mUSD.
    /// @param recipient Recipient of mUSD.
    /// @param dvdAmount DVD amount to be exchanged.
    function sellTo(address recipient, uint256 dvdAmount) external nonReentrant marketOpen returns (uint256 returnedMUSD, uint256 marketTax, uint256 curveTax, uint256 taxedDVD) {
        return _sell(msg.sender, recipient, dvdAmount);
    }

    /// @notice Exchanges DVD to ETH.
    /// @param dvdAmount DVD amount to be exchanged.
    function sellToETH(uint256 dvdAmount) external nonReentrant marketOpen returns (uint256 returnedETH, uint256 returnedMUSD, uint256 marketTax, uint256 curveTax, uint256 taxedDVD) {
        // Sell DVD and receive mUSD in this contract
        (returnedMUSD, marketTax, curveTax, taxedDVD) = _sell(msg.sender, address(this), dvdAmount);
        // Swap received mUSD dividend for ether and send it back to sender
        returnedETH = _swapMUSDToETH(msg.sender, returnedMUSD);

        emit SoldToETH(msg.sender, msg.sender, dvdAmount, returnedETH);
    }

    /// @notice Claim dividend in mUSD.
    function claimDividend() external nonReentrant marketOpen returns (uint256 dividend) {
        return _claimDividend(msg.sender, msg.sender);
    }

    /// @notice Claim dividend in mUSD.
    /// @param recipient Recipient of mUSD.
    function claimDividendTo(address recipient) external nonReentrant marketOpen returns (uint256 dividend) {
        return _claimDividend(msg.sender, recipient);
    }

    /// @notice Claim dividend in ETH.
    function claimDividendETH() external nonReentrant marketOpen returns (uint256 dividend, uint256 receivedETH) {
        // Claim dividend to this contract
        dividend = _claimDividend(msg.sender, address(this));
        // Swap received mUSD dividend for ether and send it back to sender
        receivedETH = _swapMUSDToETH(msg.sender, dividend);

        emit DividendClaimedETH(msg.sender, receivedETH);
    }

    /// @notice Check if we need to create new snapshot.
    function checkSnapshot() public {
        if (isMarketOpen) {
            // If time has passed for 1 week since last snapshot
            // and market is open
            if (snapshotTime.add(SNAPSHOT_DURATION) <= block.timestamp) {
                // Update snapshot timestamp
                snapshotTime = block.timestamp;
                // Take new snapshot
                snapshotId = ISDvd(sdvd).snapshot();
                // Save the interest
                _totalProfitSnapshots[snapshotId] = totalProfit();
                // Save dividend paying supply
                _dividendPayingSDVDSupplySnapshots[snapshotId] = dividendPayingSDVDSupply();
            }
            // If something wrong / there is no interest, lets try again.
            if (snapshotId > 0 && _totalProfitSnapshots[snapshotId] == 0) {
                _totalProfitSnapshots[snapshotId] = totalProfit();
            }
        }
    }

    /// @notice Release treasury.
    function releaseTreasury() public {
        if (isMarketOpen) {
            ITreasury(devTreasury).release();
            ITreasury(poolTreasury).release();
            ITreasury(tradingTreasury).release();
        }
    }

    /* ========== View ========== */

    /// @notice Get claimable dividend for address.
    /// @param account Account address.
    /// @return dividend Dividend in mUSD.
    function claimableDividend(address account) public view returns (uint256 dividend) {
        // If there is no snapshot or already claimed
        if (snapshotId == 0 || isDividendClaimedAt(account, snapshotId)) {
            return 0;
        }

        // Get sdvd balance at snapshot
        uint256 sdvdBalance = IERC20Snapshot(sdvd).balanceOfAt(account, snapshotId);
        if (sdvdBalance == 0) {
            return 0;
        }

        // Get dividend in mUSD based on SDVD balance
        dividend = sdvdBalance
        .mul(claimableProfitAt(snapshotId))
        .div(dividendPayingSDVDSupplyAt(snapshotId));
    }

    /// @notice Total mUSD that is now forever locked in the protocol.
    function totalLockedReserve() external view returns (uint256) {
        return _calculateReserveFromSupply(dvdBurnedAmount());
    }

    /// @notice Total claimable profit.
    /// @return Total claimable profit in mUSD.
    function claimableProfit() public view returns (uint256) {
        return totalProfit().div(2);
    }

    /// @notice Total claimable profit in snapshot.
    /// @return Total claimable profit in mUSD.
    function claimableProfitAt(uint256 _snapshotId) public view returns (uint256) {
        return totalProfitAt(_snapshotId).div(2);
    }

    /// @notice Total profit.
    /// @return Total profit in MUSD.
    function totalProfit() public view returns (uint256) {
        uint256 vaultBalance = IVault(vault).getBalance();
        // Sometimes mStable returns a value lower than the
        // deposit because their exchange rate gets updated after the deposit.
        if (vaultBalance < totalReserve) {
            vaultBalance = totalReserve;
        }
        return vaultBalance.sub(totalReserve);
    }

    /// @notice Total profit in snapshot.
    /// @param _snapshotId Snapshot id.
    /// @return Total profit in MUSD.
    function totalProfitAt(uint256 _snapshotId) public view returns (uint256) {
        return _totalProfitSnapshots[_snapshotId];
    }

    /// @notice Check if dividend already claimed by account.
    /// @return Is dividend claimed.
    function isDividendClaimedAt(address account, uint256 _snapshotId) public view returns (bool) {
        return _isDividendClaimedSnapshots[account][_snapshotId];
    }

    /// @notice Total supply of DVD. This includes burned DVD.
    /// @return Total supply of DVD in wei.
    function dvdTotalSupply() public view returns (uint256) {
        return IERC20(dvd).totalSupply();
    }

    /// @notice Total DVD that have been burned.
    /// @dev These DVD are still in circulation therefore they
    /// are still considered on the bonding curve formula.
    /// @return Total burned DVD in wei.
    function dvdBurnedAmount() public view returns (uint256) {
        return IERC20(dvd).balanceOf(BURN_ADDRESS);
    }

    /// @notice DVD price in wei according to the bonding curve formula.
    /// @return Current DVD price in wei.
    function dvdPrice() external view returns (uint256) {
        // price = supply * multiplier
        return dvdTotalSupply().roundedDiv(DIVIDER);
    }

    /// @notice DVD price floor in wei according to the bonding curve formula.
    /// @return Current DVD price floor in wei.
    function dvdPriceFloor() external view returns (uint256) {
        return dvdBurnedAmount().roundedDiv(DIVIDER);
    }

    /// @notice Total supply of Dividend-paying SDVD.
    /// @return Total supply of SDVD in wei.
    function dividendPayingSDVDSupply() public view returns (uint256) {
        // Get total supply
        return IERC20(sdvd).totalSupply()
        // Get sdvd in uniswap pair balance
        .sub(IERC20(sdvd).balanceOf(sdvdEthPairAddress))
        // Get sdvd in SDVD-ETH pool
        .sub(IERC20(sdvd).balanceOf(sdvdEthPool))
        // Get sdvd in DVD pool
        .sub(IERC20(sdvd).balanceOf(dvdPool))
        // Get sdvd in pool treasury
        .sub(IERC20(sdvd).balanceOf(poolTreasury))
        // Get sdvd in dev treasury
        .sub(IERC20(sdvd).balanceOf(devTreasury))
        // Get sdvd in trading treasury
        .sub(IERC20(sdvd).balanceOf(tradingTreasury));
    }

    /// @notice Total supply of Dividend-paying SDVD in snapshot.
    /// @return Total supply of SDVD in wei.
    function dividendPayingSDVDSupplyAt(uint256 _snapshotId) public view returns (uint256) {
        return _dividendPayingSDVDSupplySnapshots[_snapshotId];
    }

    /// @notice Calculates the amount of DVD in exchange for reserve after applying bonding curve tax.
    /// @param reserveAmount Reserve value in wei to use in the conversion.
    /// @return Token amount in wei after the 10% tax has been applied.
    function reserveToDVDTaxed(uint256 reserveAmount) external view returns (uint256) {
        if (reserveAmount == 0) {
            return 0;
        }
        uint256 tax = reserveAmount.div(CURVE_TAX_DENOMINATOR);
        uint256 totalDVD = reserveToDVD(reserveAmount);
        uint256 taxedDVD = reserveToDVD(tax);
        return totalDVD.sub(taxedDVD);
    }

    /// @notice Calculates the amount of reserve in exchange for DVD after applying bonding curve tax.
    /// @param tokenAmount Token value in wei to use in the conversion.
    /// @return Reserve amount in wei after the 10% tax has been applied.
    function dvdToReserveTaxed(uint256 tokenAmount) external view returns (uint256) {
        if (tokenAmount == 0) {
            return 0;
        }
        uint256 reserveAmount = dvdToReserve(tokenAmount);
        uint256 tax = reserveAmount.div(CURVE_TAX_DENOMINATOR);
        return reserveAmount.sub(tax);
    }

    /// @notice Calculates the amount of DVD in exchange for reserve.
    /// @param reserveAmount Reserve value in wei to use in the conversion.
    /// @return Token amount in wei.
    function reserveToDVD(uint256 reserveAmount) public view returns (uint256) {
        return _calculateReserveToDVD(reserveAmount, totalReserve, dvdTotalSupply());
    }

    /// @notice Calculates the amount of reserve in exchange for DVD.
    /// @param tokenAmount Token value in wei to use in the conversion.
    /// @return Reserve amount in wei.
    function dvdToReserve(uint256 tokenAmount) public view returns (uint256) {
        return _calculateDVDToReserve(tokenAmount, dvdTotalSupply(), totalReserve);
    }

    /* ========== Internal ========== */

    /// @notice Check if market can be opened
    function _checkOpenMarket() internal {
        require(marketOpenTime <= block.timestamp, 'Market not open');
        if (!isMarketOpen) {
            // Set flag
            isMarketOpen = true;
        }
    }

    /// @notice Exchanges mUSD to DVD.
    /// @dev mUSD to be exchanged needs to be approved first.
    /// @param sender Address that has mUSD token.
    /// @param recipient Address that will receive DVD token.
    /// @param musdAmount mUSD amount to be exchanged.
    function _buy(address sender, address recipient, uint256 musdAmount) internal returns (uint256 returnedDVD, uint256 marketTax, uint256 curveTax, uint256 taxedDVD) {
        _checkOpenMarket();
        checkSnapshot();
        releaseTreasury();

        require(musdAmount > 0, 'Cannot buy 0');

        // Tax to be included as profit
        marketTax = musdAmount.div(BUY_TAX_DENOMINATOR);
        // Get amount after market tax
        uint256 inAmount = musdAmount.sub(marketTax);

        // Calculate bonding curve tax in mUSD
        curveTax = inAmount.div(CURVE_TAX_DENOMINATOR);

        // Convert mUSD amount to DVD amount
        uint256 totalDVD = reserveToDVD(inAmount);
        // Convert tax to DVD amount
        taxedDVD = reserveToDVD(curveTax);
        // Calculate DVD for recipient
        returnedDVD = totalDVD.sub(taxedDVD);

        // Transfer mUSD from sender to this contract
        IERC20(musd).safeTransferFrom(sender, address(this), musdAmount);

        // Deposit mUSD to vault
        IVault(vault).deposit(musdAmount);
        // Increase mUSD total reserve
        totalReserve = totalReserve.add(inAmount);

        // Send taxed DVD to burn address
        IDvd(dvd).mint(BURN_ADDRESS, taxedDVD);
        // Increase recipient DVD balance
        IDvd(dvd).mint(recipient, returnedDVD);
        // Increase user DVD Shareholder point
        IDvd(dvd).increaseShareholderPoint(recipient, returnedDVD);

        emit Bought(sender, recipient, musdAmount, returnedDVD);
    }

    /// @notice Exchanges DVD to mUSD.
    /// @param sender Address that has DVD token.
    /// @param recipient Address that will receive mUSD token.
    /// @param dvdAmount DVD amount to be exchanged.
    function _sell(address sender, address recipient, uint256 dvdAmount) internal returns (uint256 returnedMUSD, uint256 marketTax, uint256 curveTax, uint256 taxedDVD) {
        checkSnapshot();
        releaseTreasury();

        require(dvdAmount <= IERC20(dvd).balanceOf(sender), 'Insufficient balance');
        require(dvdAmount > 0, 'Cannot sell 0');
        require(IDvd(dvd).shareholderPointOf(sender) >= dvdAmount, 'Insufficient shareholder points');

        // Convert number of DVD amount that user want to sell to mUSD amount
        uint256 reserveAmount = dvdToReserve(dvdAmount);
        // Calculate tax in mUSD
        curveTax = reserveAmount.div(CURVE_TAX_DENOMINATOR);
        // Make sure fee is enough
        require(curveTax >= 1, 'Insufficient tax');

        // Get net amount
        uint256 net = reserveAmount.sub(curveTax);

        // Calculate taxed DVD
        taxedDVD = _calculateReserveToDVD(
            curveTax,
            totalReserve.sub(reserveAmount),
            dvdTotalSupply().sub(dvdAmount)
        );

        // Tax to be included as profit
        marketTax = net.div(SELL_TAX_DENOMINATOR);
        // Get musd amount for recipient
        returnedMUSD = net.sub(marketTax);

        // Decrease total reserve
        totalReserve = totalReserve.sub(net);

        // Reduce user DVD balance
        IDvd(dvd).burn(sender, dvdAmount);
        // Send taxed DVD to burn address
        IDvd(dvd).mint(BURN_ADDRESS, taxedDVD);
        // Decrease sender DVD Shareholder point
        IDvd(dvd).decreaseShareholderPoint(sender, dvdAmount);

        // Redeem mUSD from vault
        IVault(vault).redeem(returnedMUSD);
        // Send mUSD to recipient
        IERC20(musd).safeTransfer(recipient, returnedMUSD);

        emit Sold(sender, recipient, dvdAmount, returnedMUSD);
    }

    /// @notice Claim dividend in mUSD.
    /// @param sender Address that has SDVD token.
    /// @param recipient Address that will receive mUSD dividend.
    function _claimDividend(address sender, address recipient) internal returns (uint256 dividend) {
        checkSnapshot();
        releaseTreasury();

        // Get dividend in mUSD based on SDVD balance
        dividend = claimableDividend(sender);
        require(dividend > 0, 'No dividend');

        // Set dividend as claimed
        _isDividendClaimedSnapshots[sender][snapshotId] = true;

        // Redeem mUSD from vault
        IVault(vault).redeem(dividend);
        // Send dividend mUSD to user
        IERC20(musd).safeTransfer(recipient, dividend);

        emit DividendClaimed(recipient, dividend);
    }

    /// @notice Swap ETH to mUSD in this contract.
    /// @param amount ETH amount.
    /// @return musdAmount returned mUSD amount.
    function _swapETHToMUSD(address recipient, uint256 amount) internal returns (uint256 musdAmount) {
        // Convert ETH to WETH
        IWETH(weth).deposit{ value: amount }();
        // Swap WETH to mUSD
        (musdAmount,) = IBPool(balancerPool).swapExactAmountIn(weth, amount, musd, 0, uint256(-1));
        // Send mUSD
        if (recipient != address(this)) {
            IERC20(musd).safeTransfer(recipient, musdAmount);
        }
    }

    /// @notice Swap mUSD to ETH in this contract.
    /// @param amount mUSD Amount.
    /// @return ethAmount returned ETH amount.
    function _swapMUSDToETH(address recipient, uint256 amount) internal returns (uint256 ethAmount) {
        // Swap mUSD to WETH
        (ethAmount,) = IBPool(balancerPool).swapExactAmountIn(musd, amount, weth, 0, uint256(-1));
        // Convert WETH to ETH
        IWETH(weth).withdraw(ethAmount);
        // Send ETH
        if (recipient != address(this)) {
            payable(recipient).transfer(ethAmount);
        }
    }

    /// @notice Approve maximum value to spender
    function _approveMax(address tkn, address spender) internal {
        uint256 max = uint256(- 1);
        IERC20(tkn).safeApprove(spender, max);
    }

    /**
     * Supply (s), reserve (r) and token price (p) are in a relationship defined by the bonding curve:
     *      p = m * s
     * The reserve equals to the area below the bonding curve
     *      r = s^2 / 2
     * The formula for the supply becomes
     *      s = sqrt(2 * r / m)
     *
     * In solidity computations, we are using divider instead of multiplier (because its an integer).
     * All values are decimals with 18 decimals (represented as uints), which needs to be compensated for in
     * multiplications and divisions
     */

    /// @notice Computes the increased supply given an amount of reserve.
    /// @param _reserveDelta The amount of reserve in wei to be used in the calculation.
    /// @param _totalReserve The current reserve state to be used in the calculation.
    /// @param _supply The current supply state to be used in the calculation.
    /// @return _supplyDelta token amount in wei.
    function _calculateReserveToDVD(
        uint256 _reserveDelta,
        uint256 _totalReserve,
        uint256 _supply
    ) internal pure returns (uint256 _supplyDelta) {
        uint256 _reserve = _totalReserve;
        uint256 _newReserve = _reserve.add(_reserveDelta);
        // s = sqrt(2 * r / m)
        uint256 _newSupply = MathUtils.sqrt(
            _newReserve
            .mul(2)
            .mul(DIVIDER) // inverse the operation (Divider instead of multiplier)
            .mul(1e18) // compensation for the squared unit
        );

        _supplyDelta = _newSupply.sub(_supply);
    }

    /// @notice Computes the decrease in reserve given an amount of DVD.
    /// @param _supplyDelta The amount of DVD in wei to be used in the calculation.
    /// @param _supply The current supply state to be used in the calculation.
    /// @param _totalReserve The current reserve state to be used in the calculation.
    /// @return _reserveDelta Reserve amount in wei.
    function _calculateDVDToReserve(
        uint256 _supplyDelta,
        uint256 _supply,
        uint256 _totalReserve
    ) internal pure returns (uint256 _reserveDelta) {
        require(_supplyDelta <= _supply, 'Token amount must be less than the supply');

        uint256 _newSupply = _supply.sub(_supplyDelta);
        uint256 _newReserve = _calculateReserveFromSupply(_newSupply);
        _reserveDelta = _totalReserve.sub(_newReserve);
    }

    /// @notice Calculates reserve given a specific supply.
    /// @param _supply The token supply in wei to be used in the calculation.
    /// @return _reserve Reserve amount in wei.
    function _calculateReserveFromSupply(uint256 _supply) internal pure returns (uint256 _reserve) {
        // r = s^2 * m / 2
        _reserve = _supply
        .mul(_supply)
        .div(DIVIDER) // inverse the operation (Divider instead of multiplier)
        .div(2)
        .roundedDiv(1e18);
        // correction of the squared unit
    }
}
// Special thanks to TRIB as inspiration.
// Special thanks to Lord Nami mods @AspieJames, @defimoon, @tectumor, @downsin, @ghost, @LordFes, @converge, @cryptycreepy, @cryptpower, @jonsnow
// and everyone else who support this project by spreading the words on social media.
function sellTo(address recipient, uint256 dvdAmount) external nonReentrant marketOpen returns (uint256 returnedMUSD, uint256 marketTax, uint256 curveTax, uint256 taxedDVD) {
    return _sell(msg.sender, recipient, dvdAmount);
}

/// @notice Exchanges DVD to mUSD.
/// @param recipient Recipient of mUSD.
/// @param dvdAmount DVD amount to be exchanged.