Lybra Finance - HE1M's results

A protocol building the first interest-bearing omnichain stablecoin backed by LSD.

General Information

Platform: Code4rena

Start Date: 23/06/2023

Pot Size: $60,500 USDC

Total HM: 31

Participants: 132

Period: 10 days

Judge: 0xean

Total Solo HM: 10

Id: 254

League: ETH

Lybra Finance

Findings Distribution

Researcher Performance

Rank: 1/132

Findings: 6

Award: $5,144.97

QA:
grade-a

🌟 Selected for report: 1

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: Neon2835

Also found by: 0xRobocop, 0xcm, Arz, DedOhWale, HE1M, MohammedRizwan, azhar, kankodu, zaevlad

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
edited-by-warden
duplicate-769

Awards

143.4901 USDC - $143.49

External Links

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/token/PeUSDMainnetStableVision.sol#L129

Vulnerability details

Impact

There is no control on borrowing flashloan, so anyone can borrow an unwanted flashloan on behalf of a receiver, and the receiver must burn share to pay the fee (in case it has nonzero eUSD balance).

Proof of Concept

Suppose there is an innocent contract (called FlashloanBorrower.sol) which is deployed to borrow flashloan from LybraFinance protocol. This contract must have the interface FlashBorrower implemented to be used during the flashloan callback:

interface FlashBorrower { /// @notice Flash loan callback /// @param amount The amount of tokens received /// @param data Forwarded data from the flash loan request /// @dev Called after receiving the requested flash loan, should return tokens + any fees before the end of the transaction function onFlashLoan(uint256 amount, bytes calldata data) external; }

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/token/PeUSDMainnetStableVision.sol#L21C1-L27C2

During executing flashloan, the requested amount is transferred to the receiver: https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/token/PeUSDMainnetStableVision.sol#L131 Then, the callback function is called on the receiver address: https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/token/PeUSDMainnetStableVision.sol#L132 Then, the same lent out fund will be transferred from the receiver to the protocol: https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/token/PeUSDMainnetStableVision.sol#L133 Finally, the fee of the flashloan is burned from the receiver. https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/token/PeUSDMainnetStableVision.sol#L137

The problem is that anyone can call this function to borrow flashloan on behalf of FlashloanBorrower.sol, and finally the fee will be deducted from FlashloanBorrower.sol.

Let's say Bob (a malicious user) calls executeFlashloan(FlashBorrower(address(FlashloanBorrower)), 1_000_000e18, ""). In this case, 1_000_000e18 will be transferred to FlashloanBorrower contract, then the callback is called, then it is transferred from FlashloanBorrower to the protocol, and finally the fee is deducted from FlashloanBorrower (assuming FlashloanBorrower has enough eUSD to pay the fee). As a result, FlashloanBorrower loses the fee of the unwanted flashloan.

Tools Used

There are two recommendations as other protocol's flashloan is working:

  • First: Taking the fee from the msg.sender, not the receiver. In other words, burning the shares of msg.sender as fee.
  • Second: Instead of directly calling EUSD.transferFrom(...), it is better to track the balanceBefore and balanceAfter, so that balanceAfter - balanceBefore >= getFee(shareAmount).

Assessed type

Context

#0 - c4-pre-sort

2023-07-09T15:01:09Z

JeffCX marked the issue as duplicate of #280

#1 - c4-judge

2023-07-28T15:30:34Z

0xean marked the issue as satisfactory

#2 - c4-judge

2023-07-28T19:53:20Z

0xean changed the severity to 3 (High Risk)

Findings Information

🌟 Selected for report: HE1M

Labels

bug
3 (High Risk)
primary issue
satisfactory
selected for report
sponsor acknowledged
edited-by-warden
H-05

Awards

4814.8482 USDC - $4,814.85

External Links

Lines of code

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

Vulnerability details

Impact

If the project is just started, a malicious user can make the _totalSupply and _totalShares imbalance significantly by providing fake income. By doing so, later, when innocent users deposit and mint, the malicious user can steal protocol's stETH without burning any share. Moreover, the protocol's income can be stolen as well.

Proof of Concept

Suppose nothing is deposited in the protocol (it is day 0).

Bob (a malicious user) deposits $1000 worth of ether (equal to 1 ETH, assuming ETH price is $1000) to mint 200e18 + 1 eUSD. The state will be:

Then, Bob transfers directly 0.2stETH (worth $200) to the protocol. By doing so, Bob is providing a fake excess income in the protocol. So, the state will be:

