-
Notifications
You must be signed in to change notification settings - Fork 85
WOETH yield buckets #2447
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
WOETH yield buckets #2447
Conversation
…oken can affect the exchange ratio of WOETH to OETH
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## sparrowDom/woeth_hack_proof #2447 +/- ##
==============================================================
Coverage ? 47.22%
==============================================================
Files ? 100
Lines ? 4898
Branches ? 1294
==============================================================
Hits ? 2313
Misses ? 2581
Partials ? 4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
contracts/contracts/token/WOETH.sol
Outdated
uint256 amount | ||
) internal override { | ||
super._transfer(sender, recipient, amount); | ||
increaseYieldLimit(); |
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'm not sure if this is a problem, but I have vague dread about updating this in the middle of a transfer.
I don't think that an external integration would expect the value of WOETH to change before and after a transfer. Might cause exploitable bugs in other people's code.
In the asset drip implementation, updating things was okay, since any changes only took effect on the next block, and this everyone using the system had a consistent exchange rate for the entire block. I don't know that we have to have one exchange rate per block, but changing during a transfer feels sketch.
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.
Hmm that's a good point. One might expect the following:
woeth.balanceOf(micah) // 5.0 units
woeth.connect(micah).transfer(somewhere, 3 units)
woeth.balanceOf(micah) // can be more than 2.0 units because of the `increaseYieldLimit`
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.
remove here: 4c4a2cb
contracts/contracts/token/WOETH.sol
Outdated
* are in sync with WOETH. Resulting in no economical gain from wrapping OETH to WOETH | ||
* and back to try to game the yield distribution. | ||
*/ | ||
uint256 public constant MAX_YIELD_INCREASE = 25e14; // 0.25% |
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 the blessed summer children who have never lived hyper-inflation.
But if the underlying currency is inflating faster than this limit allows, then our users will be in deep trouble. I think we just want to head off donations that damage lending platforms, rather than bet our user funds on stable economies for the next fifty years.
So I'd run this limit something more like 3-5%.
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.
One really cool thing about this code's approach is that in the good case, with high limits, and non-attack yield amounts, even if the limit is not reset, it continues perfectly tracking OETH's value for quite a while after the end time is over.
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.
An attacker can basically get double the yield that this number says, by donating when a period has ended. That's probably okay, but means that with this approach, we should set the yield to half of what we want.
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.
That is a very good point.
*/ | ||
function increaseYieldLimit() public { | ||
// not yet time to increase the limit | ||
if (block.timestamp < cptLimitEndTime) { |
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 am wondering if there should be a way where we can cleanly allow pushing forward the end time with every action, rather than requiring one limit to end before the next begins. This would be much nicer user facing behavior. We do have to do some math to see how much of the partially completed phase to carry over. Or maybe this is unnecessary complication.
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.
The current approach around limits is:
- very simple
- correctly handles the normal case use of the protocol
- correctly blocks donation attacks
But it is also feels like it has a lot weirdness when the limits are hit.
- 2x the limit is possible at the end of the period
- someone can get doubled yielded at the end of a period
- ratios can change mid block, separate from OETH ratios changing
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.
"someone can get doubled yielded at the end of a period"
Are you thinking that a person would be holding OETH, and knowing WOETH needs time to catch up, they would wrap their OETH just after a fat rebase?
"ratios can change mid block, separate from OETH ratios changing"
Do you think this can have negative consequences?
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.
So this is talking about behavior when the limits will be hit, and the time is past the end:
- User knows/causes a BIG rebase to be about to occur.
- They have/get a large OETH position.
- The call rebase(), collecting the OETH yield
- They then deposit into WEOTH, at a privileged exchange rate.
- The end of the deposit calls increaseYield. Limits are raised again
- User withdraws, collecting OETH at the new higher rate. No one else was able to do this.
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'm actually concerned that is a full on attack, not just a yield steal. Minting funds at the wrong rate could be bad. I'm not sure it is, so I'm going to write a test.
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 it's just a yield steal, since the someone can't mint at a lower rate than the existing users got in at.
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.
"ratios can change mid block, separate from OETH ratios changing"
Do you think this can have negative consequences?
Yes, it allows someone to selectively take actions between these two. Presumably yield stealing.
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.
Ouch!
Confirmed with tests that this is a flashloanable enabled yield steal in the current implementation.
🟥 But it's actually much worse. Stuff does get really out of wack when things are at over the rate limit, and interacting users can actually end up with less funds than they started with depending on how they interact. It gets deeply squirrelly.
@@ -65,7 +85,7 @@ contract WOETH is ERC4626, Governable, Initializable { | |||
* WOETH has already seen transactions. But it is rather annoying in unit test | |||
* environment. | |||
*/ | |||
oethCreditsHighres = _getOETHCredits(); |
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.
Still need the oethCreditsHighres = _getOETHCredits();
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.
uff thanks nice catch: b68269d
closing in favour of this one: #2453 |
This is the third attempt at the implementation of limiting the yield increase and, consequen the exchange rate between WOETH and OETH. The other 2:
This approach:
increaseYieldLimit()
To be completed before internal review begins:
Internal review: