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

ZeroTrust - EtherFiLib::_initiateWithdrawImpl will revert because rebase tokens transfer 1-2 less wei #43

Open
sherlock-admin3 opened this issue Jul 3, 2024 · 13 comments
Labels
Escalation Resolved This issue's escalations have been approved/rejected High A valid High severity issue Reward A payout will be made for this issue Sponsor Confirmed The sponsor acknowledged this issue is valid Will Fix The sponsor confirmed this issue will be fixed

Comments

@sherlock-admin3
Copy link

sherlock-admin3 commented Jul 3, 2024

ZeroTrust

High

EtherFiLib::_initiateWithdrawImpl will revert because rebase tokens transfer 1-2 less wei

Summary

EtherFiLib::_initiateWithdrawImpl will revert because rebase tokens transfer 1-2 less wei

Vulnerability Detail

The protocol always assumes that the amount of tokens received is equal to the amount of tokens transferred.
This is not the case for rebasing tokens, such as stETH and eETH, because internally they transfer shares which generally results in the received amount of tokens being lower than the requested one by a couple of wei because of roundings: transferring 1e18 eETH tokens from A to B, will may result in B receiving 0.99999e18 eETH tokens.

function _initiateWithdrawImpl(uint256 weETHToUnwrap) internal returns (uint256 requestId) {
@>        uint256 eETHReceived = weETH.unwrap(weETHToUnwrap);
        eETH.approve(address(LiquidityPool), eETHReceived);
@>        return LiquidityPool.requestWithdraw(address(this), eETHReceived);
    }
  • 1 Unwraps weETHToUnwrap weETH, meaning Transfers eETHReceived of eETH from the weETH contract to this contract itself
  • 2 RequestWithdraw eETHReceived, which will attempt to transfer eETHReceived from the contract to the Etherfi protocol
    But the actual amount of eETH received may be eETHReceived - 1~2 wei.

Step 2 will fail, because the contract doesn't have enough eETH. The issue lies in attempting to transfer eETHReceived of eETH in step 2 instead of wrapping the actual amount of tokens received.

Impact

Contract functionality DoS

Code Snippet

https://github.com/sherlock-audit/2024-06-leveraged-vaults/blob/main/leveraged-vaults-private/contracts/vaults/staking/protocols/EtherFi.sol#L24

Tool used

Manual Review

Recommendation

function _initiateWithdrawImpl(uint256 weETHToUnwrap) internal returns (uint256 requestId) {
+       uint256 balanceBefore = eETH.balanceOf(address(this));
        uint256 eETHReceived = weETH.unwrap(weETHToUnwrap);
+       uint256 balanceAfter = eETH.balanceOf(address(this));
-        eETH.approve(address(LiquidityPool), eETHReceived);
-        return LiquidityPool.requestWithdraw(address(this), eETHReceived);
+         eETH.approve(address(LiquidityPool), balanceAfter - balanceBefore);
+        return LiquidityPool.requestWithdraw(address(this), balanceAfter - balanceBefore);
    }
@github-actions github-actions bot closed this as completed Jul 5, 2024
@github-actions github-actions bot added the Excluded Excluded by the judge without consulting the protocol or the senior label Jul 5, 2024
@sherlock-admin2
Copy link
Contributor

1 comment(s) were left on this issue during the judging contest.

0xmystery commented:

It concerns deposit(), NOT wrap/unwrap

@sherlock-admin4 sherlock-admin4 changed the title Hidden Laurel Cod - EtherFiLib::_initiateWithdrawImpl will revert because rebase tokens transfer 1-2 less wei ZeroTrust - EtherFiLib::_initiateWithdrawImpl will revert because rebase tokens transfer 1-2 less wei Jul 11, 2024
@sherlock-admin4 sherlock-admin4 added the Non-Reward This issue will not receive a payout label Jul 11, 2024
@ZeroTrust01
Copy link

Escalate
This should be considered a valid issue.

function unwrap(uint256 _weETHAmount) external returns (uint256) {
        require(_weETHAmount > 0, "Cannot unwrap a zero amount");
        uint256 eETHAmount = liquidityPool.amountForShare(_weETHAmount);
        _burn(msg.sender, _weETHAmount);
@>>        eETH.transfer(msg.sender, eETHAmount);
        return eETHAmount;
    }

