code-423n4 / 2023-11-kelp-findings

13 stars 11 forks source link

Deposit can be blocked due to stETH rebasing #537

Closed c4-submissions closed 10 months ago

c4-submissions commented 11 months ago

Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L56-L58 https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L119-L144

Vulnerability details

Impact

Each asset (LST) intended for deposit must adhere to a deposit limit of 100,000e18. It's important to note that stETH, being a rebasing token, has a variable balance that can fluctuate (balance could go up and down).

Due to the nature of stETH's balance changes, the depositLimit invariant may become inaccurate. Consequently, deposits may be temporarily blocked until the stETH balance falls below the deposit limit or until the manager updates it.

Proof of Concept

stETH has two rebasing scenarios, let's delve into each of them.

Positive Rebase:

If a user deposits stETH and it's close to reaching the deposit limit. On the next deposit, he will follow these steps:

  1. He will call getAssetCurrentLimit() to determine the allowable deposit amount before reaching the asset deposit limit.
  2. Assuming the function returns the correct value and there has been no rebase since the initial deposit.
  3. The user will obtain X (the amount of tokens they can deposit).
  4. Will call depositAsset(stETH, X).
  5. However, just before the user's deposit, a rebase occurs, causing the total value of previously deposited assets to exceed the deposit limit.
  6. As a consequence, getAssetCurrentLimit() will revert due to getTotalAssetDeposits() returning a value higher than 100,000e18.
  7. His deposit, as well as any further calls to getAssetCurrentLimit(), will be blocked.

Here is a coded PoC:

Import console and StdUtils at the top of the LRTDepositPoolTest.t.sol file.

import { console } from "forge-std/Test.sol";
import { StdUtils } from "forge-std/StdUtils.sol";

Place the PoC in the LRTDepositPoolDepositAsset contract.

Can be run with:

forge test --match-contract LRTDepositPoolDepositAsset --match-test test_DepositRevertAfterStETHRebase -vvv
function test_DepositRevertAfterStETHRebase() external {
    uint256 depositAmount = 99_999 ether;

    vm.startPrank(alice);

    stETH.approve(address(lrtDepositPool), 100_000 ether);
    // Will deposit 99,999 - close to the limit
    lrtDepositPool.depositAsset(address(stETH), depositAmount);

    uint256 aliceAvailableAmountToDeposit = lrtDepositPool.getAssetCurrentLimit(address(stETH));
    console.log("Alice avaiable amount to deposit before rebase occur: ", aliceAvailableAmountToDeposit); // 1 ether

    // deposit pool balance of stETH before rebase
    uint256 depositPoolBalanceBefore = stETH.balanceOf(address(lrtDepositPool));
    console.log("Deposit pool stETH balace before rebase: ", depositPoolBalanceBefore);
    // Simulate stETH rebase
    deal(address(stETH), address(lrtDepositPool), depositAmount + 10 ether);
    // deposit pool balance of stETH after rebase
    uint256 depositPoolBalanceAfter = stETH.balanceOf(address(lrtDepositPool));
    console.log("Deposit pool stETH balace after rebase: ", depositPoolBalanceAfter);

    // Deposit will revert
    vm.expectRevert();
    lrtDepositPool.depositAsset(address(stETH), aliceAvailableAmountToDeposit);

    // further calls to getAssetCurrentLimit() will revert also
    vm.expectRevert();
    lrtDepositPool.getAssetCurrentLimit(address(stETH));
}
Logs:
Alice avaiable amount to deposit before rebase occur:  1000000000000000000
Deposit pool stETH balace before rebase:  99999000000000000000000
Deposit pool stETH balace after rebase:  100009000000000000000000

Negative Rebase

In a rare situation, the amount of stETH can decrease, called a negative rebase.

There is a scenario where the asset limit is reached, but a negative rebase will make some extra space and whoever sees this will be able to mint additional rsETH.

  1. Numerous users have made deposits, reaching the stETH deposit limit.
  2. User back-run the stETH negative rebase allowing them to deposit an additional amount until they reach the limit again.
  3. As a result, extra rsETH is minted for this specific user.

Tools Used

Manual Review, Foundry

Recommended Mitigation Steps

It's challenging to provide a coded recommendation due to the inherent nature of the stETH token and its rebasing mechanism, which cannot be halted. However, I offer the following suggestion:

Consider using wstETH as it simplifies integration with DeFi and eliminates the rebasing functionality. This can provide a more stable and predictable environment for your application.

Assessed type

ERC20

c4-pre-sort commented 11 months ago

raymondfam marked the issue as insufficient quality report

c4-pre-sort commented 11 months ago

raymondfam marked the issue as duplicate of #530

c4-pre-sort commented 11 months ago

raymondfam marked the issue as duplicate of #711

c4-pre-sort commented 11 months ago

raymondfam marked the issue as sufficient quality report

c4-pre-sort commented 11 months ago

raymondfam marked the issue as high quality report

c4-pre-sort commented 11 months ago

raymondfam marked the issue as not a duplicate

c4-pre-sort commented 11 months ago

raymondfam marked the issue as primary issue

PaperParachute commented 11 months ago

@serial-coder As it seems you aren't active on Discord, please be aware that no, you're not allowed to comment on issues until Post-judging QA, which you would know if you had read the documentation sent to you when you were granted backstage access. Please check the docs I have sent to you in discord and if possible, remove these comments until the appropriate time.

c4-sponsor commented 11 months ago

manoj9april (sponsor) disputed

manoj9april commented 11 months ago

We don't think this is a bug. Its okay if stETH token balance rebases at any point of time.

fatherGoose1 commented 10 months ago

Edge case, and doesn't impose any risk to users. QA

c4-judge commented 10 months ago

fatherGoose1 changed the severity to QA (Quality Assurance)

c4-judge commented 10 months ago

fatherGoose1 marked the issue as grade-b

fatherGoose1 commented 10 months ago

After deep review and discussion with the Kelp team, I do not see this as an issue. The rebasing will cause Kelp's stETH balance to increase, while the exchange rate will stay the same. This is how stETH performs yield generation. Contrast this to the other accepted LSTs, their balance stays the same while their exchange rate increases over time.

From the sponsor: For cbETH and rETH , the exchange rate increases corresponding to its staking yields. And for stETH, balance increases corresponding to its staking yield. Hence gradual price increase in rsETH due to stETH balance rebases is actually correct.

c4-judge commented 10 months ago

fatherGoose1 marked the issue as grade-c