  • shares[Bob] = 200e18 + 1
  • _totalShares = 200e18 + 1
  • _totalSupply = 200e18 + 1
  • borrowed[Bob] = 200e18 + 1
  • poolTotalEUSDCirculation = 200e18 + 1
  • depositAsset[Bob] = 1e18
  • totalDepositedAsset = 1e18
  • stETH.balanceOf(protocol) = 1e18 + 2e17

Then, Bob calls excessIncomeDistribution to buy this excess income. As you see in line 63, the excessIncome is equal to the difference of stETH.balanceOf(protocol) and totalDepositedAsset. So, the excessAmount = 2e17. https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraStETHVault.sol#L63

Then, in line 66, this amount 2e17 is converted to eUSD amount based on the price of stETH. Since, we assumed ETH is $1000, we have:

uint256 payAmount = (((realAmount * getAssetPrice()) / 1e18) * getDutchAuctionDiscountPrice()) / 10000 = 2e17 * 1000e18 / 1e18 = 200e18

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

Since the protocol is just started, there is no feeStored, so the income is equal to zero. https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraStETHVault.sol#L68

In line 75, we have:

uint256 sharesAmount = _EUSDAmount.mul(_totalShares).div(totalMintedEUSD) = 200e18 * (200e18 + 1) / (200e18 + 1) = 200e18

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

In line 81, this amount of sharesAmount will be burned from Bob, and then in line 93, 2e17 stETH will be transferred to Bob. So, the state will be:

Please note that currently we have _totalSupply = 200e18 + 1 and _totalShares = 1.

Suppose, Alice (an innocent user) deposits 10ETH, and mints 4000e18 eUSD. So, the amount of shares minted to Alice will be:

sharesAmount = _EUSDAmount.mul(_totalShares).div(totalMintedEUSD) = 4000e18 * 1 / (200e18 + 1) = 19

So, the state will be:

  • shares[Bob] = 1
  • _totalShares = 1 + 19 = 20
  • _totalSupply = 200e18 + 1 + 4000e18 = 4200e18 + 1
  • borrowed[Bob] = 200e18 + 1
  • poolTotalEUSDCirculation = 200e18 + 1 + 4000e18 = 4200e18 + 1
  • depositAsset[Bob] = 1e18
  • totalDepositedAsset = 1e18 + 10e18 = 11e18
  • stETH.balanceOf(protocol) = 1e18 + 10e18 = 11e18
  • shares[Alice] = 19
  • borrowed[Alice] = 4000e18
  • depositAsset[Alice] = 10e18

Now, different issues can happen leading to loss/steal of funds:

  • First Scenario: If Alice is a provider, Bob can redeem eUSD multiple of times to receive stETH without burning any share by calling rigidRedemption. To be more accurate, Bob should call this function with eusdAmount as parameter equal to _totalSupply / _totalShares. For example:

    • first call: rigidRedemption (Alice, 210e18), so we will have:

      • shares[Bob] = 1
      • _totalShares = 20
      • _totalSupply = 4200e18 + 1 - 210e18 = 3990e18 + 1
      • borrowed[Bob] = 200e18 + 1
      • poolTotalEUSDCirculation = 4200e18 + 1 - 210e18 = 3990e18 + 1
      • depositAsset[Bob] = 1e18
      • totalDepositedAsset = 11e18 - 21e16
      • stETH.balanceOf(protocol) = 11e18 - 21e16
      • shares[Alice] = 19
      • borrowed[Alice] = 4000e18 - 210e18 = 3790e18
      • depositAsset[Alice] = 10e18 - 21e16 Please note that no shares are burned from Bob, because getSharesByMintedEUSD returns zero as 210e18 * 20 / (4200e18 + 1) = 0. It means, Bob receives 0.21 stETH by burning no shares.
    • second call: rigidRedemption (Alice, 199e18), so we will have:

      • shares[Bob] = 1
      • _totalShares = 20
      • _totalSupply = 3990e18 + 1 - 199e18 = 3791e18 + 1
      • borrowed[Bob] = 200e18 + 1
      • poolTotalEUSDCirculation = 3990e18 + 1 - 199e18 = 3791e18 + 1
      • depositAsset[Bob] = 1e18
      • totalDepositedAsset = 11e18 - 210e15 - 199e15 = 11e18 - 409e15
      • stETH.balanceOf(protocol) = 11e18 - 210e15 - 199e15 = 11e18 - 409e15
      • shares[Alice] = 19
      • borrowed[Alice] = 3790e18 - 199e18 = 3591e18
      • depositAsset[Alice] = 10e18 - 210e15 - 199e15 = 10e18 - 409e15 Please note that no shares are burned from Bob, because getSharesByMintedEUSD returns zero as 199e18 * 20 / (3990e18 + 1) = 0. It means, Bob receives 0.199 stETH by burning no shares.
    • third call: rigidRedemption (Alice, 189e18), so we will have:

      • shares[Bob] = 1
      • _totalShares = 20
      • _totalSupply = 3791e18 + 1 - 189e18 = 3602e18 + 1
      • borrowed[Bob] = 200e18 + 1
      • poolTotalEUSDCirculation = 3791e18 + 1 - 189e18 = 3602e18 + 1
      • depositAsset[Bob] = 1e18
      • totalDepositedAsset = 11e18 - 409e15 - 189e15 = 11e18 - 598e15
      • stETH.balanceOf(protocol) = 11e18 - 409e15 - 189e15 = 11e18 - 598e15
      • shares[Alice] = 19
      • borrowed[Alice] = 3591e18 - 189e18 = 3402e18
      • depositAsset[Alice] = 10e18 - 409e15 - 189e15 = 10e18 - 598e15 Please note that no shares are burned from Bob, because getSharesByMintedEUSD returns zero as 189e18 * 20 / (3791e18 + 1) = 0. It means, Bob receives 0.189 stETH by burning no shares.

    So far, by just calling the function rigidRedemption three times, Bob received 0.21 + 0.199 + 0.189 = 0.598 stETH (worths $598). If, bob continues calling this function, his gain will increase more and more to the point that _totalSupply and _totalShares become closer to each other.

    A simple calculation shows that if Bob calls this function 60 times (for sure each time the input parameter should be adjusted based on the _totalSupply and _totalShares), the state will be:

    • shares[Bob] = 1
    • _totalShares = 20
    • _totalSupply = 203.7e18
    • borrowed[Bob] = 200e18 + 1
    • poolTotalEUSDCirculation = 203.7e18
    • depositAsset[Bob] = 1e18
    • totalDepositedAsset = 7e18
    • stETH.balanceOf(protocol) = 7e18
    • shares[Alice] = 19
    • borrowed[Alice] = 3.7e18
    • depositAsset[Alice] = 6e18 It shows that almost the gain of Bob is 4 stETH (worth $4000).

    The following code simply shows that how this repetitive calling of rigidRedemption works:

// SPDX-License-Identifier: MIT pragma solidity 0.8.18; contract LybraPoC { mapping(address => uint256) public borrowed; mapping(address => uint256) public shares; address public Alice = address(1); address public Bob = address(2); uint256 public bobGain; uint256 public num; function redeem() public { uint256 toBeRedeemed; uint256 requiredShares; uint256 _totalSupply = 4200e18 + 1; uint256 _totalShares = 20; shares[Bob] = 1; shares[Alice] = 19; borrowed[Bob] = 200e18 + 1; borrowed[Alice] = 4000e18; while (true) { num++; toBeRedeemed = (_totalSupply % _totalShares == 0) ? (_totalSupply / _totalShares) - 1 : (_totalSupply / _totalShares); requiredShares = (toBeRedeemed * _totalShares) / _totalSupply; if (toBeRedeemed > borrowed[Alice]) { break; } borrowed[Alice] -= toBeRedeemed; _totalSupply -= toBeRedeemed; _totalShares -= requiredShares; shares[Bob] -= requiredShares; bobGain += toBeRedeemed; } } }

Please note that, Bob does not have enough share to repay his borrow and release all his collateral. So, assuming safe collateral rate is 160%, Bob at most can withdraw 1 ETH - 1.6 * (200e18 + 1) = $680. He also gained $4000 by redeeming Alice 60 times, so Bob's balance now is: $680 + $4000 = $4680 which means $3680 is his total gain that is stolen from the protocol. In other words, protocol has minted some shares without enough stETH backed.

Please note that Bob can now start to repay his borrow to reduce borrowed[Bob] step by step, without burning any share. For example, first repays 10e18 eUSD, second repays 9e18 eUSD. But, for simplicity, I ignored this calculation, and just focused on redeeming Alice to steal big fund. By repaying multiple of times, _totalSupply and _totalShares become closer to each other. Then again it is possible to make it imbalance by providing fake income and attack the next users. So, this attack can be applied multiple of times without any restriction.

Please note that Alice is just an example of all the providers in the protocol. If there are other non-provider users also, this scenario is still valid.