The weETH::unwrap() function includes a transfer operation, so this involves the transfer of rebase tokens in this contract. In the LiquidityPool.requestWithdraw function, rebase tokens are transferred out of this contract because internally they transfer shares, which generally results in the received amount of tokens being lower than the requested one by a couple of wei due to rounding, So it will revert.

This is exactly the same issue as the one confirmed in the contest a few weeks ago.
sherlock-audit/2024-05-sophon-judging#63
sherlock-audit/2024-05-sophon-judging#119

@sherlock-admin3
Copy link
Author

Escalate
This should be considered a valid issue.

function unwrap(uint256 _weETHAmount) external returns (uint256) {
        require(_weETHAmount > 0, "Cannot unwrap a zero amount");
        uint256 eETHAmount = liquidityPool.amountForShare(_weETHAmount);
        _burn(msg.sender, _weETHAmount);
@>>        eETH.transfer(msg.sender, eETHAmount);
        return eETHAmount;
    }

The weETH::unwrap() function includes a transfer operation, so this involves the transfer of rebase tokens in this contract. In the LiquidityPool.requestWithdraw function, rebase tokens are transferred out of this contract because internally they transfer shares, which generally results in the received amount of tokens being lower than the requested one by a couple of wei due to rounding, So it will revert.

This is exactly the same issue as the one confirmed in the contest a few weeks ago.
sherlock-audit/2024-05-sophon-judging#63
sherlock-audit/2024-05-sophon-judging#119

You've created a valid escalation!

To remove the escalation from consideration: Delete your comment.

You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final.

@sherlock-admin4 sherlock-admin4 added the Escalated This issue contains a pending escalation label Jul 14, 2024
@mystery0x
Copy link
Collaborator

Escalate This should be considered a valid issue.

function unwrap(uint256 _weETHAmount) external returns (uint256) {
        require(_weETHAmount > 0, "Cannot unwrap a zero amount");
        uint256 eETHAmount = liquidityPool.amountForShare(_weETHAmount);
        _burn(msg.sender, _weETHAmount);
@>>        eETH.transfer(msg.sender, eETHAmount);
        return eETHAmount;
    }

The weETH::unwrap() function includes a transfer operation, so this involves the transfer of rebase tokens in this contract. In the LiquidityPool.requestWithdraw function, rebase tokens are transferred out of this contract because internally they transfer shares, which generally results in the received amount of tokens being lower than the requested one by a couple of wei due to rounding, So it will revert.

This is exactly the same issue as the one confirmed in the contest a few weeks ago. sherlock-audit/2024-05-sophon-judging#63 sherlock-audit/2024-05-sophon-judging#119

Here's the selected report for Sophon contest that's related to your finding on eETH:

sherlock-audit/2024-05-sophon-judging#4

Read through the report carefully, and you will notice wrap/unwrap is always 1:1. It's deposit() that's causing the 1-2 wei issue which does not apply to your report context.

@ZeroTrust01
Copy link

Escalate This should be considered a valid issue.

function unwrap(uint256 _weETHAmount) external returns (uint256) {
        require(_weETHAmount > 0, "Cannot unwrap a zero amount");
        uint256 eETHAmount = liquidityPool.amountForShare(_weETHAmount);
        _burn(msg.sender, _weETHAmount);
@>>        eETH.transfer(msg.sender, eETHAmount);
        return eETHAmount;
    }

The weETH::unwrap() function includes a transfer operation, so this involves the transfer of rebase tokens in this contract. In the LiquidityPool.requestWithdraw function, rebase tokens are transferred out of this contract because internally they transfer shares, which generally results in the received amount of tokens being lower than the requested one by a couple of wei due to rounding, So it will revert.
This is exactly the same issue as the one confirmed in the contest a few weeks ago. sherlock-audit/2024-05-sophon-judging#63 sherlock-audit/2024-05-sophon-judging#119

Here's the selected report for Sophon contest that's related to your finding on eETH:

