code-423n4 / 2023-05-xeth-findings

0 stars 0 forks source link

Spot balances of Curve pool can be manipulated to force or block rebalances #25

Open code423n4 opened 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/code-423n4/2023-05-xeth/blob/main/src/AMO2.sol#L204-L205

Vulnerability details

Spot balances of Curve pool can be manipulated to force or block rebalances

Rebalances in the AMO contract are validated by calculating the percentage of xETH present in the Curve pool. These balances can be manipulated to either force or block a rebalance operation.

Impact

Rebalance operations in the AMO contract are first validated against a set of conditions before allowed to proceed. Both rebalance up and down actions check that the current percentage of xETH in the Curve pool is outside the configured thresholds, this is implemented in the preRebalanceCheck() function:

https://github.com/code-423n4/2023-05-xeth/blob/main/src/AMO2.sol#L203-L225

203:     function preRebalanceCheck() internal view returns (bool isRebalanceUp) {
204:         uint256 stETHBal = curvePool.balances(stETHIndex);
205:         uint256 xETHBal = curvePool.balances(xETHIndex);
206: 
207:         /// @notice if either token balance is 0, the pool shall not be rebalanced
208:         if (stETHBal == 0 || xETHBal == 0) revert ZeroBalancePool();
209: 
210:         uint256 xEthPct = (xETHBal * BASE_UNIT) / (stETHBal + xETHBal);
211: 
212:         /// @notice if the ratio is above the upper threshold, rebalanceUp() will be called
213:         if (xEthPct > REBALANCE_UP_THRESHOLD) {
214:             isRebalanceUp = true;
215:         }
216:         /// @notice if the ratio is below the lower threshold, rebalanceDown() will be called
217:         /// @notice possible gas optimization here.
218:         else if (xEthPct < REBALANCE_DOWN_THRESHOLD) {
219:             isRebalanceUp = false;
220:         }
221:         /// @notice if the ratio is within the thresholds, the pool shall not be rebalanced
222:         else {
223:             revert RebalanceNotRequired();
224:         }
225:     }

As we can see in the previous example, the function uses spot balances of the Curve pool to calculate the percentage. These balances can be manipulated to either force a rebalance operation or block a rebalance operation.

The rebalance operation can be forced by temporarily swapping tokens such that xEthPct > REBALANCE_UP_THRESHOLD to allow a rebalance up action or xEthPct < REBALANCE_DOWN_THRESHOLD to allow a rebalance down action.

In a similar way, a malicious actor can block the operations by shifting the balance so that they fall within the desired boundaries. Imagine the current balance is such that xEthPct > REBALANCE_UP_THRESHOLD is true, a bad actor can front-run the transaction and temporarily swap tokens so that the condition no longer holds, effectively blocking the process as the front-runned transaction will revert.

Proof of concept

In the case of the block scenario, a griefer can basically sandwich the transaction to cause preRebalanceCheck() to revert. Let's say this is a rebalance up action:

  1. Assume xEthPct > REBALANCE_UP_THRESHOLD is true and defender sends transaction to call rebalanceUp().
  2. Griefer front-runs transaction and sandwiches it.
  3. First part of the sandwich exchanges xETH for stETH, or removes liquidity as xETH tokens. This will lower the percentage of xETH in the pool so that xEthPct > REBALANCE_UP_THRESHOLD is no longer true.
  4. Defender transaction now is reverted.
  5. Second part of the sandwich swaps back stETH for xETH, or re-adds liquidity.

For the case of the forced rebalance, a rogue defender may, for example, execute the following actions to allow a rebalance down operation:

  1. Assume xEthPct < REBALANCE_DOWN_THRESHOLD is false.
  2. Defender takes a flash loan of stETH and swaps stETH for xETH in the pool, this will lower the balance of xETH in the pool, making xEthPct < REBALANCE_DOWN_THRESHOLD true.
  3. Defender calls rebalanceDown().
  4. Defender swap back xETH for stETH and repays flash loan.

Recommendation

It is difficult to provide an easy solution to this issue. In general lines, this is similar to oracle manipulation attacks, where the usual recommendation is to not rely on spot conditions and use a TWAP oracle or external feed.

As the protocol will bootstrap and mainly use the Curve pool, there may not be another good source of liquidity for xETH. However, the AMO contract can implement some kind of "time weighted" average balances or lagged indicator around the token balances that offers stronger resilience against manipulation than spot balances.

Assessed type

Other

c4-judge commented 1 year ago

kirk-baird marked the issue as primary issue

5z1punch commented 1 year ago

Just wonder what is the motivation of the attack? Especially in the second case, the attacker gets nothing but loses fees and more slippage lost.

c4-sponsor commented 1 year ago

vaporkane marked the issue as sponsor disputed

vaporkane commented 1 year ago

There is a rebalance contract quote and also a rebalance cap to limit and avoid such scenarios.

c4-sponsor commented 1 year ago

vaporkane marked the issue as disagree with severity

c4-sponsor commented 1 year ago

vaporkane requested judge review

kirk-baird commented 1 year ago

It may be possible to stop users from rebalancing the pool or cause a rebalance but this comes at a large cost to the attacker losing fees for flashloans and exchanging tokens. The warden has not explained and I do not see how an attacker can extort funds from the protocol and as such I am going to downgrade this issue to Low severity.

c4-judge commented 1 year ago

kirk-baird changed the severity to QA (Quality Assurance)

c4-judge commented 1 year ago

kirk-baird marked the issue as grade-a

c4-sponsor commented 1 year ago

vaporkane marked the issue as sponsor acknowledged

vaporkane commented 1 year ago

Acknowledged the first case, we plan on using private transactions to limit the same.

The second case, assumes that the defender is turned rogue.