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
Rank: 44/132
Findings: 2
Award: $212.43
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: georgypetrov
Also found by: 0xRobocop, 3agle, max10afternoon
202.5014 USDC - $202.50
https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraEUSDVaultBase.sol#L72-L86 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraEUSDVaultBase.sol#L98-L116
depositAssetToMint
function. The protocol records their deposits accordingly:Alice's balance: 10 stETH Bob's balance: 8 stETH Charlie's balance: 5 stETH Total stETH vault balance: 23 stETH
New stETH vault balance: 0.95 * 23 = 21.85 stETH
Alice initiates withdrawal: 10 stETH Total stETH pool balance: `21.85-10 = 11.85 stETH`
Bob initiates withdrawal: 8 stETH Total stETH pool balance: `11.85 - 8 = 3.85 stETH`
Charlie initiates withdrawal: 5 stETH Total stETH pool balance: 3.85 - 5 = **FAIL**
Insolvency and Losses: Due to the combination of over-withdrawals and the negative rebase, the protocol faces insolvency risk. Users like Charlie will be unable to fully withdraw their stETH deposits, leading to potential losses for them.
Manual Review
contract LybraFinance { mapping(address => uint256) public depositedShares; // Tracks the shares balance for each user uint256 public totalShares; // Total shares in the protocol function depositAssetToMint( uint256 assetAmount, uint256 mintAmount ) external virtual { require( assetAmount >= 1 ether, "Deposit should not be less than 1 stETH." ); bool success = collateralAsset.transferFrom( msg.sender, address(this), assetAmount ); require(success, "TF"); uint256 sharesAmount = calculateShares(assetAmount); // Calculate shares based on the deposited asset amount totalShares += sharesAmount; // Update total shares depositedShares[msg.sender] += sharesAmount; // Update user's shares balance depositedTime[msg.sender] = block.timestamp; if (mintAmount > 0) { _mintEUSD(msg.sender, msg.sender, mintAmount, getAssetPrice()); } emit DepositAsset( msg.sender, address(collateralAsset), assetAmount, block.timestamp ); } function withdraw(address onBehalfOf, uint256 amount) external virtual { require(onBehalfOf != address(0), "TZA"); require(amount > 0, "ZERO_WITHDRAW"); uint256 sharesToWithdraw = calculateShares(amount); // Calculate the corresponding shares based on the requested amount require( depositedShares[msg.sender] >= sharesToWithdraw, "Withdraw amount exceeds deposited amount." ); uint256 assetAmount = calculateAssetAmount(sharesToWithdraw); // Calculate the corresponding asset amount based on shares bool success = collateralAsset.transfer(onBehalfOf, assetAmount); require(success, "TF"); totalShares -= sharesToWithdraw; // Update total shares depositedShares[msg.sender] -= sharesToWithdraw; // Update user's shares balance if (borrowed[msg.sender] > 0) { _checkHealth(msg.sender, getAssetPrice()); } emit WithdrawAsset( msg.sender, address(collateralAsset), onBehalfOf, assetAmount, block.timestamp ); } // Function to calculate shares based on the deposited asset amount function calculateShares(uint256 assetAmount) internal pure returns (uint256) { // Perform the necessary calculation based on the protocol's logic // ... } // Function to calculate the corresponding asset amount based on shares function calculateAssetAmount(uint256 shares) internal pure returns (uint256) { // Perform the necessary calculation based on the protocol's logic // ... } }
Other
#0 - c4-pre-sort
2023-07-10T10:38:36Z
JeffCX marked the issue as primary issue
#1 - c4-sponsor
2023-07-18T07:14:27Z
LybraFinance marked the issue as sponsor disputed
#2 - LybraFinance
2023-07-18T07:14:41Z
That should be a concern for Lido. Our protocol does not consider the possibility of negative rebases.
#3 - c4-judge
2023-07-26T13:02:41Z
0xean marked the issue as satisfactory
#4 - 0xean
2023-07-27T18:09:53Z
Our protocol does not consider the possibility of negative rebases.
Considering the possibility is documented by Lido, I think integrators should also be considering the possibility.
#5 - c4-judge
2023-07-28T20:40:49Z
0xean marked the issue as selected for report
#6 - Rassska
2023-07-30T13:54:23Z
But 5% negative rebase is extremely rare! If it's so, the Lybra also should consider the Bunker Mode which could be enabled under a massive slashing! I don't think that the likelihood here is medium.
#7 - c4-judge
2023-07-31T23:21:12Z
0xean marked the issue as duplicate of #931
#8 - 0xean
2023-07-31T23:21:35Z
marked as dupe of #931 - both of these stem from the same underlying issue, the lack of accounting for negative rebases.
#9 - c4-judge
2023-08-02T14:40:25Z
0xean marked the issue as not selected for report
🌟 Selected for report: 0xnev
Also found by: 0xRobocop, 0xbrett8571, 0xkazim, 0xnacho, 3agle, 8olidity, ABAIKUNANBAEV, Bauchibred, Co0nan, CrypticShepherd, D_Auditor, DelerRH, HE1M, Iurii3, Kaysoft, MrPotatoMagic, RedOneN, RedTiger, Rolezn, SanketKogekar, Sathish9098, Timenov, Toshii, Vagner, bart1e, bytes032, codetilda, devival, halden, hals, kutugu, m_Rassska, naman1778, nonseodion, seth_lawson, solsaver, squeaky_cactus, totomanov, y51r, yudan, zaevlad
9.931 USDC - $9.93
Letter | Name | Description |
---|---|---|
NC | Non-critical | Non risky findings |
R | Refactor | Changing the code |
Total Found Issues | 4 |
---|
Count | Explanation | Instances |
---|---|---|
[L-01] | Missing checks | 2 |
Count | Explanation | Instances |
---|---|---|
[R-01] | Documentation Mismatch | 1 |
[R-02] | Misleading comments | 2 |
amount
should be higher than 0. But there is no check to ensure this/** * @notice Burn the amount of EUSD and payback the amount of minted EUSD * Emits a `Burn` event. * Requirements: * - `onBehalfOf` cannot be the zero address. * - `amount` Must be higher than 0. * @dev Calling the internal`_repay`function. */ function burn(address onBehalfOf, uint256 amount) external { require(onBehalfOf != address(0), "BURN_TO_THE_ZERO_ADDRESS"); require(amount>0,"ZERO_AMOUNT"); //Add this check _repay(msg.sender, onBehalfOf, amount); }
LybraEUSDVaultBase.sol
, the check to ensure that "individual mint amount shouldn't surpass 10% when the circulation reaches 10_000_000" is missing./** * @notice The mint amount number of EUSD is minted to the address * Emits a `Mint` event. * * Requirements: * - `onBehalfOf` cannot be the zero address. * - `amount` Must be higher than 0. Individual mint amount shouldn't surpass 10% when the circulation reaches 10_000_000 */ function mint(address onBehalfOf, uint256 amount) external { require(onBehalfOf != address(0), "MINT_TO_THE_ZERO_ADDRESS"); require(amount > 0, "ZERO_MINT"); _mintEUSD(msg.sender, onBehalfOf, amount, getAssetPrice()); }
assetAmount
Must be higher than 1."/* * Requirements: * - `assetAmount` Must be higher than 0. * - `mintAmount` Send 0 if doesn't mint EUSD */ function depositAssetToMint(uint256 assetAmount, uint256 mintAmount) external virtual { require(assetAmount >= 1 ether, "Deposit should not be less than 1 stETH."); bool success = collateralAsset.transferFrom(msg.sender, address(this), assetAmount); require(success, "TF");
EUSD.sol
, the comment "the contract must not be paused", is used for following functions.function transfer function approve function transferFrom function increaseAllowance function decreaseAllowance function transferShares function _approve function _transferShares function mint function burn function burnShares
EUSD.sol
, The example has mentioned wrong amount of tokens/** * @title Interest-bearing ERC20-like token for Lybra protocol. * * EUSD balances are dynamic and represent the holder's share in the total amount * of Ether controlled by the protocol. Account shares aren't normalized, so the * contract also stores the sum of all shares to calculate each account's token balance * which equals to: * * shares[account] * totalSupply / _totalShares * * For example, assume that we have: * * _getTotalMintedEUSD() -> 1000 EUSD * sharesOf(user1) -> 100 * sharesOf(user2) -> 400 * * Therefore: * * balanceOf(user1) -> 2 tokens which corresponds 200 EUSD <- Should be 200 instead of 2 * balanceOf(user2) -> 8 tokens which corresponds 800 EUSD <- Should be 800 instead of 8 * * Since balances of all token holders change when the amount of total shares * changes, this token cannot fully implement ERC20 standard: it only emits `Transfer` * events upon explicit transfer between holders. In contrast, when total amount of * pooled Ether increases, no `Transfer` events are generated: doing so would require * emitting an event for each token holder and thus running an unbounded loop. */
#0 - JeffCX
2023-07-27T16:32:10Z
Well formatted
#1 - c4-pre-sort
2023-07-27T16:32:13Z
JeffCX marked the issue as high quality report
#2 - c4-judge
2023-07-28T00:03:50Z
0xean marked the issue as grade-b
#3 - c4-sponsor
2023-07-29T09:10:19Z
LybraFinance marked the issue as sponsor confirmed