🌾Key Function - harvest

In-depth descriptions of key functions

harvest

    /**
     * @notice  harvest the strategy. This involves accruing profits from the strategy and depositing
     *          user funds to the strategy. The funds are split into their constituents and then distributed
     *          to their appropriate location.
     *          For the shortPosition a perpetual position is opened, for the long position funds are swapped
     *          to the long asset. For the buffer position the funds are deposited to the margin account idle.
     * @dev     only callable by the owner
     */
    function harvest() public onlyOwner {
        uint256 shortPosition;
        uint256 longPosition;
        uint256 bufferPosition;
        isUnwind = false;

        mcLiquidityPool.forceToSyncState();
        // determine the profit since the last harvest and remove profits from the margin
        // account to be redistributed
        uint256 amount;
        bool loss;
        if (positions.unitAccumulativeFunding != 0) {
            (amount, loss) = _determineFee();
        }
        // update the vault with profits/losses accrued and receive deposits
        uint256 newFunds = vault.update(amount, loss);
        // combine the funds and check that they are larger than 0
        uint256 toActivate = IERC20(want).balanceOf(address(this));

        if (toActivate > 0) {
            // determine the split of the funds and trade for the spot position of long
            (shortPosition, longPosition, bufferPosition) = _calculateSplit(
                toActivate
            );
            // deposit the bufferPosition to the margin account
            _depositToMarginAccount(bufferPosition);
            // open a short perpetual position and store the number of perp contracts
            positions.perpContracts += _openPerpPosition(shortPosition, true);
        }
        // record incremented positions
        positions.margin = getMargin();
        positions.unitAccumulativeFunding = getUnitAccumulativeFunding();
        emit Harvest(
            positions.perpContracts,
            IERC20(long).balanceOf(address(this)),
            positions.margin
        );
    }

The harvest function is the strategy's "work" function. It is responsible for running the strategy.

    /**
     * @notice  determine the funding premiums that have been collected since the last epoch
     * @return  fee  the funding rate premium collected since the last epoch
     * @return  loss whether the funding rate was a loss or not
     */
    function _determineFee() internal returns (uint256 fee, bool loss) {
        int256 feeInt;

        // get the cash held in the margin cash, funding rates are saved as cash in the margin account
        int256 newAccFunding = getUnitAccumulativeFunding();
        int256 prevAccFunding = positions.unitAccumulativeFunding;
        int256 livePositions = getMarginPositions();
        if (prevAccFunding >= newAccFunding) {
            // if the margin cash held has gone down then record a loss
            loss = true;
            feeInt = ((prevAccFunding - newAccFunding) * -livePositions) / 1e18;
            fee = uint256(feeInt / DECIMAL_SHIFT);
        } else {
            // if the margin cash held has gone up then record a profit and withdraw the excess for redistribution
            feeInt = ((newAccFunding - prevAccFunding) * -livePositions) / 1e18;
            uint256 balanceBefore = IERC20(want).balanceOf(address(this));
            if (feeInt > 0) {
                mcLiquidityPool.withdraw(perpetualIndex, address(this), feeInt);
            }
            fee = IERC20(want).balanceOf(address(this)) - balanceBefore;
        }
    }

The harvest will begin by determining the profit/loss since the previous harvest. It will do this by getting the unitAccumulativeFunding and getting the difference from the last harvest's unitAccumulativeFunding. If the difference is positive then the profits will be withdrawn from the margin account. If the difference is negative then the loss will be determined.

    /**
     * @notice function to update the state of the strategy in the vault and pull any funds to be redeposited
     * @param  _amount change in the vault amount sent by the strategy
     * @param  _loss   whether the change is negative or not
     *                 be the sender
     * @return toDeposit the amount to be deposited in to the strategy on this update
     */
    function update(uint256 _amount, bool _loss)
        external
        onlyStrategy
        returns (uint256 toDeposit)
    {
        // if a loss was recorded then decrease the totalLent by the amount, otherwise increase the totalLent
        if (_loss) {
            totalLent -= _amount;
        } else {
            _determineProtocolFees(_amount);
            totalLent += _amount;
        }
        // increase the totalLent by the amount of deposits that havent yet been sent to the vault
        toDeposit = want.balanceOf(address(this));
        totalLent += toDeposit;
        lastUpdate = block.timestamp;
        emit StrategyUpdate(_amount, _loss, toDeposit);
        if (toDeposit > 0) {
            want.approve(strategy, toDeposit);
            want.safeTransfer(msg.sender, toDeposit);
        }
    }