  • Second Scenario: If Alice is liquidated, Bob can liquidate her without burning share again similar to the mechanism described during redeeming.

  • Third Scenario: Please note that if another innocent user (Eve) is also involved in our example, she will lose fund as well. So, let's say that Eve deposited 20 ETH, and also minted 10000e18 eUSD. So, the state will be:

    • shares[Bob] = 1
    • _totalShares = 20 + 47 = 67
    • _totalSupply = 4200e18 + 1 + 10000e18 = 14200e18 + 1
    • borrowed[Bob] = 200e18 + 1
    • poolTotalEUSDCirculation = 4200e18 + 1 + 10000e18 = 14200e18 + 1
    • depositAsset[Bob] = 1e18
    • totalDepositedAsset = 11e18 + 20e18 = 31e18
    • stETH.balanceOf(protocol) = 11e18 + 20e18 = 31e18
    • shares[Alice] = 19
    • borrowed[Alice] = 4000e18
    • depositAsset[Alice] = 10e18
    • shares[Eve] = 47
    • borrowed[Eve] = 10000e18
    • depositAsset[Eve] = 20e18 Now, suppose only Alice is provider, and Eve is not. So, we can redeem Alice by using the same mechanism we describe in the first scenario. Using the same piece of code for repetitive redemption, we have:
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; contract LybraPoC { mapping(address => uint256) public borrowed; mapping(address => uint256) public shares; address public Alice = address(1); address public Bob = address(2); address public Eve = address(3); uint256 public bobGain; uint256 public num; uint256 public _totalSupply; uint256 public _totalShares; function redeem() public { uint256 toBeRedeemed; uint256 requiredShares; _totalSupply = 14200e18 + 1; _totalShares = 67; shares[Bob] = 1; shares[Alice] = 19; shares[Eve] = 47; borrowed[Bob] = 200e18 + 1; borrowed[Alice] = 4000e18; borrowed[Eve] = 10000e18; while (true) { num++; toBeRedeemed = (_totalSupply % _totalShares == 0) ? (_totalSupply / _totalShares) - 1 : (_totalSupply / _totalShares); requiredShares = (toBeRedeemed * _totalShares) / _totalSupply; if (toBeRedeemed > borrowed[Alice]) { break; } borrowed[Alice] -= toBeRedeemed; _totalSupply -= toBeRedeemed; _totalShares -= requiredShares; shares[Bob] -= requiredShares; bobGain += toBeRedeemed; } } }

After redeeming Alice 23 times, the state will be:

