-
Notifications
You must be signed in to change notification settings - Fork 1
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
Intrinsic arbitrage from price discrepancy #62
Comments
0xleastwood marked the issue as primary issue |
0xleastwood marked the issue as selected for report |
elmutt (sponsor) confirmed |
Recommended Mitigation StepsWe want that a deposit not diminish the value of previous deposits. That is, withdrawing Let Withdrawing For small deposits and withdrawals the price functions are approximately linear and we can write e.g. Solving for The difference from the current implementation is that instead of the true prices Since we know the true prices up to within certain bounds, we can minimise so if We minimise within the bounds, but we of course want the bounds to be as tight as possible, so that this minimum is as high as possible. The oracle prices provide us with good bounds, namely There are a few ways to further improve the bounds. During the deposit we learn If the trading losses are significant it may be necessary to take these into account even for the bounds from the oracle prices, such that both upper and lower bounds are slightly reduced. |
@0xleastwood I'm really sorry to do this at this stage, but did you have the chance to go through these scenarios? Seems there are lot of suppositions, some of which the same warden is confusingly invalidating in other discussions. I think examples 1a and 1b have invalid assumptions ("the contract holds 100 safEth and 0 vAfEth, but that the ratio has now been changed to 0"). In 2a and 2b, what is "the contract thinks all prices are.."? How is the contract thinking prices? It seems this has nothing to do with the stated impact. The chainlink response is used to price vAfEth, but the core here is a discrepancy of the ratio with the underlying assets (which again confusingly the author is trying to invalidate in other issues). Furthermore the deposit/withdraw cycle can't be executed without exposure to the underlying assets due to the locking mechanism. |
I'll look into these, I do believe it is possible to deposit and withdraw atomically as long as there is an unlocked amount of tokens in the votium strategy contract. That would be the extent at which a withdrawal could be made "instantly". |
I do agree that example 1a and 1b are somewhat infeasible because the protocol team has already stated that such a ratio would not exist in the first place. However, there is validity in the other examples. |
Thanks for the excellent analysis @d3e4, this one really got us thinking. After extensive analysis we came to the following conclusions:
These are a little bit concerning We believe this is an acceptable risk because:
or
or
or
If we assume safEthPrice is constant (in terms of eth it goes up very slowly) so we can pretty much ignore it (replace it with a constant) or
Assigning values generated when ratios were roughly equal to everything except vStratPrice and plotting it looks like: ![]() We think this graph helps show that if ratios are equal it is hard to mess things up with a bad chainlink price. We understand ratios will often not be equal but we wanted to see the base case of ratios being equal Based on this analysis we think a 2% chainlink variance is an acceptable risk, even when the ratios are far apart. However its possible we missed something and if anyone can demonstrate in a unit test that its a bigger problem we would love to see it! |
Also wanted to point out the ratios can easily change if cvx price changes, so it it a real concern. We initially didnt think about it right and assumed ratios would be more stable |
Lines of code
https://github.com/code-423n4/2023-09-asymmetry/blob/6b4867491350f8327d0ac4f496f263642cf3c1be/contracts/AfEth.sol#L148-L169
Vulnerability details
Impact
The up to 2 % price discrepancy from Chainlink creates an intrinsic arbitrage. Especially, it makes withdrawals worth more than deposits in the sense that one can immediately withdraw more than just deposited.
Proof of Concept
When depositing ETH into AfEth, the ETH is split according to
ratio
and sold for safEth and vAfEth. The received share of afEth is then determined by the value in ETH of the resulting amounts of safEth and vAfEth. Note that there are two prices involved here: the true price at which ETH is traded for safEth and vAfEth (insellCvx()
andbuyCvx()
), and the estimated value in ETH that safEth and vAfEth is considered to have (ISafEth.approxPrice()
andVotiumStrategy.price()
). These are not necessarily the same.If the ratio by which the deposited ETH is split is not the same as the ratio of the true values of the underlying assets, this implies that a deposit implicitly makes a trade between safEth and vAfEth according to the estimated price which may thus differ from the true price obtained when withdrawing. This presents an arbitrage opportunity.
Note that if all prices were the same it would not matter if safEth is "traded" for vAfEth within a deposit as the trade then makes no change in the total value deposited.
The conditions for this issue is thus that
VotiumStrategy.price()
is different from the price obtained bysellCvx()
andbuyCvx()
, and that the deposit ratio is not the same as the withdrawal ratio.VotiumStrategy.price()
in particular is based on a Chainlink oracle with a 2 % deviation threshold, which means that the true price is allowed to deviate up to 2 %, within 24 hours, from the reported price.ISafEth.approxPrice()
may perhaps be similarly inaccurate (I have not looked into this).The ratio can skew in this way for two reasons. One is when the
ratio
is different from the ratio of the underlying balances, such as whenratio
is changed. Deposits are made according toratio
but withdrawals are made proportionally from the extant balances. In this case the implicit trade between safEth and vAfEth can happen in either direction, either beneficial or detrimental to the depositor (if there is a price discrepancy).The other is caused by the price discrepancy itself when depositing. In this case it is always beneficial to the depositor (and detrimental to the holders).
Example 1a - reconverging ratio, vAfEth is actually more expensive
Suppose the contract holds 100 safEth and 0 vAfEth, but that the
ratio
has now been changed to 0. Further suppose that the contract thinks all prices are 1, but that 100 ETH actually trades for 102 vAfEth.Then depositing 100 ETH will convert it to 102 vAfEth, which will be valued as 102 ETH. This mints 102 afEth.
The balances are now 100 safEth and 102 vAfEth and the total supply is 202 afEth.
Withdrawing 102 afEth converts 102/202 of the underlying, i.e. 50.495 safEth and 51.505 vAfEth, into 50.495 + 51.505/1.02 = 100.99 ETH.
Example 1b - reconverging ratio, vAfEth is actually cheaper
Suppose the contract holds 100 safEth and 0 vAfEth, but that the
ratio
has now been changed to 0. Further suppose that the contract thinks all prices are 1, but that 100 ETH actually trades for 98 vAfEth.Then depositing 100 ETH will convert it to 98 vAfEth, which will be valued as 98 ETH. This mints 98 afEth.
The balances are now 100 safEth and 98 vAfEth and the total supply is 198 afEth.
Withdrawing 98 afEth converts 98/198 of the underlying, i.e. 49.495 safEth and 48.505 vAfEth, into 49.495 + 48.505/0.98 = 98.99 ETH.
Example 2a - stable ratio, vAfEth is actually more expensive
Suppose the contract holds 50 safEth and 50 vAfEth and that the
ratio
is 0.5. Further suppose that the contract thinks all prices are 1 but that 50 ETH actually trades for 51 vAfEth.Then depositing 100 ETH will convert 50 ETH to 50 safEth and 50 ETH to 51 vAfEth, which will be valued as 101 ETH. This mints 101 afEth.
The balances are now 100 safEth and 101 vAfEth and the total supply is 201 afEth.
Withdrawing 101 afEth converts 101/201 of the underlying, i.e. 50.249 safEth and 50.751 vAfEth, into 50.249 + 50.751/1.02 = 100.005 ETH.
Example 2b - stable ratio, vAfEth is actually cheaper
Suppose the contract holds 50 safEth and 50 vAfEth and that the
ratio
is 0.5. Further suppose that the contract thinks all prices are 1 but that 50 ETH actually trades for 49 vAfEth.Then depositing 100 ETH will convert 50 ETH to 50 safEth and 50 ETH to 49 vAfEth, which will be valued as 99 ETH. This mints 99 afEth.
The balances are now 100 safEth and 99 vAfEth and the total supply is 199 afEth.
Withdrawing 99 afEth converts 99/199 of the underlying, i.e. 49.749 safEth and 49.251 vAfEth, into 49.749 + 49.251/0.98 = 100.005 ETH.
Thus one can make a profit by depositing and immediately withdrawing. Immediate withdrawals are possible at the moment locks expire (and before they have been relocked), but it may be enough to just immediately request a withdrawal if the true price is the same (or better) when eventually withdrawn.
The price discrepancy will appear whenever there are price fluctuations of up to 2 % within 24 hours, which seems quite likely.
Regarding the case where the underlying is reconverging after a change of
ratio
it is worth noting that convergence is quite slow. Several times the entire balances must be traded before the new ratio is approached.Recommended Mitigation Steps
I would have to think more about this.
Assessed type
Math
The text was updated successfully, but these errors were encountered: