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

ZenBull strategy oracle #517

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
[submodule "packages/foundry/lib/yield-utils-v2"]
path = packages/foundry/lib/yield-utils-v2
url = https://github.com/yieldprotocol/yield-utils-v2
[submodule "packages/foundry/lib/solmate"]
path = packages/foundry/lib/solmate
url = https://github.com/transmissions11/solmate
176 changes: 176 additions & 0 deletions packages/foundry/contracts/oracles/opyn/ZenBullOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.13;

import "@yield-protocol/utils-v2/contracts/cast/CastBytes32Bytes6.sol";
import {wadPow, wadDiv, wadMul} from "solmate/utils/SignedWadMath.sol";
import "@yield-protocol/utils-v2/contracts/token/IERC20.sol";
import "../../interfaces/IOracle.sol";
import {ICrabStrategy} from "./CrabOracle.sol";
import {IUniswapV3PoolState} from "../uniswap/uniswapv0.8/pool/IUniswapV3PoolState.sol";
import "forge-std/src/console.sol";

error ZenBullOracleUnsupportedAsset();

interface IZenBullStrategy {
/**
* @notice return the internal accounting of the bull strategy's crab balance
* @return crab token amount hold by the bull strategy
*/
function getCrabBalance() external view returns (uint256);

/**
* @notice get crab vault debt and collateral details
* @return vault eth collateral, vault wPowerPerp debt
*/
function getCrabVaultDetails() external view returns (uint256, uint256);

function totalSupply() external view returns (uint256);
}

/// @notice Returns price of zen bull token in USDC & vice versa
/// @dev Based on calculations provided by Opyn team https://gist.github.com/iamsahu/91428eb2029f4a78eabbe26ed7490087
contract ZenBullOracle is IOracle {
using CastBytes32Bytes6 for bytes32;

ICrabStrategy public immutable crabStrategy;
IZenBullStrategy public immutable zenBullStrategy;
IUniswapV3PoolState public immutable osqthWethPool;
IUniswapV3PoolState public immutable wethUsdcPool;
IERC20 public immutable eulerDToken;
IERC20 public immutable eulerEToken;
bytes6 public immutable usdcId;
bytes6 public immutable zenBullId;

event SourceSet(
ICrabStrategy crabStrategy,
IZenBullStrategy zenBullStrategy,
IUniswapV3PoolState osqthWethPool,
IUniswapV3PoolState wethUsdcPool,
IERC20 eulerDToken,
IERC20 eulerEToken
);

constructor(
ICrabStrategy crabStrategy_,
IZenBullStrategy zenBullStrategy_,
IUniswapV3PoolState osqthWethPool_,
IUniswapV3PoolState wethUsdcPool_,
IERC20 eulerDToken_,
IERC20 eulerEToken_,
bytes6 usdcId_,
bytes6 zenBullId_
) {
crabStrategy = crabStrategy_;
zenBullStrategy = zenBullStrategy_;
osqthWethPool = osqthWethPool_;
wethUsdcPool = wethUsdcPool_;
eulerDToken = eulerDToken_;
eulerEToken = eulerEToken_;
usdcId = usdcId_;
zenBullId = zenBullId_;
emit SourceSet(
crabStrategy_,
zenBullStrategy_,
osqthWethPool_,
wethUsdcPool_,
eulerDToken_,
eulerEToken_
);
}

/**
* @notice Retrieve the value of the amount at the latest oracle price.
* Only `zenBullId` and `usdcId` are accepted as asset identifiers.
*/
function peek(
bytes32 base,
bytes32 quote,
uint256 baseAmount
)
external
view
virtual
override
returns (uint256 quoteAmount, uint256 updateTime)
{
return _peek(base.b6(), quote.b6(), baseAmount);
}

/**
* @notice Retrieve the value of the amount at the latest oracle price. Same as `peek` for this oracle.
* Only `zenBullId` and `usdcId` are accepted as asset identifiers.
*/
function get(
bytes32 base,
bytes32 quote,
uint256 baseAmount
)
external
virtual
override
returns (uint256 quoteAmount, uint256 updateTime)
{
return _peek(base.b6(), quote.b6(), baseAmount);
}

/**
* @notice Retrieve the value of the amount at the latest oracle price.
*/
function _peek(
bytes6 base,
bytes6 quote,
uint256 baseAmount
) private view returns (uint256 quoteAmount, uint256 updateTime) {
if (base == zenBullId && quote == usdcId) {
quoteAmount = (_getZenBullPrice() * baseAmount) / 1e18;
} else if (base == usdcId && quote == zenBullId) {
quoteAmount = (baseAmount * 1e18) / _getZenBullPrice();
} else {
revert ZenBullOracleUnsupportedAsset();
}
updateTime = block.timestamp;
iamsahu marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @notice Calculates the price of one zen bull token in USDC
*/
function _getZenBullPrice() private view returns (uint256) {
uint256 bullUSDCDebtBalance = eulerDToken.balanceOf(
address(zenBullStrategy)
);
uint256 bullWethCollateralBalance = eulerEToken.balanceOf(
address(zenBullStrategy)
);
uint256 bullCrabBalance = zenBullStrategy.getCrabBalance();
(uint256 crabEthBalance, uint256 craboSqthBalance) = zenBullStrategy
.getCrabVaultDetails();
uint256 crabTotalSupply = crabStrategy.totalSupply();
uint256 bullTotalSupply = zenBullStrategy.totalSupply();
(, int24 tick, , , , , ) = osqthWethPool.slot0();
int256 osqthWethPrice = wadDiv(
1e18,
wadPow(10001e14, int256(tick) * 1e18)
);
iamsahu marked this conversation as resolved.
Show resolved Hide resolved
(, tick, , , , , ) = wethUsdcPool.slot0();
int256 wethUsdcPrice = wadDiv(
1e18,
wadPow(10001e14, int256(tick) * 1e18)
);

int256 crabUsdcValue = wadMul(int256(crabEthBalance), wethUsdcPrice) -
iamsahu marked this conversation as resolved.
Show resolved Hide resolved
wadMul(
int256(craboSqthBalance),
wadMul(osqthWethPrice, wethUsdcPrice)
);

int256 crabUsdcPrice = wadDiv(crabUsdcValue, int256(crabTotalSupply));
int256 bullUsdcValue = wadMul(int256(bullCrabBalance), crabUsdcPrice) +
wadMul(int256(bullWethCollateralBalance), wethUsdcPrice) -
int256(bullUSDCDebtBalance);

uint256 bullUsdcPrice = uint256(
wadDiv(bullUsdcValue, int256(bullTotalSupply))
);
return bullUsdcPrice;
}
}
4 changes: 2 additions & 2 deletions packages/foundry/contracts/test/oracles/CrabOracleTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ pragma solidity >=0.8.13;

import "forge-std/src/Test.sol";
import "@yield-protocol/utils-v2/contracts/access/AccessControl.sol";
import "../../oracles/crab/CrabOracle.sol";
import {ICrabStrategy} from "../../oracles/crab/CrabOracle.sol";
import "../../oracles/opyn/CrabOracle.sol";
import {ICrabStrategy} from "../../oracles/opyn/CrabOracle.sol";
import "../../oracles/uniswap/UniswapV3Oracle.sol";
import "../utils/TestConstants.sol";

Expand Down
59 changes: 59 additions & 0 deletions packages/foundry/contracts/test/oracles/ZenBullOracleTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: BUSL-1.1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please deploy on a fork, and use that to code a test harness that verifies that the ZenBullPrice obtained is within an order of magnitude of something. If ZenBull is expected to stay within 0.1 ETH and 10 ETH most of the time, that would be a good range to test against.

pragma solidity >=0.8.13;

import "forge-std/src/Test.sol";
import "@yield-protocol/utils-v2/contracts/access/AccessControl.sol";
import "../../oracles/opyn/ZenBullOracle.sol";
import {IZenBullStrategy} from "../../oracles/opyn/ZenBullOracle.sol";
import "../../oracles/uniswap/UniswapV3Oracle.sol";
import "../utils/TestConstants.sol";
import {wadPow, wadDiv} from "solmate/utils/SignedWadMath.sol";

contract ZenBullOracleTest is Test, TestConstants {
ZenBullOracle public zenBullOracle;

ICrabStrategy crabStrategy_ =
ICrabStrategy(0x3B960E47784150F5a63777201ee2B15253D713e8);
IZenBullStrategy zenBullStrategy_ =
IZenBullStrategy(0xb46Fb07b0c80DBC3F97cae3BFe168AcaD46dF507);
IUniswapV3PoolState osqthWethPool_ =
IUniswapV3PoolState(0x82c427AdFDf2d245Ec51D8046b41c4ee87F0d29C);
IUniswapV3PoolState wethUsdcPool_ =
IUniswapV3PoolState(0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8);
IERC20 eulerDToken_ = IERC20(0x84721A3dB22EB852233AEAE74f9bC8477F8bcc42);
IERC20 eulerEToken_ = IERC20(0x1b808F49ADD4b8C6b5117d9681cF7312Fcf0dC1D);

function setUp() public {
vm.createSelectFork(MAINNET, 16468440);
zenBullOracle = new ZenBullOracle(
crabStrategy_,
zenBullStrategy_,
osqthWethPool_,
wethUsdcPool_,
eulerDToken_,
eulerEToken_,
USDC,
ZENBULL
);
}

function testPeek() public {
(uint256 amount, ) = zenBullOracle.peek(
bytes32(ZENBULL),
bytes32(USDC),
1e18
);
emit log_named_uint("Zenbull in USDC Value", amount);
assertEq(amount, 3475041114);
}

function testPeekReversed() public {
(uint256 amount, ) = zenBullOracle.peek(
bytes32(USDC),
bytes32(ZENBULL),
1e6
);
emit log_named_uint("USDC in Zenbull Value", amount);
assertEq(amount, 287766379503042);
}
}
1 change: 1 addition & 0 deletions packages/foundry/contracts/test/utils/TestConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ contract TestConstants {
bytes6 public constant FRAX = 0x313800000000;
bytes6 public constant OSQTH = 0x313900000000;
bytes6 public constant CRAB = 0x323900000000;
bytes6 public constant ZENBULL = 0x324000000000;

bytes6 public constant FYETH2206 = bytes6("0006");
bytes6 public constant FYDAI2206 = bytes6("0106");
Expand Down
1 change: 1 addition & 0 deletions packages/foundry/lib/solmate
Submodule solmate added at 399889