sherlock-audit/2024-05-sophon-judging#4

Read through the report carefully, and you will notice wrap/unwrap is always 1:1. It's deposit() that's causing the 1-2 wei issue which does not apply to your report context.

shenshu

You mixed up the link I provided. I pointed out that this issue is exactly the same as issue group 1 (in the picture), but your link is to issue group 2 (in the picture). I never said it was the same as issue group 2.

@WangSecurity
Copy link

@ZeroTrust01 could you provide the link to the eETH contract please, so I can see how they've implemented their transfer function with fees.

@ZeroTrust01
Copy link

@ZeroTrust01 could you provide the link to the eETH contract please, so I can see how they've implemented their transfer function with fees.

This is the address of the eETH contract.
https://etherscan.io/address/0x35fA164735182de50811E8e2E824cFb9B6118ac2

This issue is not about the transfer function with fees; it is about rebase token transfer using shares. If the transfer amount is 1e18, the received amount might be (1e18 - 1 wei).

I learned this from a report where the issue was exactly the same as this one.
sherlock-audit/2024-05-sophon-judging#119

@WangSecurity
Copy link

Thank you. For future reference, historical decisions are not sources of truth and each issue is different, so I would like to ask you instead of saying "this issue is the exact same as in the previous contest", prove that your issue is valid, regardless of other contests and decisions. Often it's the case that the same issue is valid in one contest, but invalid in an another.

About the issue. Even though, Notional's code fetches the eEthReceived from weEth.unwrap the eEthReceived represents the amount before eEth.transfer. eETH.transfer makes a conversion from input amount to token shares and sends the amount of shares received, which can be lower due to precision loss. Later, this will revert since the contract tries to send eEthreceived.

Planning to accept the escalation and validate the report with high severity since it can happen on every withdrawal from EtherFi. @mystery0x @ZeroTrust01 are there any duplicates?

@wtp0x
Copy link

wtp0x commented Jul 17, 2024

I believe it is unique.

@WangSecurity WangSecurity added the High A valid High severity issue label Jul 18, 2024
@sherlock-admin2 sherlock-admin2 added Reward A payout will be made for this issue and removed Non-Reward This issue will not receive a payout labels Jul 18, 2024
@WangSecurity
Copy link

Result:
High
Unique

@WangSecurity WangSecurity reopened this Jul 18, 2024
@sherlock-admin2 sherlock-admin2 removed the Escalated This issue contains a pending escalation label Jul 18, 2024
@sherlock-admin3 sherlock-admin3 added the Escalation Resolved This issue's escalations have been approved/rejected label Jul 18, 2024
@sherlock-admin4
Copy link
Contributor

Escalations have been resolved successfully!

Escalation status:

@sherlock-admin3 sherlock-admin3 removed the Excluded Excluded by the judge without consulting the protocol or the senior label Jul 22, 2024
@sherlock-admin3 sherlock-admin3 added Sponsor Confirmed The sponsor acknowledged this issue is valid Will Fix The sponsor confirmed this issue will be fixed labels Jul 27, 2024
@sherlock-admin2
Copy link
Contributor

The protocol team fixed this issue in the following PRs/commits:
https://github.com/notional-finance/leveraged-vaults/pull/94/files

jeffywu added a commit to notional-finance/leveraged-vaults that referenced this issue Aug 9, 2024
@sherlock-admin2
Copy link
Contributor

The Lead Senior Watson signed off on the fix.

jeffywu added a commit to notional-finance/leveraged-vaults that referenced this issue Sep 9, 2024
* fix: adding post mint and redeem hooks

* test: changes to base tests

* config: changes to config

* feat: changes to global

* feat: changes to trading

* feat: changes to utils

* feat: changes to single sided lp

* feat: vault storage

* fix: misc fixes

* fix: staking vaults

* fix: solidity versions

* fix: test build

* fix: adding staking harness

* fix: adding initialization

* fix: initial test bugs

* fix: weETH valuation

* fix: deleverage collateral check

* fix: initial harness compiling

* fix: initial test running

* fix: acceptance tests passing

* test: migrated some tests

* fix: withdraw tests

* test: adding deleverage test

* fix: adding liquidation tests

* test: withdraw request

* test: finalize withdraws manual

* test: tests passing

* fix: single sided lp tests with vault rewarder

* fix: putting rewarder tests in

* fix: reward tests running

* fix: vault rewarder address

* fix: initial staking harness

* fix: adding staking harness

* fix: initial PT vault build

* fix: moving ethena vault code

* fix: moving etherfi code

* feat: adding pendle implementations

* fix: staking harness to use USDC

* fix: curve v2 adapter for trading

* test: basic tests passing

* fix: adding secondary trading on withdraw

* fix tests

* fix: trading on redemption

* fix: ethena vault config

* fix: switch ethena vault to sell sDAI

* fix warnings

* fix: more liquidation tests passing

* fix: ethan liquidation tests

* pendle harness build

* fix: initial tests passing

* fix: adding pendle oracle

* fix: test deal token error

* fix: changing pendle liquidation discount

* fix: all tests passing

* fix: etherfi borrow currency

* fix: adding more documentation

* change mainnet fork block

* properly update data seed files

* fix arbitrum tests

* fix test SingleSidedLP:Convex:crvUSD/[USDT]

* fix: can finalize withdraws

* fix: refactor withdraw valuation

* fix: pendle expiration tests

* fix: pendle pt valuation

* remove flag

* fix: remove redundant code path

* fix: initial commit

* fix: vault changes

* fix: vault changes

* fix: some tests passing

* fix: fixing more tests

* fix: updated remaining tests

* fix: split withdraw bug

* fix: new test

* fix: remaining tests

* fix: split withdraw reqest bug

* feat: add PendlePTKelp vault

* update oracle address, fix tests

* Address CR comments

* add test_canTriggerExtraStep

* fix tests

* fix: run tests

* feat: adding generic vault

* feat: update generate tests

* fix: changes from merge

* fix: adding has withdraw requests

* fix: update oracle address for network

* fix: merge kelp harness

* fix: base tests passing

* fix: move generation config

* fix: initial pendle test generation

* fix: mainnet tests passing

* fix: vault rewarder

* fix: more pendle tests

* fix: pendle dex test

* fix: adding camelot dex

* fix: update usde pt

* fix: adding camelot adapter

* fix: support configurable dex

* fix: adding more PT vaults

* fix: approval bug

* fix: update dex information

* fix: mainnet tests passing

* fix: update arbitrum pendle tests

* fix: update deployment addresses

* test: add balancer v2 batch trade

* fix: add given out batch trade

* fix: remove trade amount filling

* fix: add some comments

* fix: audit issue #60

* fix: switch to using getDecimals

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#73

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#72

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#70

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#66

* test: adding pendle oracle test

* fix:  sherlock-audit/2024-06-leveraged-vaults-judging#69

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#64

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#43

* fix: audit issue #18

* fix: move slippage check

* fix: add comment back

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#56

* test: adding test that catches math underflow

* fix: adding test for vault shares

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#44

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#6

* test: adds test to check split withdraw request value

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#78

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#80

* fix: updating valuations for tests

* fix: update run tests

* fix: remove stETH withdraws from Kelp in favor of ETH withdraws

* fix: update tests for pendle rs eth

* fix: resolve compile issues

* fix: rsETH oracle price

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#87

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#67

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#6

* test: update tests for invalid splits

* fix: sherlock fix review comments

* merge: merged master into branch

* fix: empty reward tokens

* fix: claim rewards tests

* fix: liquidation tests

* fixing more tests

* fix: allowing unused reward pools

* test: migrating reward pools

* fix: rewarder test

* fix: claim rewards before withdrawing

* fix: deployed vault rewarder lib on arbitrum

* fix: deployed new tbtc vault

* docs: adding deployment documentation

* fix: update config

---------

Co-authored-by: sbuljac <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Escalation Resolved This issue's escalations have been approved/rejected High A valid High severity issue Reward A payout will be made for this issue Sponsor Confirmed The sponsor acknowledged this issue is valid Will Fix The sponsor confirmed this issue will be fixed
Projects
None yet
Development

No branches or pull requests

7 participants