Dissecting the Inverse Finance protocol
Inverse Finance is a protocol that 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. The code repository lives in https://github.com/InverseFinance/inverse-protocol
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
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); }
It transfers the underlying asset from the user to the strategy contract
underlying.safeTransferFrom(msg.sender, address(strat), amount);
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)); } }
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
.
It burns the vault tokens.
_burn(msg.sender, amount);
It divests the withdrawal amount from the strategy contract.
strat.divest(amount);
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
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 constantmagnitude
is to avoid rounding errors for small numbers (See this post for more information). The purpose of storagemagnifiedDividendCorrections
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;
It updates the user’s withdrawn dividend
withdrawnDividends[user] = withdrawnDividends[user].add(_withdrawableDividend);
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.
It divests from the current vault.
uint prevTotalValue = strat.calcTotalValue(); strat.divest(prevTotalValue);
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
.
It harvests from the vault.
uint afterFee = vault.harvest(amount);
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);
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];
Finally, it distributes the received target asset to the vault.
to.approve(address(vault), received); vault.distribute(received);
It appears that there is another contract called LpUniswapHarvester
, which means Inverse Finance is also investing users’ deposits into LP tokens to earn LP provider fees (I am not too familiar with Inverse Finance’s complete offering yet but this is what the contract name suggests).
I believe I read somewhere that the harvester (admin) 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.