-
Notifications
You must be signed in to change notification settings - Fork 79
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: Ignore donations #2106
base: master
Are you sure you want to change the base?
WOETH: Ignore donations #2106
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2106 +/- ##
==========================================
+ Coverage 61.86% 62.76% +0.90%
==========================================
Files 66 66
Lines 3322 3373 +51
Branches 863 657 -206
==========================================
+ Hits 2055 2117 +62
+ Misses 1264 1253 -11
Partials 3 3 ☔ View full report in Codecov by Sentry. |
RequirementsWhat is the PR trying to do? Is this the right thing? Are there bugs in the requirements? Easy ChecksAuthentication
Ethereum
Cryptographic code
Gas problems
Black magic
Overflow
Proxy
Events
Medium ChecksRounding
Dependencies
External calls
Tests
Deploy
ThinkingLogicAre there bugs in the logic?
Deployment ConsiderationsAre there things that must be done on deploy, or in the wider ecosystem for this code to work. Are they done? Internal State
For all 3 questions above it is important that: The internal credits stored in WOETH and stored in OETH (for WOETH contract) should always match unless someone sends extra OETH to the WOETH contract manually. Does this code do that? AttackWhat could the impacts of code failure in this code be. What conditions could cause this code to fail if they were not true. Does this code successfully block all attacks. FlavorCould this code be simpler? |
contracts/contracts/token/WOETH.sol
Outdated
using StableMath for uint256; | ||
// doesn't need to be public, but convenient to be able to confirm the state on the mainnet | ||
uint256 public oethCreditsHighres; | ||
bool _oethCreditsInitialized; |
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.
Missing an explicit visibility.
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.
thanks nice catch, corrected
The core attack we are trying to stop is someone sending the OETH to the wOETH contract, causing the value of wOETH in OETH terms to go suddenly up. It looks like totalAssets uses the amount of OETH held by the contract as one of two multipliers. totalAssets is in turn used to calculate the exchange ratio. If someone donates to the contract, one of these two multipliers goes up, and the donation has perfectly succeeded in increasing the value of each wOETH. This attack does not appear to be blocked at all? Or am I missing something? |
It also feels really scary that were are minting and burning using old ratios. That doesn't cause rektness? |
@DanielVF not completely sure what you mean. So the logic is that:
|
I misread the code and got the credits per token and the credits switched. Nevermind everything I said - I'll have to stare at it more! |
function totalAssets() public view virtual override returns (uint256) { | ||
(, uint256 creditsPerTokenHighres, ) = OETH(asset()) | ||
.creditsBalanceOfHighres(address(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.
If we used in credits
here as well from the call from creditsBalanceOfHighres
, we would have ever piece of data already loaded inside this function to check that our actual assets was equal or greater than our expected assets.
May or may not be such a great idea though to revert in a view function.
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 would be a good check that would detect possible mathematical irregularities (perhaps due to rounding). Though as you point out it would be weird to revert in a view function.
Monitoring sounds like a good place for it though. Have Grafana regularly query both credits values and check that the ones in OETH.sol are greater or equal to the ones in WOETH.sol.
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.
Invariants I can think of:
|
WOETH: Ignore donations
🚨 Report Summary
For more details view the full report in OpenZeppelin Code Inspector |
if (_oethCreditsInitialized) { | ||
require(false, "Initialize2 already called"); | ||
} |
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.
Why not just require(!_oethCreditsInitialized, "Initialize2 already called");
?
* @param oethAmount Amount of OETH to be converted to OETH credits | ||
* @return amount of OETH credits the OETH amount corresponds to | ||
*/ | ||
function _creditsPerAsset(uint256 oethAmount) |
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.
nit: This function name confuses me a bit. When I see per asset
, I assume it's gonna return credits for 1 OETH. But it takes in a oethAmount
and multiplies the credits by it. So, it sounds more like creditsForAssetAmount
than creditsPerAsset
require( | ||
assets <= maxDeposit(receiver), | ||
"ERC4626: deposit more then max" | ||
); | ||
|
||
address caller = _msgSender(); | ||
uint256 shares = previewDeposit(assets); | ||
|
||
// if _asset is ERC777, transferFrom can call reenter BEFORE the transfer happens through | ||
// the tokensToSend hook, so we need to transfer before we mint to keep the invariants. | ||
SafeERC20.safeTransferFrom( | ||
IERC20(asset()), | ||
caller, | ||
address(this), | ||
assets | ||
); | ||
_mint(receiver, shares); | ||
oethCreditsHighres += _creditsPerAsset(assets); | ||
|
||
emit Deposit(caller, receiver, assets, shares); | ||
|
||
return shares; |
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.
Could this be simplified into just (to avoid code-duplication):
shares = super.deposit(assets, receiver);
oethCreditsHighres += _creditsPerAsset(assets);
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.
Same with other overridden functions as well
_burn(owner, shares); | ||
oethCreditsHighres -= _creditsPerAsset(assets); |
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.
Haven't tested it yet but I wonder if we should compute the credits before burn? Let me see if it makes any difference or if it's breakable
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 tried running some tests. Change in order didn't affect the numbers at all
} | ||
|
||
/** @dev See {IERC4262-mint} */ | ||
function mint(uint256 shares, address receiver) |
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.
You could save on some code complexity calling deposit from the mint function to prevent having most of the same code twice.
A donation to increase the OETH price remains possible via the OETH contract but will require significant funds to increase the entire OETH vault value. |
Code Change Checklist
To be completed before internal review begins:
Internal review: