-
Notifications
You must be signed in to change notification settings - Fork 22
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
ETH-USD price fallback #393
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good in general. I left some questions inline.
function fetchPrice() public returns (uint256, bool) { | ||
// If branch is live and the primary oracle setup has been working, try to use it | ||
if (priceSource == PriceSource.primary) { | ||
return _fetchPrice();} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can pass forge fmt
here (I was confused until I saw the ending }
was at the end of the line).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not done yet, right? No big deal, we can merge and do forge fmt
later.
return _fetchPrice();} | ||
|
||
// Otherwise if branch is shut down and already using the lastGoodPrice, continue with it | ||
if (priceSource == PriceSource.lastGoodPrice) {return (lastGoodPrice, false);} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far is I can see, this one will never have ETHUSDxCanonical
. So I guess we can get rid of the if
clause (and adapt the comment above)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can even have an assert
for testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not done yet, right?
@@ -70,11 +83,11 @@ abstract contract MainnetPriceFeedBase is IPriceFeed, Ownable { | |||
return (scaledPrice, oracleIsDown); | |||
} | |||
|
|||
function _disableFeedAndShutDown(address _failedOracleAddr) internal returns (uint256) { | |||
function _shutDownAndSwitchToLastGoodPrice(address _failedOracleAddr) internal returns (uint256) { | |||
// Shut down the branch | |||
borrowerOperations.shutdownFromOracleFailure(_failedOracleAddr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s not related to this PR, but I wonder if we could emit the ShutDownFromOracleFailure
here, so we don’t need to pass _failedOracleAddr
as param to BorrowerOperations
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, makes sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not done yet, right?
} | ||
|
||
// Otherwise if branch is shut down and already using the lastGoodPrice, continue with it | ||
if (priceSource == PriceSource.lastGoodPrice) {return (lastGoodPrice, false);} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can get rid of the if
, as there’s no other possibility, and note it in a comment.
It doesn’t matter too much, but right now it feels like the execution flow could not enter in any of the if
clauses and return (0, false)
(which is not the case, fortunately).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not done yet, right?
// Only called if the primary LST oracle has failed, branch has shut down, | ||
// and we've switched to using: ETH-USD * canonical_rate. | ||
function _fetchPriceETHUSDxCanonical(uint256 _ethUsdPrice) internal returns (uint256) { | ||
assert(priceSource == PriceSource.ETHUSDxCanonical); | ||
// Get the ETH_per_LST canonical rate directly from the LST contract | ||
// TODO: Should we also shutdown if the call to the canonical rate reverts, or returns 0? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so, we should fallback to lastGoodPrice
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not done yet, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can remove the TODO?
|
||
return (lstUsdPrice, false); | ||
return (lstUsdCanonicalPrice); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we don’t parenthesis anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not done yet, right?
// Only called if the primary LST oracle has failed, branch has shut down, | ||
// and we've switched to using: ETH-USD * canonical_rate. | ||
function _fetchPriceETHUSDxCanonical(uint256 _ethUsdPrice) internal returns (uint256) { | ||
assert(priceSource == PriceSource.ETHUSDxCanonical); | ||
// Get the ETH_per_LST canonical rate directly from the LST contract | ||
// TODO: Should we also shutdown if the call to the canonical rate reverts, or returns 0? | ||
uint256 lstEthRate = _getCanonicalRate(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the case of wstETH
this is not LST -> ETH, but LST -> stETH. I guess we are assuming 1 stETH = 1 ETH
, but historically there have been deviations.
https://www.coingecko.com/en/coins/lido-staked-ether/eth
Maybe we should at least note it as a comment or in the README.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's the best we can do when the STETH-USD source has failed. Sure, I'll make a note in the Readme.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not done yet, right?
|
||
// Take the minimum of (market, canonical) in order to mitigate against upward market price manipulation | ||
uint256 lstUsdPrice = LiquityMath._min(lstUsdMarketPrice, lstUsdCanonicalPrice); | ||
uint256 lstUsdCanonicalPrice = _ethUsdPrice * lstEthRate / 1e18; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would still make sense to use the min against lastGoodPrice
.
If the branch was shutdown, it may be due to a bug in the LST, so the canonical price may be wrong.
This way we would at least make sure we protect against the worst case of urgent redemptions not being profitable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, yes this makes sense. It could still be that market_price < min(lastGoodPrice, canonical_price)
(depends on price movement) - but it increases the chances that we can fully clear the bad debt. Good idea!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not done yet, right?
@@ -158,20 +156,14 @@ contract TestDeployer is MetadataDeployment { | |||
address ETHOracle; | |||
address STETHOracle; | |||
address RETHOracle; | |||
address ETHXOracle; | |||
address OSETHOracle; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😢
9cdf066
to
bbf0e6f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
// Only called if the primary LST oracle has failed, branch has shut down, | ||
// and we've switched to using: ETH-USD * canonical_rate. | ||
function _fetchPriceETHUSDxCanonical(uint256 _ethUsdPrice) internal returns (uint256) { | ||
assert(priceSource == PriceSource.ETHUSDxCanonical); | ||
// Get the ETH_per_LST canonical rate directly from the LST contract | ||
// TODO: Should we also shutdown if the call to the canonical rate reverts, or returns 0? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can remove the TODO?
uint256 lstUsdMarketPrice = ethUsdPrice * rEthEthPrice / 1e18; | ||
|
||
// Calculate the canonical LST-USD price: USD_per_LST = USD_per_ETH * ETH_per_LST | ||
// Calculate the canonical LST-USD price: USD_per_RETH = USD_per_ETH * ETH_per_RETH | ||
// TODO: Should we also shutdown if the call to the canonical rate reverts, or returns 0? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are doing this above, right? So we can remove the TODO
rEthUsdPrice = LiquityMath._max(rEthUsdMarketPrice, rEthUsdCanonicalPrice); | ||
} else { | ||
// Take the minimum of (market, canonical) in order to mitigate against upward market price manipulation. | ||
// Assumes a deviation between market <> canonical of >2% represents a legitemate market price difference. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There’s a typo here, right? (legitimate)
uint256 max = _rEthUsdCanonicalPrice * (DECIMAL_PRECISION + _2_PERCENT) / 1e18; | ||
uint256 min = _rEthUsdCanonicalPrice * (DECIMAL_PRECISION - _2_PERCENT) / 1e18; | ||
|
||
return _rEthUsdMarketPrice >= min && _rEthUsdCanonicalPrice <= max; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn’t both be _rEthUsdMarketPrice
here?
return fetchPrice(); | ||
} | ||
|
||
function _fetchPricePrimary(bool _isRedemption) internal override returns (uint256, bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_isRedemption
isn’t used here? Doesn’t it throw a warning?
6a05645
to
bdb6492
Compare
Addresses the issue of a stale price during shutdown after oracle failure. For WSTETH and RETH branches, we now do our best to use a live price based on ETH-USD x canonical_rate if the primary price calculation has failed.
https://github.com/liquity/bold?tab=readme-ov-file#4---oracle-failure-and-urgent-redemptions-with-the-frozen-last-good-price
Fixes #474