Next, the harvest will update the vault of the profit/loss. The vault will update its totalLent which is the funds it has lent to the vault; if there is a profit then totalLent will increase and the protocol fees will be determined. If there is a loss then totalLent will decrease. Finally, funds that have been deposited after the previous harvest will be recorded and transferred to the strategy contract.

    /**
     * @notice  split an amount of assets into three:
     *          the short position which represents the short perpetual position
     *          the long position which represents the long spot position
     *          the buffer position which represents the funds to be left idle in the margin account
     * @param   _amount the amount to be split in want
     * @return  shortPosition  the size of the short perpetual position in want
     * @return  longPosition   the size of the long spot position in long
     * @return  bufferPosition the size of the buffer position in want
     */
    function _calculateSplit(uint256 _amount)
        internal
        returns (
            uint256 shortPosition,
            uint256 longPosition,
            uint256 bufferPosition
        )
    {
        require(_amount > 0, "_calculateSplit: _amount is 0");
        // remove the buffer from the amount
        bufferPosition = (_amount * buffer) / MAX_BPS;
        // decrement the amount by buffer position
        _amount -= bufferPosition;
        // determine the longPosition in want then convert it to long
        uint256 longPositionWant = _amount / 2;
        longPosition = _swap(longPositionWant, want, long);
        // determine the short position
        shortPosition = _amount - longPositionWant;
    }

The harvest will then calculate the split of the gains (deposits and/or profits) and split them first into the buffer position, then the long position, then the short position. The long position is swapped from the deposit asset to the long asset using a Uniswap v2 or Uniswap v3 interfaced AMM.

    /**
     * @notice  deposit to the margin account without opening a perpetual position
     * @param   _amount the amount to deposit into the margin account
     */
    function _depositToMarginAccount(uint256 _amount) internal {
        IERC20(want).approve(address(mcLiquidityPool), _amount);
        mcLiquidityPool.deposit(
            perpetualIndex,
            address(this),
            int256(_amount) * DECIMAL_SHIFT
        );
        emit DepositToMarginAccount(_amount, perpetualIndex);
    }

The harvest will then deposit the short position and buffer position deposit asset into the strategy's MCDEX margin account.

    /**
     * @notice  open the perpetual short position on MCDEX
     * @param   _amount the collateral used to purchase the perpetual short position
     * @return  tradeAmount the amount of perpetual contracts opened
     */
    function _openPerpPosition(uint256 _amount, bool deposit)
        internal
        returns (int256 tradeAmount)
    {
        if (deposit) {
            // deposit funds to the margin account to enable trading
            _depositToMarginAccount(_amount);
        }

        // get the long asset mark price from the MCDEX oracle
        (int256 price, ) = oracle.priceTWAPLong();
        // calculate the number of contracts (*1e12 because USDC is 6 decimals)
        int256 contracts = ((int256(_amount) * DECIMAL_SHIFT) * 1e18) / price;
        int256 longBalInt = -int256(IERC20(long).balanceOf(address(this)));
        // check that the long and short positions will be equal after the deposit
        if (-contracts + getMarginPositions() >= longBalInt) {
            // open short position
            tradeAmount = mcLiquidityPool.trade(
                perpetualIndex,
                address(this),
                -contracts,
                price - slippageTolerance,
                block.timestamp,
                referrer,
                tradeMode
            );
        } else {
            tradeAmount = mcLiquidityPool.trade(
                perpetualIndex,
                address(this),
                -(getMarginPositions() - longBalInt),
                price - slippageTolerance,
                block.timestamp,
                referrer,
                tradeMode
            );
        }
        emit PerpPositionOpened(tradeAmount, perpetualIndex, _amount);
    }

The harvest will then open a short perpetual position on MCDEX using the short position collateral. The MCDEX internal oracle is used to determine the number of short contracts to open.

Finally, a few parameters are updated and a harvest event is emitted. Fin.

Last updated