Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalized Perp Vaults #47

Open
wants to merge 7 commits into
base: wbtc/stakedao
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 27 additions & 57 deletions contracts/actions/ShortOTokenActionWithSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';

import { IAction } from '../interfaces/IAction.sol';
import { IController } from '../interfaces/IController.sol';
import { ICurve } from '../interfaces/ICurve.sol';
import { IOracle } from '../interfaces/IOracle.sol';
import { IOToken } from '../interfaces/IOToken.sol';
import { IStakeDao } from '../interfaces/IStakeDao.sol';
import { IWETH } from '../interfaces/IWETH.sol';
import { SwapTypes } from '../libraries/SwapTypes.sol';
import { AirswapBase } from '../utils/AirswapBase.sol';
Expand Down Expand Up @@ -50,21 +48,16 @@ contract ShortOTokenActionWithSwap is IAction, AirswapBase, RollOverBase {
uint256 public rolloverTime;

IController public controller;
ICurve public curvePool;
IERC20 crvLPToken;
IOracle public oracle;
IStakeDao public stakedaoStrategy;
IERC20 underlying;

event MintAndSellOToken(uint256 collateralAmount, uint256 otokenAmount, uint256 premium);

constructor(
address _vault,
address _sdTokenAddress,
address _swap,
address _opynWhitelist,
address _controller,
address _curvePoolAddress,
uint256 _vaultType,
address _underlying,
uint256 _min_profits
Expand All @@ -74,17 +67,14 @@ contract ShortOTokenActionWithSwap is IAction, AirswapBase, RollOverBase {
underlying = IERC20(_underlying);

controller = IController(_controller);
curvePool = ICurve(_curvePoolAddress);

oracle = IOracle(controller.oracle());
stakedaoStrategy = IStakeDao(_sdTokenAddress);
crvLPToken = stakedaoStrategy.token();

// enable vault to take all the sdToken back and re-distribute.
IERC20(_sdTokenAddress).safeApprove(_vault, uint256(-1));
// enable vault to take all the underlying back and re-distribute.
underlying.safeApprove(_vault, uint256(-1));

// enable pool contract to pull sdToken from this contract to mint options.
IERC20(_sdTokenAddress).safeApprove(controller.pool(), uint256(-1));
// enable pool contract to pull underlying from this contract to mint options.
underlying.safeApprove(controller.pool(), uint256(-1));

_initSwapContract(_swap);
_initRollOverBase(_opynWhitelist);
Expand All @@ -101,11 +91,18 @@ contract ShortOTokenActionWithSwap is IAction, AirswapBase, RollOverBase {
* if the action has an opened gamma vault, see if there's any short position
*/
function currentValue() external view override returns (uint256) {
return stakedaoStrategy.balanceOf(address(this)).add(lockedAsset);
return underlying.balanceOf(address(this)).add(lockedAsset);

// todo: caclulate cash value to avoid not early withdraw to avoid loss.
}

/**
* @dev return the amount of locked asset in the action.
*/
function currentLockedAsset() external view override returns (uint256) {
return lockedAsset;
}

/**
* @dev the function that the vault will call when the round is over
*/
Expand Down Expand Up @@ -136,7 +133,7 @@ contract ShortOTokenActionWithSwap is IAction, AirswapBase, RollOverBase {
}

/**
* @dev owner only function to mint options with "crvLPToken" and sell otokens in this contract
* @dev owner only function to mint options with underlying and sell otokens in this contract
* by filling an order on AirSwap.
* this can only be done in "activated" state. which is achievable by calling `rolloverPosition`
*/
Expand All @@ -147,27 +144,23 @@ contract ShortOTokenActionWithSwap is IAction, AirswapBase, RollOverBase {
require(_order.signer.token == address(underlying), 'S5');
require(_order.sender.amount == _otokenAmount, 'S6');

// mint options
// mint options & lock asset
_mintOTokens(_collateralAmount, _otokenAmount);

lockedAsset = lockedAsset.add(_collateralAmount);

// this order matters a lot because we need to get the balance before selling otokens, after minting.
uint256 sdTokenBalanceBefore = stakedaoStrategy.balanceOf(address(this));
// underlying before minting
uint256 underlyingBalanceBefore = underlying.balanceOf(address(this));

// sell options on airswap for underlying
IERC20(otoken).safeIncreaseAllowance(address(airswap), _order.sender.amount);
_fillAirswapOrder(_order);

// convert the underlying received as premium to sdToken
_underlyingToSdToken();
// check that minimum premium is received & that it is higher than min threshold
uint256 underlyingBalanceAfter = underlying.balanceOf(address(this));
uint256 underlyingTokenEarned = underlyingBalanceAfter.sub(underlyingBalanceBefore);
require(_collateralAmount.mul(MIN_PROFITS).div(BASE) <= underlyingTokenEarned, 'S7');

// check that minimum premium is received
uint256 sdTokenBalanceAfter = stakedaoStrategy.balanceOf(address(this));
uint256 sdTokenEarned = sdTokenBalanceAfter.sub(sdTokenBalanceBefore);
require(_collateralAmount.mul(MIN_PROFITS).div(BASE) <= sdTokenEarned, 'S7');

emit MintAndSellOToken(_collateralAmount, _otokenAmount, sdTokenEarned);
emit MintAndSellOToken(_collateralAmount, _otokenAmount, underlyingTokenEarned);
}

/**
Expand Down Expand Up @@ -210,41 +203,19 @@ contract ShortOTokenActionWithSwap is IAction, AirswapBase, RollOverBase {
}

/**
* @dev add liquidity to curve, deposit into stakedao.
*/
function _underlyingToSdToken() internal {
uint256 underlyingBalance = underlying.balanceOf(address(this));

uint256[3] memory amounts;
amounts[0] = 0;
amounts[1] = underlyingBalance;
amounts[2] = 0;

// deposit underlying to curve
underlying.approve(address(curvePool), underlyingBalance);
curvePool.add_liquidity(amounts, 0); // minimum amount of crvLPToken to receive is 0
uint256 crvLPTokenToDeposit = crvLPToken.balanceOf(address(this));

// deposit crvLPToken to stakedao
crvLPToken.safeApprove(address(stakedaoStrategy), 0);
crvLPToken.safeApprove(address(stakedaoStrategy), crvLPTokenToDeposit);
stakedaoStrategy.deposit(crvLPTokenToDeposit);
}

/**
* @dev mint otoken in vault 0
* @dev mint otoken in vault 1
*/
function _mintOTokens(uint256 _collateralAmount, uint256 _otokenAmount) internal {
// this action will always use vault id 0
// this action will always use vault id 1
IController.ActionArgs[] memory actions = new IController.ActionArgs[](2);

actions[0] = IController.ActionArgs(
IController.ActionType.DepositCollateral,
address(this), // vault owner is this address
address(this), // deposit from this address
address(stakedaoStrategy), // collateral is sdToken
address(underlying), // collateral is the underlying
1, // vaultId is 1
_collateralAmount, // amount of sdToken to deposit
_collateralAmount, // amount of underlying to deposit
0, // index
"" // data
);
Expand All @@ -259,7 +230,7 @@ contract ShortOTokenActionWithSwap is IAction, AirswapBase, RollOverBase {
0, // index
"" // data
);

controller.operate(actions);
}

Expand Down Expand Up @@ -293,7 +264,6 @@ contract ShortOTokenActionWithSwap is IAction, AirswapBase, RollOverBase {

return false;
}


/**
* @dev funtion to add some custom logic to check the next otoken is valid to this strategy
Expand Down
Loading