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():
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;
}
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.
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 theLybraStETHDepositVault
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 callexcessIncomeDistribution
, 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 theexcessIncomeDistribution(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 byexcessIncomeDistribution
using a dutch auction to apply a discount():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 inexcessIncomeDistribution
):(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