  • shares[Bob] = 1
  • _totalShares = 20 + 47 = 67
  • _totalSupply = 10200.2e18
  • borrowed[Bob] = 200e18 + 1
  • poolTotalEUSDCirculation = 10200.2e18 + 1
  • depositAsset[Bob] = 1e18
  • totalDepositedAsset = 27e18
  • stETH.balanceOf(protocol) = 27e18
  • shares[Alice] = 19
  • borrowed[Alice] = 2.1e17
  • depositAsset[Alice] = 6e18
  • shares[Eve] = 47
  • borrowed[Eve] = 10000e18
  • depositAsset[Eve] = 20e18

Now if Eve wants to repay her whole borrowed amount, she should burn almost 65 shares: 10000e18 * 67 / 10200e18, but she has only 47 shares. So, she can only repay at most 7155e18 of her borrow. It means that Eve's fund is stolen by Bob. In other words, the collateralized ETH are taken by Bob without burning any shares, so the left shares do not have enough ETH backed.

This scenario shows that Bob made _totalSupply and _totalShares imbalance, then two innocent users deposited in the protocol and borrowed some eUSD. Since the difference between these two _totalSupply and _totalShares is large, small amount of shares are minted. Then, Bob redeemed some amount through the user who was provider. By doing so, the values of _totalSupply and _totalShares become closer to each other. Now if the second user intends to repay her borrow, she should burn more shares that she owns (because the difference of the values _totalSupply and _totalShares is now smaller).

Please note that for sake of simplicity the fees related to the redemption/liquidation are ignored. So, considering those into our calculation does not make the scenarios invalid.

In Summary:

Bob makes _totalSupply and _totalShares imbalance significantly, by just providing fake income in the protocol at day 0. Now that it is imbalanced, he can redeem, or liquidate users without burning any shares. He can also steal protocol's income fund without burning any shares.

Tools Used

First Fix: During the _repay, it should return the amount of burned shares. https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraEUSDVaultBase.sol#L279

So that in the functions liquidation, superLiquidation, and rigidRedemption, again the burned shares should be converted to eUSD, and this amount should be used for the rest of calculations.

function rigidRedemption(address provider, uint256 eusdAmount) external virtual { // ... uint256 brnedShares = _repay(msg.sender, provider, eusdAmount); eusdAmount = getMintedEUSDByShares(brnedShares); //... }

Second Fix: In the excessIncomeDistribution, the same check

uint256 sharesAmount = EUSD.getSharesByMintedEUSD(payAmount - income); if (sharesAmount == 0) { //EUSD totalSupply is 0: assume that shares correspond to EUSD 1-to-1 sharesAmount = (payAmount - income); }

should be included in the else body as well. https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraStETHVault.sol#L75-L79

Assessed type

Context

#0 - c4-pre-sort

2023-07-11T20:00:09Z

JeffCX marked the issue as primary issue

#1 - c4-sponsor

2023-07-14T09:41:24Z

LybraFinance marked the issue as sponsor acknowledged

#2 - c4-judge

2023-07-26T00:05:24Z

0xean marked the issue as satisfactory

#3 - c4-judge

2023-07-28T20:49:37Z

0xean marked the issue as selected for report

Findings Information

🌟 Selected for report: DelerRH

Also found by: DelerRH, HE1M, LaScaloneta, No12Samurai, RedTiger, adeolu, ayden, bart1e, f00l, pep7siup

Labels

bug
2 (Med Risk)
satisfactory
sponsor confirmed
duplicate-828

Awards

43.047 USDC - $43.05

External Links

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/miner/ProtocolRewardsPool.sol#L227 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/miner/ProtocolRewardsPool.sol#L190

Vulnerability details

Impact

Miscalculation in token.decimals() results in wrong calculation of reward, and causing users to receive much lower reward than expected.

Proof of Concept

There are two issues regarding the token.decimals():

Tools Used

In both bugs, it is recommended to replace token.decimals() with 10**token.decimals().

Assessed type

Decimal

#0 - c4-pre-sort

2023-07-09T14:27:20Z

JeffCX marked the issue as primary issue

#1 - c4-sponsor

2023-07-18T08:52:37Z

LybraFinance marked the issue as sponsor confirmed

#2 - c4-judge

2023-07-26T13:15:32Z

0xean marked the issue as satisfactory

#3 - c4-judge

2023-07-28T20:39:17Z

0xean marked issue #828 as primary and marked this issue as a duplicate of 828

Findings Information

🌟 Selected for report: Musaka

Also found by: Brenzee, Bughunter101, HE1M, Jorgect, kutugu, pep7siup

Labels

bug
2 (Med Risk)
satisfactory
duplicate-223

Awards

84.3563 USDC - $84.36

External Links

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/miner/ProtocolRewardsPool.sol#L190

Vulnerability details

Impact

Miscalculation in claiming the protocol rewards earnings causes the users not be able to receive their reward properly.

Proof of Concept

During claiming the protocol rewards earnings, there is a miscalculation in

uint256 eUSDShare = balance >= reward ? reward : reward - balance;

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/miner/ProtocolRewardsPool.sol#L196C13-L196C79

Tools Used

The following line should be modified.

uint256 eUSDShare = balance >= reward ? reward : reward - balance;

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/miner/ProtocolRewardsPool.sol#L196C13-L196C79

Recommended modification:

uint256 eUSDShare = balance >= reward ? reward : balance;

Assessed type

Math

#0 - c4-pre-sort

2023-07-10T13:50:33Z

JeffCX marked the issue as duplicate of #161

#1 - c4-judge

2023-07-28T15:44:21Z

0xean marked the issue as satisfactory

Awards

1.3247 USDC - $1.32

Labels

bug
2 (Med Risk)
high quality report
satisfactory
duplicate-27

External Links

Lines of code

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

Vulnerability details

Impact

Unmatched function for getting the exchange rate can lead to being unable to mint PeUSD when depositing ETH into Rocket Pool.

Proof of Concept

The interface used in LybraRETHVault.sol for getting the exchange rate does not match the target contract RETH. In line 10, the interface is getExchangeRatio: https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraRETHVault.sol#L10

while the contract RocketTokenRETH.sol implements getExchangeRate. https://github.com/rocket-pool/rocketpool/blob/6a9dbfd85772900bb192aabeb0c9b8d9f6e019d1/contracts/contract/token/RocketTokenRETH.sol#L66 which is deployed in the following address: https://etherscan.io/address/0xae78736Cd615f374D3085123A210448E74Fc6393#readContract#F6

This can cause the protocol not to be able to mint PeUSD when depositing ETH into Rocket Pool, because it will revert. The flow is: LybraRETHVault::depositEtherToMint >> LybraRETHVault::getAssetPrice >> IRETH(address(collateralAsset)).getExchangeRatio() >> Revert!!!

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraRETHVault.sol#L27 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraRETHVault.sol#L35 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraRETHVault.sol#L47C33-L47C83

The same issue is also available in LybraWbETHVault.sol. https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraWbETHVault.sol#L10 https://etherscan.io/token/0xa2E3356610840701BDf5611a53974510Ae27E2e1#readProxyContract#F13

Tools Used

Match the interfaces together.

Assessed type

Context

#0 - c4-pre-sort

2023-07-04T02:31:16Z

JeffCX marked the issue as duplicate of #129

#1 - c4-pre-sort

2023-07-04T02:33:56Z

JeffCX marked the issue as high quality report

#2 - c4-pre-sort

2023-07-04T13:29:35Z

JeffCX marked the issue as duplicate of #27

#3 - c4-judge

2023-07-28T17:15:05Z

0xean marked the issue as satisfactory

Awards

57.9031 USDC - $57.90

Labels

bug
grade-a
high quality report
QA (Quality Assurance)
sponsor confirmed
edited-by-warden
Q-29

External Links

Q1

The comment states that: amount Must be higher than 0. Individual mint amount shouldn't surpass 10% when the circulation reaches 10_000_000 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraEUSDVaultBase.sol#L124C10-L124C126 But it is not implemented same as what the comment suggests. This amount is limited to mintVaultMaxSupply set in the LybraConfigurator: https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraEUSDVaultBase.sol#L260 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/configuration/LybraConfigurator.sol#L119

Recommendation

Whether remove this comment or implement such functionality in mint(...):

if ( (borrowed[msg.sender] * 100) / poolTotalEUSDCirculation > 10 && poolTotalEUSDCirculation > 10_000_000 * 1e18 ) revert("Mint Amount cannot be more than 10% of total circulation");
Q2

Wrong error message in require-statement. EUSD should be changed to PeUSD. https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L131

#0 - c4-pre-sort

2023-07-27T19:34:50Z

JeffCX marked the issue as high quality report

#1 - c4-judge

2023-07-27T23:58:20Z

0xean marked the issue as grade-b

#2 - c4-judge

2023-07-28T20:47:47Z

0xean marked the issue as grade-a

#3 - 0xean

2023-07-28T20:48:07Z

upgrading to A score based on multiple QA reports combined for this warden

#4 - c4-sponsor

2023-07-29T11:22:19Z

LybraFinance marked the issue as sponsor confirmed

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter