code-423n4 / 2023-06-lybra-findings

8 stars 7 forks source link

Dutch auction on excessIncomeDistribution can be frontrunned #455

Closed code423n4 closed 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraStETHVault.sol#L62 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraStETHVault.sol#L101

Vulnerability details

Impact

the excessIncomeDistribution function of the LybraStETHDepositVault contract (https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraStETHVault.sol#L62), sells all the stETH accumulated by the vault over time, to the caller for eUSD. This function applies a discount using a dutch auction model, applying a 1% discount every 30 min after lido's rebase. Anyone (users, MEV, miners) can look at the mempool, and wait for someone else to call excessIncomeDistribution, than front running it. Making sure to always get the best price possible on sold stETH and neutralizing the whole point of having a dutch auction.

Proof of Concept

The LybraStETHDepositVault uses the excessIncomeDistribution(uint256 stETHAmount) function to sell accumulate stETH and to rebase eUSD. The price, in eUSD, at which the stETH accumulated will be sold, is computed by excessIncomeDistribution using a dutch auction to apply a discount():

uint256 payAmount = (((realAmount * getAssetPrice()) / 1e18) * getDutchAuctionDiscountPrice()) / 10000;

The dutch auction looks at block.timestamp, and use it to compute the amount of discount to apply (since the result will be dived by 10000 in excessIncomeDistribution):

    function getDutchAuctionDiscountPrice() public view returns (uint256) {
        uint256 time = (block.timestamp - lidoRebaseTime) % 1 days;
        if (time < 30 minutes) return 10000;
        return 10000 - (time / 30 minutes - 1) * 100;
    }

(https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraStETHVault.sol#L101)

As you can notice by looking at the contract, there isn't any type of front run protection on this auction, which opens up the opportunity for the following scenario:

Step0: Users use the vault normally generating yield over time. Step1: Alice wants to buy the stETH accumulated at the best price possible, to do so she start observing the mempool Step2: Bob sees that there is a good discount on the stETH available and calls excessIncomeDistribution to buy it Step3: Alice sees Bob bid on the dutch auction, and front runs his request.

With this implementation, a malicious user has no reason to participate regularly to the dutch auction, but they can simply wait for someone else to place a bid, and than decide if they want to front run it or not. Braking the main purpose of an auction.

Assessed type

Timing

JeffCX commented 1 year ago

I think instead of use the term "frontrunning", in this case two user are competing for a good offer

c4-pre-sort commented 1 year ago

JeffCX marked the issue as low quality report

c4-judge commented 1 year ago

0xean marked the issue as unsatisfactory: Insufficient quality