Developer Guide

Inverse Finance's DCA vaults protocol allows users to deposit stablecoins and earn yields, which then will be used to invest in a more volatile asset such as ETH or WBTC. This is achieved by aggregating users’ deposited stablecoins into a vault such as the yDAI vault, then a keeper harvests the yield every day and swaps the stablecoin yields into the target asset via Uniswap. DCA vaults are open source and the contracts can be found on github.

The key directories to look at are vaults, strats and harvester. The vault is where users deposit and withdraw their underlying and yield assets. Strats stands for strategies and they are the strategies the protocol uses to generate yields. For example, it is using Compound, Harvest, Yearn and YCredit as yield strategies. Harvester is the interface to harvest yields from the vaults and then swap them for yield assets (ETH, WBTC, YFI).

In the following guide, we will use the Yearn strategy.

Vault.sol

Users can deposit stablecoins to Inverse Finance’s vault by calling the method deposit in Vault.sol, which does the following

  1. It checks that the current total supply + deposit amount does not breach depositLimit.

if (depositLimit > 0) {
// if deposit limit is 0, then there is no deposit limit
require(totalSupply().add(amount) <= depositLimit);
}
  1. It transfers the underlying asset from the user to the strategy contract

underlying.safeTransferFrom(msg.sender, address(strat), amount);
  1. It invests in Yearn’s vault. There is a buffer amount in the strategy contract, so the underlying asset is only transferred every time it is greater than the buffer.

uint balance = underlying.balanceOf(address(this));
if (balance > buffer) {
uint max = yToken.availableDepositLimit();
if(max > 0) {
yToken.deposit(Math.min(balance - buffer, max));
}
}
  1. It mints vault tokens for the user as a right to claim his deposit and yield in the future.

_mint(msg.sender, amount);

Similarly, a user can withdraw his deposit by calling withdraw.

  1. It burns the vault tokens.

_burn(msg.sender, amount);
  1. It divests the withdrawal amount from the strategy contract.

strat.divest(amount);
  1. It sends the user back his underlying asset.

underlying.safeTransfer(msg.sender, amount);

Users are entitled to receive dividends in the vault’s target asset by calling claim

  1. It checks the user’s withdrawable dividends, which is the user’s accumulative dividends minus the user’s withdrawn dividends.

accumulativeDividendOf(_owner).sub(withdrawnDividends[_owner]);

A user’s accumulative dividend is calculated by multiplying his magnified dividend per share by his balance and then adjust for dividend corrections. The vault is inherited from the contract DividenToken.sol and follows the ERC-1726 standard. The reason why the dividend per share has to be magnified by a constant magnitude is to avoid rounding errors for small numbers (See this post for more information). The purpose of storage magnifiedDividendCorrections is to account for a user’s balance change. When a user’s balance is updated in the vault, his dividend should remain the same. However, the computed value of “dividend per share x user balance“ has changed as a result of user balance change. Dividend correction is introduced to counter the change in user balance.

magnifiedDividendPerShare.mul(balanceOf(_owner)).toInt256Safe().add(magnifiedDividendCorrections[_owner]).toUint256Safe() / magnitude;
  1. It updates the user’s withdrawn dividend

withdrawnDividends[user] = withdrawnDividends[user].add(_withdrawableDividend);
  1. It transfers the yield asset to the user

target.safeTransfer(user, _withdrawableDividend);

There are two other methods to pay attention to, namely setStrat and harvest. setStrat can only be called by the timelock address and by doing this changes the vault’s yield strategy to a new one.

  1. It divests from the current vault.

uint prevTotalValue = strat.calcTotalValue();
strat.divest(prevTotalValue);
  1. It transfers the underlying asset to the new strategy contract and invests in the new strategy.

underlying.safeTransfer(address(strat_), underlying.balanceOf(address(this)));
strat_.invest();

I will talk about harvesting yields in the next section.

Harvester.sol

The purpose of this contract is to harvest yields generated from the vault and to swap the yields for the target assets through a decentralized exchange. It is done by calling the method harvestVault, which internally calls the vault’s method harvest.

  1. It harvests from the vault.

uint afterFee = vault.harvest(amount);
  1. It checks the time elapsed since the last harvest calculates the vault’s token generating rate.

uint durationSinceLastHarvest = block.timestamp.sub(vault.lastDistribution());
ratePerToken[vault] = afterFee.mul(10**(36-from.decimals())).div(vault.totalSupply()).div(durationSinceLastHarvest);
  1. It swaps the yields for the target asset by calling Uniswap router’s method swapExactTokensForTokens. This is a standard method name/signature for most decentralized exchanges so the router should be interchangeable.

IERC20Detailed from = vault.underlying();
IERC20 to = vault.target();
from.approve(address(router), afterFee);
uint received = router.swapExactTokensForTokens(afterFee, outMin, path, address(this), deadline)[path.length-1];
  1. Finally, it distributes the received target asset to the vault.

to.approve(address(vault), received);
vault.distribute(received);

The harvester calls the harvester contract every day in order to achieve dollar-cost averaging.

Strategies

Inverse Finance uses different strategies to generate yields and each contract is an interface to an underlying protocol such as Yearn or Compound. Each contract has standardized methods invest, divest and calcTotalValue so that the vault contract can interact with each strategy interchangeably.

Reference

https://0xkowloon.substack.com/p/dissecting-the-inverse-finance-protocol