Peapods engaged Guardian to review the security of its leveraged volatility farming updates. From the 9th of September to the 3rd of October, a team of 6 auditors reviewed the source code in scope.
- Published
- Review window
- September 9 to October 3, 2024
- Language
- Solidity
- Chains
- Arbitrum, Ethereum
- Sector
- Yield and vaults
- 12 Critical
- 13 High
- 45 Medium
- 31 Low
- 0 Informational
Scope
Overview
Peapods engaged Guardian to review the security of its leveraged volatility farming updates. From the 9th of September to the 3rd of October, a team of 6 auditors reviewed the source code in scope.
Issues Detected Throughout the engagement 25 High/Critical issues were uncovered and promptly remediated by the Peapods team. Several issues impacted the fundamental behavior of the protocol, following their remediation Guardian believes the protocol to uphold the primary functionality described for the LVF system.
Security Recommendation Given the number of High and Critical issues detected, Guardian supports a secondary security review of the protocol at a finalized frozen commit. Furthermore, the Peapods team should increase units tests across the codebase, as well as integration tests between the LVF and FraxLend systems. The engagement exposed multiple blind spots within the auto-compounding logic and the FraxLend interaction that should be thoroughly tested, and the Foundry testing infrastructure that was built during the audit can be utilized.
Findings 101
-
C-01 Critical Incorrect Price From aspTKN Oracle Logic Error Resolved
Description
Proof of concept: PoC
The
getPricefunction should return price as(aspTKN / pairedLPToken)which is consumed by the FraxlendisSolventfunction to determine a borrower's LTV.However, the calculation is incorrect because it takes price from the
spTKNoracle and divides it by_aspTknPerSpTknwhen it should be multiplying instead. Therefore, the price returned is always incorrect and borrower's LTV is miscalculated in Fraxlend.Recommendation
Instead of
_priceLow = (_priceLow * _assetFactor) / _aspTknPerSpTkn;do_priceLow = (_priceLow * _aspTknPerSpTkn) / _assetFactor;Resolution
Peapods Team: Resolved in the following code change.
-
C-02 Critical Compounding Of Rewards To LP Failure Logic Error Partially resolved
Description
Proof of concept: PoC
Function
_processRewardsToPodLpis called on every major flow such as deposit and withdrawal to compound any earned rewards to the LP token and back into theAutoCompoundingPodLp. The amounts passed toindexUtils.addLPAndStakewill be the entire balance of POD in theAutoCompoundingPodLp, and half of the paired lp token that’s obtained from the reward tokens with a V3 swap.The issue is that the token A and token B amounts can be wildly different from their current reserve ratio in the pool, causing the desired token inputs to fail the calculated
amountAMinandamountBMinpassed to Uniswap V2.There can be a multitude of reasons why few paired LP tokens are to be added, such as small reward distribution since the last reward claim and/or V3 swap manipulation in
swapV3Singlesince 0 slippage is passed. An attacker is not necessary for the UniV2 revert to occur.Attackers can also inflate the balances with token donations to trigger this revert as well. This DoS will occur even if
LP_SLIPPAGEwas drastically increased. Ultimately, all core functionalities of theAutoCompoundingPodLpcan be prevented, and users can lose assets due to the inability to withdraw.Recommendation
In
_pairedLpTokenToPodLp, consider performing some sanity checks to ensure tokens are in the correct ratio before callingIndexUtils.addLPAndStake. Furthermore, considering wrapping the auto-compounding in a try-catch.Resolution
Peapods Team: Resolved in the following code change.
Guardian Team: The implemented single-sided LP formula is incorrectly implemented, using
_fullAmtinstead of_rwhere necessary. This will make the result of_pairedSwapAmtlarger than_amountIn, 21 causing an underflow and blocking all rewards processing. -
C-03 Critical spTKN Oracle Can Be Manipulated With Donation Oracle Manipulation Resolved
Description
Proof of concept: PoC
The price of a
spTKNis affected by the amount and price of the underlying token in the pod. This is accounted for in_accountForCBRInPricewhich does:(_amtUnderlying * IERC20(_underlying).balanceOf(_pod) * 10 * IERC20Metadata(_pod).decimals()) /
IERC20(_pod).totalSupply() / 10 * IERC20Metadata(_underlying).decimals();The problem lies in using the balance of underlying the pod, which can be easily manipulated through a donation of the underlying token to the pod. This would increase the value of
spTKNand subsequently theaspTKN, which is the collateral token inFraxPairLend.Although the attacker loses the donated tokens, they can manipulate the oracle pricing to borrow more tokens from the lending protocol, exploiting the system. Low liquidity pods are more susceptible to such attacks.
Recommendation
Instead of using
balanceOf, use a storage variable to keep track of the balance of underlying tokens in a pod.Resolution
Peapods Team: Resolved in the following code change.
-
C-04 Critical removeLeverage DoS Due To Inconsistent Amounts DoS Resolved
Description
When removing leverage, the user provides the
_borrowAssetAmtto be flash loaned. This amount is then used to calculate_borrowSharesToRepay, with the calculation performed by rounding up.Additionally, the
LeverageManagergrants approval to the Fraxlend pair for exactly the_borrowAssetAmt. Then, on the Fraxlend side, amount to repay is recalculated using this_borrowSharesToRepay.However, this calculation also rounds up.
_amountToRepay = _totalBorrow.toAmount(_shares, true);. Because of rounding up twice during this transaction flow, the_amountToRepayends up being higher than the flash-loaned_borrowAssetAmt.When the internal
_repayAssetfunction attempts to transfer_amountToRepayfromLeverageManagerto Fraxlend pair, it fails because theLeverageManagerneither holds that amount of the borrow asset nor has given that amount of approval to the Fraxlend pair.Recommendation
Consider passing in the shares in
removeLeverageand calculate the flash loan amount from the shares to mimic FraxLend logic.Resolution
Peapods Team: Resolved in the following code change.
-
C-05 Critical Accounting Error In totalAvailableAssetsForVault Logic Error Resolved
Description
Proof of concept: PoC
totalAvailableAssetsForVaultshould return the available assets that aFraxlendPairvault can pull fromLendingAsssetVault.However, several accounting errors exist resulting in the under-calculation of available assets. Consider these two examples of a single whitelisted vault with 100% allocation:LendingAssetVault has 10 DAI of which 6 DAI has been withdrawn into FraxPair Vault
Example 1
totalAvailableAssetsForVaultwill return 0 when it should return 4 instead
Example 2
- LendingAssetVault has 10 DAI of which 4 DAI has been withdrawn into FraxPair Vault
totalAvailableAssetsForVaultwill return(10 - 4) - 4 = 2when it should return 6 instead
As
FraxLendPairrelies heavily on this function to obtain available assets fromLendingAssetVaultthis results in: 1) preventing furtherwhitelistWithdrawafter 50% of assets are withdrawn, 2) inflating utilization rate in FraxlendPair and increase interest charged to borrowers, 3) deposits allowed above thedepositLimit.Recommendation
Update the function to:
uint256 _overallAvailable = totalAvailableAssets(); uint256 _vaultMax = ((_totalAssets * _vaultMaxPerc[_vault]) / PERCENTAGE_PRECISION); uint256 _totalVaultAvailable = _vaultMax > vaultUtilization[_vault] ? _vaultMax - vaultUtilization[_vault] : 0; _totalVaultAvailable = _overallAvailable < _totalVaultAvailable ? _overallAvailable : _totalVaultAvailable; return _totalVaultAvailable;Resolution
Peapods Team: Resolved in the following code change.
-
C-06 Critical User Voting Shares Can Be Burned By Others Logic Error Resolved
Description
The update function can be called by anyone to update another user's stake position. The issue lies in
_updatewhich burns a user's share balance if the CBR had decreased from the time when the user first staked.Consider this example:
- Alice stakes 10 pTKNs and received 10 voting shares. CBR is 1.0
- CBR drops to 0.8 due to external factors
- Bob calls update on Alice's position. 2 shares are burned from her
If Bob was unable to call update on Alice's position, she could choose to do nothing and preserve her shares, potentially waiting for CBR to recover before performing more staking actions.
Furthermore, the CBR can be be drastically decreased with a flashloan from the pod, which would allow Bob to initially stake, flashloan to decrease CBR, and update Alice's position to burn all of her voting power.
Consequently, Bob can have all the voting power and claim all the rewards at the expense of other stakers.
Recommendation
Do not allow update to be called on another user's position and consider limiting direct balance checks. Also, do re-consider the design of the burn during _update as it will deter users from staking if their previous shares are burnt due to a change in CBR.
Resolution
Peapods Team: Resolved in the following code change.
-
C-07 Critical Inflation Attack In LendingAssetVault Logic Error Resolved
Description
Proof of concept: PoC
The classic inflation attack during the first deposit is possible in the
LendingAssetVault(LAV) through thedonatefunction.Attack scenario:
- LAV is created. attacker deposits 1 wei of assets and receives 1 share
- Attacker observes User depositing 100e18 of assets and frontruns with a donation of 100e18
assets
- User deposits 100e18 but receives 0 shares due to rounding down
- Attacker redeems 1 share and receives all assets in the vault (200e18 + 1 wei)
- User loses all deposits
Recommendation
Consider removing the donate function. Or else, consider other forms of protection against inflation attack, see https://blog.openzeppelin.com/a-novel-defense-against-erc4626-inflation-attacks
Resolution
Peapods Team: Resolved in the following code change.
-
C-08 Critical aspTKNOracle Can Be Manipulated With Donation Oracle Manipulation Partially resolved
Description
Proof of concept: PoC
In a previous audit, it was reported that the
aspTKNoracle could be manipulated through a donation ofspTKN(see https://hackmd.io/@tapir/SyxqzohUA#H-9-aspTKN-oracle-can-be-manipulated).While this has been fixed through the accounting of assets with a storage variable, this attack is still possible through donation of reward tokens which then gets compounded into
spTKNs(assets)._processRewardsToPodLpis called on every external user action, which checks forbalanceOfreward tokens in the contract, and then converts the reward tokens tospTKNs.Similar to the previously reported issue, although the attacker loses the donated tokens, they can manipulate the oracle pricing to borrow more tokens from the lending protocol, exploiting the system. Low liquidity pods are more susceptible to such attacks.
Recommendation
No straightforward solution as existing reward flow relies on transferring tokens to the
AutoCompounder. Consider re-designing the reward and compounding flow to ensure the oracle pricing cannot be easily manipulated through a donation of reward tokens.Resolution
Peapods Team: Resolved in the following code change.
Guardian Team: If the intermediate token is also a reward token, then the entire balance would be transferred. This allows an attacker to donate that intermediate token, and it would bypass the
maxSwapcaps that were implemented to remedy the issue originally. -
C-09 Critical Oracle Precision Error Due To Token Decimals Arithmetic Error Resolved
Description
The
getPricesfunction aims to return price in 18 decimals, which will be consumed by theaspTKNoracle and ultimately theFraxlendPaircontract to determine LTV and borrow amount.The issue lies in
_calculateBasePerSpTknwhere token decimals are correctly handled up till the calculation of_pairPrice18. As the variable name suggests, this is the price of the LP pair returned in 18 decimals:uint256 _pairPrice18 = (2 * _avgBaseAssetInLp18 * 10 ** ((_clT0Decimals + _clT1Decimals) / 2)) /
IERC20(_pair).totalSupply();However, if either token is not in 18 decimals, e.g. USDC: 6 decimals, then the price returned here will not be in 18
decimals.Assume token0is 18 decimals and token1 is 6 decimals, the math for decimals works out to be: 18 + ((18 + 6) / 2) - 18 = 12.The incorrect precision affects all downstream calculations and results in a wrong price consumed by
FraxlendPair. This ultimately affects LTV calculations which can lead to pairs being drained and users being liquidated unfairly.Recommendation
Change the calculations to:
uint256 _pairPrice18 = (2 * _avgBaseAssetInLp18 * 10 ** 18 / IERC20(_pair).totalSupply();Afterwards, remove the
_baseTDecimalslogic in_spTknBasePrice18.Resolution
Peapods Team: Resolved in the following code change.
-
C-10 Critical UniswapDexAdapter.swapV2Single Doesn't Work DoS Resolved
Description
Proof of concept: PoC
UniswapDexAdapteruses IUniswapV2Router02.sol to initiate calls to the V2 Router. In this interface theswapExactTokensForTokensSupportingFeeOnTransferTokensfunctions is expected to return an array with amounts.However, in the actual implementation of that function there are no values returned. This mismatch will result in a revert every time the function is called causing a DOS for the protocol.
Recommendation
Correct the interface to exclude the returned array.
Resolution
Peapods Team: Resolved in the following code change.
-
C-11 Critical Incorrect addInterest Interface Integration Resolved
Description
In the function
_updateInterestAndMdInAllVaults, which is called during every deposit/mint,addInterest()is called to trigger interest accrual on the FraxlendPair vault.However, the wrong interface is used which should pass
bool _returnAccountingas a function input. Therefore, the current implementation would always fail and DOS all deposits intoLendingAssetVault.Recommendation
Use the correct interface for
addInterest.Resolution
Peapods Team: Resolved in the following code change.
-
C-12 Critical pTKN Share Siphoning Via FlashMint Logic Error Resolved
Description
If a smart contract has
IFlashLoanRecipient::callback()or afallback()function, a malicious user can set them as the receiver of theflashMint()function. This will burn .1% of theirpTKNbalance.Since the only restriction on the amount of
pTKNs minted is that the total supply does not overflow, this can allow a user to burn the entirety of the contract's pTKN balance.This is profitable for the malicious user because burning
pTKNs increases the value of existingpTKNs since they are now backed by more underlying tokens.Recommendation
Charge the fee to the msg.sender instead of the
_recipient.Resolution
Peapods Team: Resolved in the following code change.
-
H-01 High Lack Of Access Control In redeemFromVault Access control Resolved
Description
The function
redeemFromVaultcan be called by an attacker who passes in an arbitrary_vaultand_amountSharesAs this function can be called by anyone, a griefing attack is possible to call
redeemFromVaultto denyLendingAssetVaultof yield (whenever there is available liquidity inFraxlendPair).Recommendation
Validate the input data and consider only allowing owner to call
redeemFromVault.Resolution
Peapods Team: Resolved in the following code change.
-
H-02 High Whitelist Actions Should Update All Vaults Logic Error Resolved
Description
Proof of concept: PoC
Asset availability changes during
whitelistDepositandwhitelistWithdraw. But, in these functions only the vault that is calling is updated with_updateAssetMetadataFromVault. Instead, all whitelisted vaults should be updated too which affects their interest calculations.Consider this example:
- LAV has 100 DAI
- Two Vaults A & B, with a 100% and 50% max allocation from LAV respectively.
- Vault A & B each have whitelist withdrawn 25 DAI, so A's utilization rate is 25 / 75 = 33% while B's is
25 / 50 = 50%
- 1 day passes
- A new borrower borrows 50 DAI from Vault A, so utilization rate increases from 33 to 100%.
- Available assets are also reduced for vault B, its utilization rate will increase to 25 / 25 = 100%
In the example above, the next time accrued interest in Vault B is calculated, it assumes a 100% utilization rate for the entire duration since the last update.
Instead, it should have been a 50% utilization (lower interest rate) for 1 day and then the 100% utilization rate after the borrow from Vault A. Borrowers will therefore always be incorrectly charged for interest across all whitelisted vaults.
Recommendation
whitelistDepositandwhitelistWithdrawshould update all vaults by calling both:_updateAssetMetadataFromVault(_vault)and_updateInterestAndMdInAllVaults(_vault)Resolution
Peapods Team: Resolved in the following code change.
Guardian Team: The recommendation was not implemented.
-
H-03 High Staking Pool Rewards Sniping Is Possible Logic Error Acknowledged
Description
As there is no penalty nor timelock for unstaking with the
StakingPoolToken, a user may claim rewards without actually staking by front-runningdepositRewardand do:stake -> depositRewards ->unstake.The user would immediately be eligible to claim rewards at the expense of other users who are staked.
Recommendation
Consider implementing a timelock or penalty for unstaking. Also, consider using time-weighted reward distribution.
Resolution
Peapods Team: Acknowledged.
-
H-04 High Loss Of Rewards Due Two Step Swap Failure Logic Error Resolved
Description
When compounding rewards in
_processRewardsToPodLp, some rewards may require a two-step process to swap to the paired LP token.However in
_swapV2, only a single swap is performed. Therefore, if a second swap is required, it will not be performed and the intermediate token received will remain in the contract.These rewards will not be compounded and users will lose potential yield. An identical error was found in
Zapper.sol.Recommendation
Perform a second swap for tokens which require a two-step process.
Resolution
Peapods Team: Resolved in the following code change.
-
H-05 High Rewards Are Lost For aspToken Logic Error Resolved
Description
Users stake their LP tokens because
spTokensaccrue rewards in different tokens. The criteria for such a token is to either be a part of the whitelisted ones or be the specified token for the givenTokenRewardscontract.When
spTokensare deposited toaspTokens, theAutoCompondingPodLpcontract receives the rewards and uses_processRewardsToPodLpto convert them to newspTokens. However, it does so only for the whitelisted tokens and ignores the specific reward token for theTokenRewards.In result, any rewards accumulated in the specific token that is not part of the whitelisted tokens will not be correctly distributed to the holders of the
aspTokens.Recommendation
In addition to the whitelisted tokens collect the rewards from the
rewardsTokenas well.Resolution
Peapods Team: Resolved in the following code change.
-
H-06 High Malicious Function Input In Add/Remove Leverage Logic Error Resolved
Description
Proof of concept: PoC
In
LeverageManager, theaddLeverageandremoveLeveragefunctions allows an attacker to pass in a malicious contract as_selfLendingPairPodand_dexAdapterrespectively.This opens up the surface for attacks and reentrancy. It could be used for example to avoid payment of close fees during
removeLeverage:- Alice calls
removeLeveragepassing in malicious contract as _dexAdapter - Malicious contract is called in
_swapPodForBorrowToken - Malicious contract does the swap with a DEX but does not return any pod tokens, instead
transferring directly to Alice 4. As no pod tokens remain in
LeverageManager, no close fees are applied at the end ofcallbackand the protocol loses revenue.Recommendation
Perform validation on the
_selfLendingPairPodand_dexAdapterinputs. Do not allow users to provide arbitrarydexAdapteraddresses, and use everyWeightedIndex's own immutabledexAdapter.Additionally, consider minimizing the use of arbitrary inputs in public functions, as they can expand the attack surface and introduce potential vulnerabilities, such as reentrancy risks
Resolution
Peapods Team: Resolved in the following code change.
Guardian Team: Because there is no validation on
_overrideLendingPair, users can pass in an arbitrary, malicious contract for this address. The malicious contract can return zero for the_podAmtRemaining, which the protocol relies upon to calculate the_closeFeeAmt. - Alice calls
-
H-07 High Reward Sniping With AutoCompounder Logical Error Acknowledged
Description
When token rewards such as ARB are deposited into the
TokenRewardscontract, a large portion of it is expected to be transferred toAutoCompoundingPodLpwhich is a large holder ofspTKNs.Then the next time a user interacts with
AutoCompoundPodLp,_processRewardsToPodLpis called to compound the reward tokens into more LP, benefiting existing holders ofaspTKN.Recommendation
As there is no penalty nor timelock for deposits and withdrawals, a user may front-run the deposit of reward tokens by depositing into
AutoCompoundingPodLpso as to claim some of the rewards, and then back-run the deposit of rewards and withdraw fromAutoCompoundingPodLp. Such extractive behavior results in less rewards for other users who are staked for the long term.Resolution
Peapods Team: Acknowledged.
-
H-08 High removeLeverage Inaccurate Share Calculation Logic Error Resolved
Description
When removing leverage, the amount of shares to repay to the Fraxlend vault is calculated based off the amount of the borrowed assets that are desired to be repaid. This is accomplished by calling
toShares().However, this calculation is done before any call is made to the Fraxlend vault. Because of this, the calculation of shares to be repaid does not account for any interest that has been accrued.
If enough time has passed or the interest rate is high enough, it will lead to an inaccurate amount of shares to pay off based on the amount of assets provided. This will lead to a revert in
repayAssets(), due to insufficient balance and DoSremoveLeverage()Recommendation
Prior to the calculation of
_borrowSharesToRepayinremoveLeverage(), calladdInterest()on the vault.Resolution
Peapods Team: Resolved in the following code change.
-
H-09 High Liquidators Can Avoid Bad Debt Socialization Logic Error Resolved
Description
Proof of concept: PoC
During liquidation of a borrower, bad debt is only realized when the borrower has zero leftover collateral (see
FraxlendPairCore.sol: 1130). This allows a liquidator to liquidate just enough shares such that a dust amount of collateral is left behind.Thereafter, there might be little to no incentive for other liquidators to liquidate the borrower as the gas cost to do so exceeds the collateral value.
As a result, the bad debt is not socialized and lenders may exit the system without any losses. The liquidator himself may be a lender and therefore incentivized to exploit this loophole.
Recommendation
Consider implementing a threshold for collateral remaining after liquidation, such that a liquidator must leave sufficient collateral behind if doing a partial liquidation.
Resolution
Peapods Team: Resolved in the following code change.
-
H-10 High DoS In _withdrawToVault Due To Underflow DoS Resolved
Description
Proof of concept: PoC
When the Fraxlend pair does not have enough assets to lend, necessary amounts are transferred from the
LendingAssetVault(LAV), and Frax shares are minted toLAV. The opposite occurs when removing leverage: Frax shares are burned from theLAV, and assets are transferred back to the vault.The share amounts to mint and burn are always in favor of the protocol. Frax shares that the
LAVreceives during borrowing are rounded down in_depositFromVault, while Frax shares burned during repayment are rounded up in_withdrawToVault, as expected.In the
_repayAssetfunction, the_withdrawToVaultis called with the asset amounts to repay. However, this causes DoS in certain situations. When attempting to transfer the entire utilized amount back (_extAmount == _externalAssetsToWithdraw), the share amount is rounded up in_withdrawToVault, causing the function to revert due to the insufficient balance error, as theLAVholds 1 fewer shares.Recommendation
Check the share balance of the
LAVbefore burning, and burn shares up toLAVbalance.Resolution
Peapods Team: Resolved in the following code change.
-
H-11 High Assets Can Be Borrowed/Repaid While Paused Logic Error Resolved
Description
borrowAssetandrepayAssetfunctions may be paused by admin but can be bypassed throughleveragePositionandrepayAssetWithCollateralwhich performs borrow/repay actions. This loophole could be exploited by attackers while the protocol is paused.Recommendation
Consider extending the pause effects to the
leveragePositionandrepayAssetWithCollateralfunctions.Resolution
Peapods Team: Resolved in the following code change.
-
H-12 High Lack Of _selfLendingPairPod Validation Validation Resolved
Description
When calling
addLeverage(), a user is allowed to input whatever_selfLendingPairPodthey desire, without any validation. This will allow a user to successfully add leverage with a self lending pod that is different from the pod they used to create their position.However when they attempt to withdraw, they will be forced to use the pod associated with their NFT. This will prevent a user from removing leverage on their position, and force the position to be open indefinitely.
Since positions are transferable, a malicious user could sell their position to an unsuspecting user. This will lead to a user being stuck with a worthless position.
Recommendation
Validate that the
_selfLendingPairPodpassed in toaddLeverage()is the same pod associated with their NFT. Alternatively, allow users to update theselfLendingPodthey have associated with their position NFT.Resolution
Peapods Team: Resolved in the following code change.
-
H-13 High Flash Mint Manipulates Supply Logical Error Resolved
Description
WeightedIndex.flashMint()allows anyone to sandwich protocol actions by manipulatingtotalSupply().There are a lot of parts in the protocol that depend on
totalSupply:WeightedIndex.convertToShares()WeightedIndex.convertToAssets()ConversionFactorPTKN._calculateCbrWithDen()
Recommendation
Either change the code that depends on
totalSupplyor reconsider the existence of theflashMintfunction.Resolution
Peapods Team: Resolved in the following code change.
-
M-01 Medium Insufficient Token Amounts Lead To Swap Error Logic Error Partially resolved
Description
When compounding rewards in
_pairedLpTokenToPodLp,pairedLpTokensare swapped to pTKN via Uniswap'sswapV2Single. However, if too little tokens are provided it may revert in Uniswap V2 with 'INSUFFICIENT_INPUT_AMOUNT' orINSUFFICIENT_OUTPUT_AMOUNT.This could be caused by a balance of 1 wei of
pairedLpTokens. which after halving becomes 0 pTKNs. This small balance could be easily donated by an attacker looking to DOS the contract, or simply caused by leftover tokens from a previous transaction.As processing of rewards is called by every major flow, reverting could have serious implications such as preventing users from removing leverage and result in liquidations.
Recommendation
Verify the balance of tokens before calling the swap function to avoid reverts.
Resolution
Peapods Team: Resolved in the following code change.
-
M-02 Medium Underflow In _updateAssetMetadataFromVault Arithmetic Error Partially resolved
Description
Proof of concept: PoC
Whenever
_updateAssetMetadataFromVaultis called, a vault's Collateral Backed Ratio (CBR) is updated and compared against its previous value. If the CBR decreased from the previous update, then the vault's utilization is also decreased based on_vaultAssetRatioChange.The issue occurs when
_vaultAssetRatioChangeis greater than 100%. This leads to an underflow when updatingvaultUtilization[_vault].Consider this example:
- Vault's CBR decreased from 100e27 to 49e27
_vaultAssetRatioChange: (100e27 * 1e27 / 49e27 ) - 1e27 = 1.04e27vaultUtilization[_vault]: 100 - (100 * 1.04e27 / 1e27) = underflow
Such a drastic drop in CBR is unlikely but possible in vaults with obscure tokens (as Peapods is designed to be used permissionlessly). A revert in the update for one vault will cause DOS in all whitelisted vaults, and prevent liquidations in
FraxlendPairvaults.Recommendation
Handle the case when
_vaultAssetRatioChangeis greater than 100% to avoid the underflow.Resolution
Peapods Team: Resolved in the following code change.
-
M-03 Medium Precision Loss Leads To Reverts In TokenRewards Precision Resolved
Description
When a user receives shares from
TokenRewardsfor the first time,_cumulativeRewardsis called to update the user's rewards mapping with anexcludedamount. This amount will be used to calculated the user's share of future rewards.The issue occurs in
_cumulativeRewards: (_share * _rewardsPerShare[_token]) / PRECISIONBy rounding down the calculation, it is essentially excludes the user from 1 wei less of rewards, implying the user earned 1 wei more rewards. This will overtime lead to insufficient balance of rewards to transfer out, and DOS all functionality of the contract when that happens.
Recommendation
Always round up the calculation in
_cumulativeRewardswhen calculating the excluded amount.Resolution
Peapods Team: Resolved in the following code change.
-
M-04 Medium Positions Are Lost When lendingPair Is Changed Logical Error Resolved
Description
When a new position is initialized, its lendingPair is set to the lending pair for the pod configured by the owner of the contract. However, adding and removing leverage always use the most recent
lendingPairfor the pod of the position instead of the pair at the time of it creation.This results in positions being lost when the owner changes the pair by calling setLendingPair because _removeLeverage will make the custodian remove collateral from the new pair where it doesn't have any.
Recommendation
Use
positionProps.lendingPairinstead of the latest lending pair when adding/removing leverage.Resolution
Peapods Team: Resolved in the following code change.
-
M-05 Medium Flash Loan Repayments Fail During addLeverage Logical Error Resolved
Description
When adding leverage, the user provides pod tokens, and the corresponding
pairedLpTokensare flash loaned. At the end of the transaction, this flash loan is repaid by borrowingpairedLpTokensfrom Fraxlend.However, the flash loan fee is not accounted for when borrowing from Fraxlend. The borrow amount from Fraxlend equals the flash loan amount unless users provide a greater
overrideBorrowAmt.The Natspec comments regarding this variable is: ”Override amount to borrow from the lending pair, only matters if max LTV is >50% on the lending pair”.
Since providing
overrideBorrowAmtis not mandatory and there are no restrictions, the borrow amount from Fraxlend will usually be equal to_props.pairedLpDesiredin most cases. However, this amount is equal to_d.amount, which is smaller than_flashPaybackAmt, causing the transaction to revert.Recommendation
The minimum borrow amount from Fraxlend to repay the flash loan should be
_props.pairedLpDesired + _d.fee.Resolution
Peapods Team: Resolved in the following code change.
-
M-06 Medium Borrowers Pay For Paused Interest Logical Error Resolved
Description
FraxlendPairhas a function pause which pauses all actions in the pair and a functionpauseInterestwhich pauses interest accrual.When interest is paused, new interest will not be accumulated and that's expected. However, once unpaused, the current borrowers will have to pay interest for the duration from the moment the protocol was paused until the current block.
Since the protocol may function normally and have just it interest paused, that means new lenders and borrowers may come and go. This will cause a huge interest misaccounting.
For example, interest is paused on Monday. Some borrowers leave the pair. and new ones enter it right before it's unpaused the next Monday. Since the
lastUpdatedtimestamp will be the first monday, the new borrowers will immediately owe interest for that one week they were not even part of the pair.Recommendation
Update the timestamp when
pauseInterest(false) is calledcurrentRateInfo.lastTimestamp =uint64(block.timestamp);Resolution
Peapods Team: Resolved
-
M-07 Medium Improper Slippage And Deadline During Deposit Logic Error Acknowledged
Description
When calling
deposit, a slippage of0and a deadline ofblock.timestampis passed to_processRewardsToPodLp. Using0for slippage is dangerous as MEV bots could sandwich the swap to steal tokens.Similarly, setting block.timestamp as deadline is ineffective as the transaction could sit in the mempool until it's ready to be processed, at which time block.timestamp is set, therefore offering no protection from sandwich attacks.
Recommendation
Allow user to input slippage and deadline parameters when depositing.
Resolution
Peapods Team: Acknowledged.
-
M-08 Medium AutoCompoundingPodLp Is Not EIP Compliant ERC4626 Resolved
Description
Some EIP-4626 compliance issues have been observed in the
AutoCompoundingPodLpcontract: 1. According to EIP-4626, the mint function must mint exactly the user-inputted amount of shares, and the withdraw function must transfer exactly the specified assets amount. However, these functions mint or withdraw fewer tokens due to rounding down twice during the action flows.During the withdraw function in the codebase:
- User provides _assets amount.
- It is converted to shares with
convertToSharesfunction, which rounds down. - Then, the internal
_withdrawfunction is called with this shares amount. - In this internal function, the shares amount is converted to assets again with
convertToAssets,
which also rounds down.
As a result, the actual assets amount transferred to the user is not the same as the user-provided amount. The same issue can be observed in the mint function as well. 1. According to EIP-4626, withdraw and redeem functions must support transaction flows where the msg.sender has an approval from the owner. However, in the codebase, withdrawals and redeems can only be performed by the owner, and approved users cannot execute these actions.
Recommendation
Update mint and withdraw functions to comply with the EIP specification and ensure the exact amounts are transferred. Also, update withdraw and redeem function to support approved users.
Resolution
Peapods Team: Resolved in the following code change.
-
M-09 Medium Rewards Not Updated Prior To Fee Change Logic Error Resolved
Description
Owner can set a new protocol fee via
setProtocolFee. However, because rewards are not updated prior to the fee change, the new fee will apply to previously accrued rewards.For example, 100 PEAS in rewards were accrued since the last update. Fees are increased from 1 to 2%. An additional 1% of fees are unjustly applied to the accrued rewards.
Recommendation
In
setProtocolFee, call_processRewardsToPodLpbefore settingprotocolFeeto the new fee.Resolution
Peapods Team: Resolved in the following code change.
-
M-10 Medium Vault Whitelist Can Be Set One Above Max Logic Error Resolved
Description
When whitelisting a new vault with
setVaultWhitelist, even ifmaxVaultvalue has been reached, the new vault is still added to the whitelist due to using<= maxValueinstead of "< maxValue" in the check.Recommendation
Change from
require(_vaultWhitelistAry.length <= maxVaults, 'M');torequire(_vaultWhitelistAry.length < maxVaults, 'M');Resolution
Peapods Team: Resolved in the following code change.
-
M-11 Medium Inflated Admin Fee Logic Error Resolved
Description
When a swap fails in
depositFromPairedLPToken(), the amount that can be used in the next attempt is halved from the attempted swap amount.The next time
depositFromPairedLPToken()is called the fee will be based off the current balance of the contract, which will include the balance from the failed swap.When
_swapForRewardsis called, it will reduce the_amountInand_amountOutbut still charge the admin fee based on the balance of the contract. This results in excessive admin fees paid over the multiple swaps.Recommendation
Adjust the admin fee to match the actual amount that has been swapped.
Resolution
Peapods Team: Resolved in the following code change.
-
M-12 Medium Same Heartbeat For Multiple Oracles Oracles Resolved
Description
getPriceUSD18makes requests to a base and quote asset oracles. If any of them has been updated more thanmaxOracleDelayseconds ago,_isBadDatawill be set totrue.Since not all feeds have the same heartbeat, if the oracle uses two feeds with different ones, it may happen that one of the prices is stale, but it's accepted as a valid one.
Recommendation
Use two different delay variables - one for the base feed and one for the quote feed.
Resolution
Peapods Team: Resolved in the following code change.
-
M-13 Medium No Circuit Breaker Checks In ChainlinkOracle Oracles Resolved
Description
getPriceUSD18returns_isBadDataif the oracle price is stale. However, it doesn't consider the price going outside of the price range for the oracle's aggregator.The price will be capped between
minAnswerandmaxAnswerof the aggregator. This will result in a wrong price being used in the protocol.Even though most feeds have disabled their circuit breaker feature, there are still some that haven't, for example CVX/ETH
Recommendation
If the price goes outside the aggregator range, set
_isBadDatato trueResolution
Peapods Team: Resolved in the following code change.
-
M-14 Medium DOS Of Borrow & Redeem Logic Error Resolved
Description
Each time borrow or redeem is called in
FraxlendPairCore, if there are insufficient local assets, then_depositFromVaultis called to pull assets from the vault.However, in
_depositFromVaultthere is a check:if (depositLimit < _totalAsset.totalAmount(address(externalAssetVault))) revertExceedsDepositLimit();This check prevents the deposit from vault if the vault's allocated assets to the
FraxlendPairexceeds the deposit limit.Consider this example:
- FraxlendPair has a deposit limit of 10 ETH
- Vault has 30 ETH and allocates 50% to
FraxlendPair - Borrow/redeem actions cannot go through as the
depositLimitcheck would always fail
Recommendation
Consider removing the
depositLimitcheck from_depositFromVault. Instead, inLendingAssetVault, apart from percentage based allocations to aFraxLendPairvault, consider checking for the vault'sdepositLimittoo to avoid over-allocation.Resolution
Peapods Team: Resolved in the following code change.
-
M-15 Medium Feeds With > 18 Decimals Are Problematic Math Resolved
Description
The price returned by the oracle is adjusted to 18 decimals with the following computation:
_price18 = uint256(_price) * (10 ** 18 / 10 ** _decimals);Notice that the division here happens before the multiplication. This means that if the decimals of the feed are more than 18, the price will be rounded to 0 causing big problems for the assets pricing.
Recommendation
Multiply price by 1e18 and divide afterwards.
Resolution
Peapods Team: Resolved in the following code change.
-
M-16 Medium addLiquidity Fails For Fee-On-Transfer Tokens Logic Error Acknowledged
Description
When
addLiquidityV2is called, an amount of_pairedLPTokenis transferred into the contract, and the same amount is used to add liquidity in theDEX_HANDLER.However, if the
pairedLPTokenis a Fee-on-Transfer token, then the amount received would be less than expected due to a fee. Therefore, theDEX_HANDLER.addLiquiditycall could fail due to insufficient tokens.Recommendation
Use actual balance of
pairedLPTokenswhen callingDEX_HANDLER.addLiquidity.Resolution
Peapods Team: Acknowledged.
-
M-17 Medium Single Token Pod Assumption Logic Error Acknowledged
Description
When getting price from the oracle, if the base token is a pod,
_getBaseTokenInClPoolis called. There it gets all assets from the pod and assumes the first token in the array is the base token.However, pods were designed to be multi-asset and able to be created permissionlessly. Therefore, if the first asset is not the intended base token, then serious integration issues would occur.
Furthermore in the function
_debondFromSelfLendingPod, an assumption is made that theselfLendingPodhas only one token. If ever this assumption is broken, there would be integration errors withFraxlendPairand a possibility of stuck tokens inLeverageManagerafter debonding.Recommendation
In the constructor, similar to how
UNDERLYING_TKNis defined, store the intended underlying token for the base (pod). Furthermore, consider handling the case where aSelfLendingPodhas multiple tokens.Resolution
Peapods Team: Acknowledged.
-
M-18 Medium Wrong Price Calculations If T0 Is baseToken Protocol Resolved
Description
When the base token is one of the two tokens in the
UNDERLYING_TKN_CL_POOL, it has to betoken1. That's because_pricePTKNPerBase18is calculated based on whether or not the base token is part of that pool by checking if it'stoken1.This means for pools where the base token is
token0the price will be wrongly flipped.Recommendation
If the base token is included in the pool pair, always make sure it's the
token1.Resolution
Peapods Team: Resolved.
-
M-19 Medium Improper Deadline For Fraxlend Swaps Logic Error Resolved
Description
Similarly to
M-01,FraxlendPairCore::repayAssetWithCollateral()&FraxlendPairCore::leveragedPosition()does not allow a user to set theblock.timestampfor their swap.This exposes users to MEV sandwich attacks, and can cause them to lose out on funds that would have been used to repay their debt.
Recommendation
Allow users to input the deadline for the swaps.
Resolution
Peapods Team: Resolved in the following code change.
-
M-20 Medium FraxVault Incompatible With Non-Standard Tokens Logical Error Acknowledged
Description
The
FraxlendPaircontracts do not support non-standard tokens such as rebasing or fee-on-transfer tokens, whose balance changes during transfers or over time. If the Peapods team expects to support these tokens, then there will be accounting issues when interacting withFraxlendPaircontracts.Recommendation
Verify the amount of tokens transferred to the contracts before and after the actual transfer to infer any fees/interest.
Resolution
Peapods Team: Acknowledged.
-
M-21 Medium removeLeverage Could Fail For Self-Lending Pairs Logical Error Resolved
Description
This bug was reported in a previous audit but does not seem to be fixed (see
https://hackmd.io/@tapir/SyxqzohUA#M-6-Removing-leverage-will-likely-fail-if-the-pod-token-needs-
to-be-sold-for-the-borrowed-token-in-a-self-lending-scenario)
The issue remains that there is no natural Uniswap V2 market that exists to swap pod tokens for borrowed assets (from
FraxlendPair), when the pair is self-lending.Furthermore, during
_swapPodForBorrowTokenin theremoveLeverage flow, the entire amount of pod tokens received is passed asamountInMaxfor the swap --resulting in zero slippage protection.This could allow an attacker to deploy a pool to take advantage of this scenario and steal all pod tokens from a user.
Recommendation
Implement proper slippage protection for the swap, and ensure that a healthy Uniswap V2 market exists for the swapping of self-lending pairs.
Resolution
Peapods Team: Resolved in the following code change.
-
M-22 Medium USDC Blacklist Prevents Transfers Logic Error Resolved
Description
When the staking pool token is transferred it will call
_setShares(), which will lead to_distributeReward()being called.Inside of
_distributeReward(), it will loop through the reward tokens and transfer any rewards to the users. If a user becomes blacklisted from using USDC,_distributeReward()will revert.This, in turn, will lead to the tokens being stuck in the users wallet and become untransferable. Additionally, this prevents a user from calling
claimRewards(). They will not be able to claim any other rewards tokens earned outside of USDC.Recommendation
Instead of pushing rewards to users automatically, rely on them claiming the rewards themselves and allow them to specify which token they would like to claim.
Resolution
Peapods Team: Resolved in the following code change.
-
M-23 Medium Uniswap Ticks Rounding Math Resolved
Description
Multiple contracts in the system -V3TwapUtilities, V3AerodromeUtilities, UniswapV3SinglePriceOracle - fetch the TWAP price of a given asset. When calculating the TWAP, the delta of two CL ticks is divided by a given time period.
Since solidity truncates when it divides, for negative ticks the result will be rounded up instead of rounded down resulting in a different price. For reference, see how is this handled in OracleLibrary.
Recommendation
Implement the same solution as in
OracleLibraryResolution
Peapods Team: Resolved in the following code change.
-
M-24 Medium Arbitrage From Deviation In Oracle Price Oracles Acknowledged
Description
The maximum a user can borrow is determined by the exchange rate returned by the oracle. However, all oracles are susceptible to front-running as their prices tend to lag behind an assets real price.
For example, Chainlink oracles are updated after price crosses a threshold while Uniswap V3 TWAP returns price over past X blocks. An attacker could exploit the difference between the price reported by an oracle and the asset's actual price to gain a profit by front-running the oracle's price update.
The likelihood of this condition is increased for Peapods due to the multiple layers that an underlying asset is wrapped in.
Consider this example:
- Fraxlend Vault has a high
maxLTVof 95%, with the collateral asaspTKN(pPEAS-WETH) and asset
(WETH)
- 1
aspTKNis currently worth 0.002 WETH - Price of
aspTKNdrops while WETH price increases, such that 1aspTKNshould be worth 0.0015
WETH
- Due to the lag in oracle update, price is not updated yet
- Attacker sees this opportunity and front-runs the oracle update to:
- Deposit 100
aspTKNs - Max borrow 0.19 WETH (95% LTV)
- Afterwards, the oracle price is updated to 1
aspTKN= 0.0015 WETH - The attacker's position is now unhealthy as his collateral is worth less than the loan amount
- Attacker back-runs the oracle update to liquidate himself:
- To seize 100
aspTKNhe repay 0.15 WETH - Gains back his original collateral plus 0.04 WETH
All profits gained result in bad debt socialized among lenders.
Recommendation
Consider adding a borrowing fee to mitigate arbitrage opportunities.
Resolution
Peapods Team: Acknowledged.
- Fraxlend Vault has a high
-
M-25 Medium mint In Fraxlend Rounds In Users’ Favour Rounding Resolved
Description
The
mintfunction in theFraxlendPairCorecontract rounds down in favour of the users and users pay fewer assets for corresponding shares.Recommendation
Round-up in favour of the protocol.
Resolution
Peapods Team: Resolved in the following code change.
-
M-26 Medium Oracle Incompatible With Non-Standard Tokens Underflow Resolved
Description
The current logic in getPrices will underflow when the BASE token has more than 18 decimals: uint256 _priceOne18 = _priceBaseSpTKN * 10 ** (18 - IERC20Metadata(BASE_TOKEN).decimals());
This will entirely prevent oracle compatibility with borrow tokens that have more than 18 decimal precision, which is problematic in Peapods which is a permissionless system.
Recommendation
Query the decimals first and if it is greater than 18, subtract 18 from the base token’s decimals.
Resolution
Peapods Team: Resolved in the following code change.
-
M-27 Medium Fee-on-transfer Tokens Are Not Supported Logic Error Acknowledged
Description
Fee on transfer tokens are not supported correctly for:
- pod underlying token -
bond()doesn't check the received amount of the transfer and mints shares
based on the initial amount
- pod paired token -
LeverageManagerassumes it has received the whole amount of the flashloaned
token
Recommendation
Consider supporting fee on transfer tokens
Resolution
Peapods Team: Acknowledged.
- pod underlying token -
-
M-28 Medium Missing Check For Sequencer Downtime Oracle Manipulation Resolved
Description
Chainlink recommends that all Optimistic L2 oracles consult the Sequencer Uptime Feed to ensure that the sequencer is live before trusting the data returned by the oracle.
See https://docs.chain.link/data-feeds#l2-sequencer-uptime-feeds
If the Arbitrum sequencer goes down for example, oracle data will not be updated and could become stale. Attackers could take advantage of the stale prices and carry out attacks, such as borrowing more against their collateral's true value.
Recommendation
Follow the code example of Chainlink:
https://docs.chain.link/data-feeds/l2-sequencer-feeds#example-code
Resolution
Peapods Team: Resolved in the following code change.
-
M-29 Medium ASP Insufficient Liquidity DOS DoS Resolved
Description
When
AutoCompoundingPodLpswaps the paired tokens for pod tokens, it adds them as liquidity. However, if the amounts are too small, this will result in minting 0 liquidity and a revert withINSUFFICIENT_LIQUIDITY_MINTED.Recommendation
Just like in the
DecentralizedIndex, consider rewards only if they exceed a given minimum.Resolution
Peapods Team: Resolved in the following code change.
-
M-30 Medium User's Lockup Period Should Not Change Midway Logic Error Resolved
Description
Owner can set the
lockupPeriodvariable viasetLockupPeriod. However, this would affect all users who are already staked with the previouslockupPeriodvalue, which would be unfair.Recommendation
When a user stakes, consider storing the current
lockupPeriodvalue in their ownStakestruct. And use that value when checking for unlock time inunstake.Resolution
Peapods Team: Resolved in the following code change.
-
M-31 Medium Asp Rewards Can Be Sandwiched Logic Error Resolved
Description
The idea of
AutoCompoundingPodLpis to swap the accumulated rewards into paired lp tokens and use them to generate new spTokens.When the current reward token of the asp doesn't match the reward token for its pod, 0 slippage is used for the swap so anyone can sandwich the transaction to benefit from it which will result in a loss for the asp holders.
The following can be executed in one transaction:
- swap
- process rewards
- swap again
Recommendation
Consider adding an adequate slippage parameter to the swaps and a
try/catchas well to not introduce a new way of DOS-ing the asp.Resolution
Peapods Team: Resolved.
-
M-32 Medium Incorrect Autocompounding Asset And Shares Conversions Logic Error Resolved
Description
In
AutoCompoundingPodLp::withdraw(), it will convert the amount of assets to shares prior to calling_processRewardsToLp(). Then it will convert the shares back to assets.In between conversions, the
_cbr()is likely to increase due to the increase in total assets that will occur when_processRewardsToLp()is called. This will lead to a larger output amount of assets than requested to be withdrawn.When
AutoCompoundingPodLp::mint()is called, it will convert the amount of shares to assets. Then call_processRewardsToLp(), and proceed to convert the amount of assets back to shares.This will have the inverse effect, and provide a smaller amount of shares for the deposited user than requested. Ultimately, both of these functions will provide users with different amount of shares and assets respectively than expected.
Recommendation
_processRewardsToLp()should be called in the beginning before any conversions.Resolution
Peapods Team: Resolved.
Guardian Team: In functions
withdrawandredeemasset-share calculations are made with a stale cbr since conversions are made before calling_processRewardsToLp(). This will lead to incorrect outputs for the user. -
M-33 Medium LendingAssetVault Asset/Share Conversion Error Logic Error Resolved
Description
Similarly to M-20, the
LendingAssetVault::withdraw()andLendingAssetVault::mint()perform asset and share conversions with an update to_cbr()taking place in between.This takes place with the call to
_updateInterestAndMdInAllVaults()happening in_withdraw()and_deposit(). This will lead to a similar scenario where the accounting for assets will be incorrect for withdrawals and the shares will be incorrect for mints compared to the amounts requested.Recommendation
_updateInterestAndMdInAllVaults()should be called in the beginning rather than between conversions.Resolution
Peapods Team: Resolved in the following code change.
Guardian Team: LendingAssetVault mint performs
convertToAssetsbefore updating for interest. This will lead to users minting not getting the amount of shares requested, which is against ERC4626 spec and unexpected for users. -
M-34 Medium VotingPool Pods Priced Equally Protocol Acknowledged
Description
Currently, all pods are priced equally in the
VotingPoolcontract (assuming the conversion factor is 1:1). This means users can stake cheap pods and receive the same amount of voting tokens they would have received with more expensive pods.For example, a pod with DAI as underlying token and a pod with WETH as underlying token would both be priced equally if their conversion factors are the same.
There may also be a situation where the pod with DAI token has its conversion factor higher - this will lead to the DAI pod minting more voting tokens than the WETH one.
Recommendation
Either implement another pricing mechanism or make sure to only use pods with close price.
Resolution
Peapods Team: Acknowledged.
-
M-35 Medium VotingPool Incompatible With Non-Standard Tokens Integration Acknowledged
Description
If the underlying token of a pod is a Fee-on-Transfer token, the accounting when staking in
VotingPoolwould be inaccurate. The balance of tokens after fees should be accounted for instead. Multi-asset pods are also incompatible as_calculateCbrWithDenassumes_asset[0]is the only token in the pod.Recommendation
As Peapods is expected to be permissionless and work with all types of tokens, handle such non-standard tokens accordingly.
Resolution
Peapods Team: Acknowledged.
-
M-36 Medium redeemFromVault DOS DoS Resolved
Description
When
LAV.redeemFromVault()is called,FraxlendPair.redeem()is called and the returned value (the assets received) are subtracted from thevaultUtilization.Since
vaultUtilizationis adjusted by dividing in_updateAssetMetadataFromVault,vaultUtilizationmay end up being 1 wei less than the received assets. Because of this theredeemFromVaulttransaction will fail.Recommendation
Subtract the minimum between the received assets and
vaultUtilization.Resolution
Peapods Team: Resolved in the following code change.
-
M-37 Medium Vaults' Utilization Not Updated After Bad Debt Logic Error Resolved
Description
During liquidation in
FraxlendPair, if bad debt was incurred,whitelistUpdateis called which updates of all whitelisted vaults (except the calling vault).Then the bad debt is realized via:
totalAsset.amount -= _amountToAdjustbeforeLendingAssetVaultis updated again to reduce its own internal tracking for_totalAssets.Instead, the
whitelistUpdateof all vaults should be performed after the bad debt is realized inFraxlendPair. As a result, all other vaults will assume a higher amount of available assets (did not account for lost assets from bad debt) and charge a higher interest.Recommendation
Call
whitelistUpdateat the end of liquidate after all state changes have been made inFraxlendPairResolution
Peapods Team: Resolved in the following code change.
-
M-38 Medium Same TWAP For Multiple V3 Pools Oracles Acknowledged
Description
spTKNMinimalOracleuses the sametwapIntervalfor two different pools. Depending on the available liquidity on the two pools and the assets volatility, one twap period may not be sufficient to get accurate prices for both pools.Recommendation
Consider having a different interval for each pool.
Resolution
Peapods Team: Acknowledged.
-
M-39 Medium _getPairedTknAmt 0 Bond Slippage Logic Error Acknowledged
Description
LeverageManager._getPairedTknAndAmtuses 0 as slippage parameter when bonding tokens. In result, the receivedpTokenamount may be too small that it causes significant loss for the user.Recommendation
Allow the user to input their slippage.
Resolution
Peapods Team: Acknowledged.
-
M-40 Medium getPairAccounting Includes LAV Assets Logic Error Resolved
Description
FraxlendPair.getPairAccounting includes the unlent assets from the LAV. External integrations that depend on that function will receive wrong information.
For example, if they calculate the value of a single share using the output of that function, their result will be wrong because they account for assets not present in the pair.
Recommendation
Exclude the LAV assets.
Resolution
Peapods Team: Resolved in the following code change.
-
M-41 Medium Donation Increases Share Supply Logical Error Resolved
Description
Proof of concept: PoC
Function
donateaims to increase thetotalAssetsof theLendingAssetVaultwithout increasing thetotalSupplyof shares, hence a donation.The issue is that
_burn(address(this), convertToShares(_assetAmt));converts the_assetAmtto shares after_deposit(_assetAmt, address(this));already minted shares, so the newly calculated share amount to burn will be less than the calculated and minted shares in_deposit.Ultimately, function
donateincreases thetotalSupplyeven though it is not meant to.Recommendation
Burn the entire added supply post-deposit.
Resolution
Peapods Team: Resolved in the following code change.
-
M-42 Medium Not Updating Pairs Will Break LAV Protocol Resolved
Description
When a deposit or withdraw happens in the
LendingAssetVaultall pairs should be updated because thetotalAssetsare increased unilaterally which affects the pairs.There is a function which allows setting
_updateInterestOnVaultsto be set to false. If so, any updates to all pair at once will be skipped. This means users can manipulate pairs' utilization rates by depositing/withdrawing.Recommendation
_updateInterestOnVaultsis meant to be set to false if updating all vaults start causing OOG errors. Given the problem it creates and the facts that there is a limit to the maximum pairs that can be connected to a vault and that pairs can also be removed, the removal of_updateInterestOnVaultis best.Resolution
Peapods Team: Resolved in the following code change.
-
M-43 Medium Leverage Doesn't Work When FOT Is Enabled DoS Acknowledged
Description
Proof of concept: PoC
Pods have a property
hasTransferTaxwhich when enabled, a fee-on-transfer is taken from the value to be transferred and the recipient receives less tokens.This is a problem for the LeverageManager.addLeverage() function because it assumes the whole amount has been received and assigns that amount to
LeverageFlashProps.podAmount.Later, when the
podAmountis requested from theIndexUtils, the transaction will revert because theLeverageManagercontract doesn't have all the tokens.Recommendation
Consider the fee on transfer aspect of the pod tokens when adding leverage.
Resolution
Peapods Team: Acknowledged.
-
M-44 Medium Swap Error Handling Causes DOS Of AutoCompounder Logic Error Resolved
Description
Proof of concept: PoC
In
_processRewardsToPodLp, if a swap of the main reward token fails, an override feature kicks in to halve and store the next_amountInto swap in_tokenToPairedSwapAmountInOverride.A temporary DOS attack can be carried out as such: 1. Donate 50 wei of the main reward token (PEAS), assuming contract has no previous balance of PEAS. 2. Call
depositto trigger_processRewardsToPodLpwhere the small swap to Uniswap V3 would fail due to insufficient amountOut. 3. Half of 50 wei (i.e. 25 wei) will then be stored in the override mapping. 4. Contract will attempt to swap for another 6 times before the override amount is set to zero — preventing actual rewards from being processed.Recommendation
Consider re-designing the error handling for the swap.
Resolution
Peapods Team: Resolved in the following code change.
-
M-45 Medium DOS Of depositFromPairedLpToken Logic Error Resolved
Description
Proof of concept: PoC
In
depositFromPairedLpToken, if a swap of a reward token fails, an override feature kicks in to halve and store the next_amountInto swap in_rewardsSwapAmountInOverride.A temporary DOS attack is possible by making use of this feature: 1. Deposit 50 wei of the reward token. The small swap to Uniswap V3 would fail due to insufficient
amountOut. 2. Half of 50 wei (i.e. 25 wei) will then be stored in_rewardsSwapAmountInOverride. 3. Contract will attempt to swap for another 6 times before the override amount is set to zero — preventing actual rewards from being processed.Recommendation
Consider redesigning the override design for swap failures.
Resolution
Peapods Team: Resolved in the following code change.
-
L-01 Low Wrong Address Assignment Logic Error Resolved
Description
Numerous addresses are set as constant variables in the protocol. However, a multitude of these addresses are specific to Ethereum Mainnet and are either not deployed or occupied by an EOA on other chains, such as Arbitrum and Base.
This will lead to reverts when they are interacted with when calling
addLeverage(), and make the functionality unusable outside of Ethereum Mainnet.Here is a list of variables that are assigned a constant address that is either incorrect or not deployed outside of Mainnet:
DAIPROTOCOL_FEE_ROUTERREWARDS_WHITELISTSTYETHYETH,WETH_YETH_POOL,V3_ROUTER
Recommendation
Use an immutable instead of a constant for the addresses, and pass in the proper addresses in the constructor on deployment.
Resolution
Peapods Team: The issue was resolved in commit 9c1d3c2.
-
L-02 Low Oracle Incompatible With Existing Pod Contracts Integration Acknowledged
Description
During
getPrices, in order to correctly price eachpTKN _accountForCBRInPriceis called internally. There it first checks unlocked = 1 which is the reentrancy guard in the pod contract. However, in older versions of the pod contract which are currently live, unlocked is not a uint but a boolean.Therefore, this call to older pod contracts will always revert and fail, making the oracle incompatible with pods such as
pPEASandpOHMwhich hold the bulk of the protocol's TVL (seehttps://etherscan.io/token/0x027CE48B9b346728557e8D420Fe936A72BF9b1C7?a=0x80e9c48ec4
Recommendation
Ensure that the oracle is compatible with the interfaces of older pod contracts.
Resolution
Peapods Team: Acknowledged.
-
L-03 Low POD Ratio Can Be Manipulated Protocol Resolved
Description
Proof of concept: PoC
Each underlying asset of the POD token has a weight assigned to it which determines how much of that token should be paid. If the weights for tokens A and B are 50 and 100, this means for each
token A, 2token Bshould be paid.The
WeightedIndexcontract computes the_tokenAmtSupplyRatioX96variable by dividing the amount of underlying tokens the user is paying by the total amount of tokens held in the contract.This variable is used in two places:
- to determine how much Pods will the user receive
- to calculate the amount of the other underlying tokens that the user must pay.
The problem is that
balanceOfcan be manipulated by anyone by sending tokens to the contract. Let's take a look at the following example:- Two tokens - A and B - with weights 50 and 100.
- Alice wraps
2A + 4B = 2 Pod - A third party sends
2Ato the contract. Total A = 4 - Bob comes and tries to wrap
2A + 4B. - Since the ratio is
2A / 4A = 1/2, he receives 1 Pod and pays2A + 2B.
We can see how the ratio
A:Bchanged from1:2to1:1which diverts from the expected ratio of the pod and the backing ratio users expect when entering a pod.Recommendation
Consider tracking the underlying token balances in an internal mapping.
Resolution
Peapods Team: Resolved in the following code change.
-
L-04 Low PodFlashSource Incompatible With Existing Pods Integration Acknowledged
Description
In the
paymentAmountfunction,FLASH_FEE_AMOUNT_DAIis called on the pod contract to obtain the flash fee.However, for existing pod contracts which are live (e.g.
pPEAS) the flash fee is namedFLASH_FEEinstead. So calls to these pods will always fail.LeverageManager.addLeveragewill therefore also fail if the flash source is a pod.Recommendation
Ensure that
PodFlashSourceis compatible with both old and new pod contracts.Resolution
Peapods Team: Acknowledged.
-
L-05 Low Malicious Pod Could Allow Reentrancy Reentrancy Acknowledged
Description
If a malicious pod were to be used in
LeverageManager, it would allow reentrancy in the add/remove leverage functions. The attacker would be able to access the criticalcallbackfunction and provide arbitrary data to steal other users' funds.This is currently prevented by owner-approved
flashSourceandlendingPairs. However, if a malicious pod were to be accidentally approved, the consequences would be severe.Recommendation
Be extra careful about the approvals for
flashSourceandlendingPairs. Consider validating that the caller incallbackis a whitelisted flash source.Resolution
Peapods Team: Acknowledged.
-
L-06 Low LendingAssetVault Is Not EIP-4626 Compliant ERC4626 Resolved
Description
According to EIP-4626,
maxMintshould return 2**256 - 1 if there is no mint limit. The current implementation of the function returnstype(uint256).max- 1 which is equivalent to 2**256 - 2.Recommendation
Return
type(uint256).max.Resolution
Peapods Team: Resolved in the following code change.
-
L-07 Low Users Avoid Debonding Fee Logic Error Partially resolved
Description
debond()checks if a user is withdrawing 98% or more of the total supply. If they are, then they do not have to pay a fee when debonding. A malicious user could take out a flashloan and bond to increase their share of the total supply to reach the target 98%, then debond right away to avoid paying fees.Recommendation
Charge the fee to users unless they are debonding 100% of the total supply.
Resolution
Peapods Team: Resolved in the following code change.
-
L-08 Low _clBaseFeed Should Be Set When BASE != USD Oracles Resolved
Description
clBaseFeedis assigned toCHAINLINK_BASE_PRICE_FEEDin the constructor. Later, in the Chainlink oracle when the QUOTE/BASE price is required, if this variable is not set the result will be QUOTE/USD.Since there is no validation in the constructor, it's possible to not set the
clBaseFeed. This will be okay for feeds where the BASE asset is USD, but otherwise the pricing will be incorrect.Recommendation
The best solution is to add validation in the constructor.
Resolution
Peapods Team: Resolved.
-
L-09 Low Dormant Token Rewards Logic Error Resolved
Description
When a flashloan is taken from
PodFlashSource.sol, it callsDecntralizedIndex::flash(). The fee for the flashloan is taken in DAI and transferred to theRewardsToken.solif DAI is thePAIRED_LP_TOKENand not the reward token.The DAI will remain dormant in the contract since it is not added to the
rewardsPerTokenmapping, and stakers will not receive the proper rewards during that time period.Recommendation
Call
depositFromPairedLPToken()after the fee is taken when DAI is thePAIRED_LP_TOKENand not the reward token.Resolution
Peapods Team: Resolved in the following code change.
-
L-10 Low Unused Code In LAV Best Practices Resolved
Description
The
_assetDecimalsfunction inLendingAssetVaultis not needed.Recommendation
Consider removing the function.
Resolution
Peapods Team: Resolved.
-
L-11 Low Lenders Can Avoid Socialization Of Bad Debt Logical Error Acknowledged
Description
During the liquidation of a borrower in a
FraxlendPairvault, any debt that cannot be repaid (i.e. bad debt) is socialized among all lenders to the vault which includes theLendingAssetVault(LAV).Within the LAV, the bad debt from one vault is further socialized among depositers to the LAV. The issue lies with lenders/depositors who can avoid the socialization of bad debt by front-running a
liquidatecall, therefore putting a greater burden on the other lenders.Recommendation
Clearly document this risk to users.
Resolution
Peapods Team: Acknowledged.
-
L-12 Low Removing Pairs In LAV Leaves Dirty State Logic Error Resolved
Description
When a pair is removed by calling
setVaultWhitelist(vault, false), thevaultWhitelistAryIdxandvaultMaxPercvariables for that pair are not cleared.Recommendation
Clear these variables.
Resolution
Peapods Team: Resolved in the following code change.
-
L-13 Low Whitelist Update Does Not Update All Vaults Logic Error Acknowledged
Description
The function
whitelistUpdatecan either update one specific vault or all vaults depending on the boolean passed in. When the boolean is false, all vaults but the calling vault is updated sincemsg.senderis passed as the_vaultToExclude.It is unclear if this is the intended behavior as the natspec comments indicate that all vaults should be updated. However, if the calling vault was included in the update,
addInterestmay revert due to reentrancy protection in theFraxlendPaircontract.Recommendation
Be aware that not all vaults are updated when
whitelistUpdate(false)is called, and consider updating the natspec comments for accuracy.Resolution
Peapods Team: Acknowledged.
-
L-14 Low Approved Parties Cannot removeLeverage Protocol Resolved
Description
Leverage can be added to positions by the owner of the position NFT or any approved party of that NFT. However, the opposite action - removing leverage - can be performed only by the owner of the NFT.
Recommendation
Document this behavior.
Resolution
Peapods Team: Resolved in the following code change.
-
L-15 Low Stale Price Causes Division By 0 Logic Error Acknowledged
Description
When a stale price is passed from the oracle, it will return the price as 0 and emit an alert in
_updateExchangeRate(). If thehighExchangeRateis 0, then it will end up reverting in the calculation of_deviationbecause it divides by 0.This will lead to the event never being emitted and transactions will fail without a clear root cause.
Recommendation
Instead of emitting a log when there is bad price data, revert with a custom error to signify that the price is wrong.
Resolution
Peapods Team: Acknowledged.
-
L-16 Low Oracle Reverts On Stale Price Oracles Acknowledged
Description
When a stale price is passed from the oracle, it will return the price as 0 and emit an alert in
_updateExchangeRate(). If thehighExchangeRateis 0, then it will end up reverting in the calculation of_deviationbecause it divides by 0.This will lead to the event never being emitted and transactions will fail without a clear root cause.
Recommendation
Instead of emitting a log when there is bad price data, revert with a custom error to signify that the price is wrong.
Resolution
Peapods Team: Acknowledged.
-
L-17 Low Pod Flashloans Prevented Via Lock Logic Error Acknowledged
Description
_accountForCBRInPrice()validates that the pod is not currently locked, and otherwise reverts. When a user takes out a flashloan from a pod it locks the contract, and will preventgetPrices()from being called.This will prevent
borrowAsset()(and any other function that callsupdateExchangeRate()) from executing from the pod that has taken the flashloan.Recommendation
Without the lock validation, the oracle price would be manipulatable via a flashloan. Document that using
PodFlashSourcecan lead to a revert from locking the pod.Resolution
Peapods Team: Acknowledged.
-
L-18 Low VotingPool Unstake Rounding Math Acknowledged
Description
When users stake, the amount of voting tokens they get is determined by multiplying the deposited amount by the factor. In
unstakethe opposite is done - the unstaked amount is divided by the factor. This will result in users receiving less tokens than they initially deposited.Recommendation
Document the discrepancy
Resolution
Peapods Team: Acknowledged.
-
L-19 Low Possible Inflation Attack In AutoCompounder Logic Error Partially resolved
Description
AutoCompoundingPodLpguards against the classic inflation attack by internally tracking deposits with the_totalAssetsvariable. However, the attack is still possible by sending reward tokens which are converted into assets before each action (e.g. deposit/withdraw). This increases_totalAssetswithout minting new shares.If the
AutoCompoundingPodLpis created by the factory contract, a minimum deposit is performed with 1e3 assets that creates 1e3 shares. This mitigates the effect of a donation but an attack is still possible.Consider this example:
- After factory creation with min deposit:
_totalAssets = 1e3, totalSupply = 1e3 - Attacker donates the equivalent of 1000e18 assets
- User deposits 1e18 assets:
_cbr: (1000e18 + 1e13) * 1e18 / 1e3-> 1.00..03e36 (v large number)convertToShares: 1e18 * 1e18 / 1.00..03e36-> round down to 0- User receives 0 shares and loses assets
The larger the donation, the higher the threshold will be for user's deposits to round down to 0 shares.
Recommendation
Consider performing a larger minimum deposit by the factory contract, which then increases the cost of the attack. Also, during deposit, after
convertToSharesis performed, checked that shares are not equal to 0 or else revert.Resolution
Peapods Team: Resolved in the following code change.
- After factory creation with min deposit:
-
L-20 Low Approved Address Gets The Flash Loan Refund Logic Error Resolved
Description
Removing leverage can only be done by position NFT owners, but adding leverage can be done by any approved user or operator. When adding leverage, the
msg.senderprovides their own funds aspodtokens, andpodtoken refunds are returned to themsg.senderas expected.However, flash-loaned
pairedLpTokens are also refunded to themsg.sender, not necessarily to the position owner. When the position owner has an over-collateralized position in Fraxlend, an approved user can flash loan morepairedLpTokenthan needed and receive the entire refund.Recommendation
Reconsider who should receive the flash loan refunds. If the
msg.senderis expected to receive them, rather than the position owner, consider documenting this behavior.Resolution
Peapods Team: Resolved in the following code change.
-
L-21 Low getFullUtilizationInterest() Calculation Logic Error Acknowledged
Description
Inside of
getFullUtilizationInterest(), the_newFullUtilizationInterestwill always go down when the current utilization is less thanMIN_TARGET_UTIL.It does not take into consideration if the Fraxlend vault’s utilization has actually gone up or down, and simply looks at if the utilization is within certain target ranges.
Recommendation
Be aware that this will occur, and that it will only go down based on your configuration of
MIN_TARGET_UTIL.Resolution
Peapods Team: Acknowledged.
-
L-22 Low Unused Cached Value Logic Error Resolved
Description
_exchangeRateInfois cached to memory in theisSolventmodifier of theFraxlendPairCorecontract. However, this cached value is never used but the storage variable is being used instead.Recommendation
Consider using the cached memory variable. Alternatively, remove the variable.
Resolution
Peapods Team: Resolved in the following code change.
-
L-23 Low bondWeightedFromNative Fails For Multi-Asset Pod Logic Error Acknowledged
Description
In
_swapNativeForTokensWeightedV2, a loop is performed to swap WETH for each index token.However by calling
swapExactTokensForTokensSupportingFeeOnTransferTokens, all WETH (tokenIn) is swapped for tokenOut on the first loop iteration, leaving no remaining WETH for next iteration, if there are multiple assets in an index.Then, there will be insufficient index tokens to bond when
_indexFund.bondis later called.Recommendation
Use the other Uniswap function
swapTokensForExactTokensinstead to get out a specific amount of each index token. Alternatively, calculategetAmountsInto pass in exactly the amount of WETH needed before calling the swap function.Resolution
Peapods Team: Acknowledged.
-
L-24 Low Both buy And sell Fees Can Be Applied Logical Error Resolved
Description
If a POD is transferred from a V2 pair, a buy fee is applied. If it's transferred to a V2 pair, a sell fee is applied. This means that if a POD is transferred from a V2 pair to the pair itself, both fees will be applied.
On top of that only the second fee is deducted from the amount to be subtracted. Imagine the following:
- Pair tries to transfer 20 tokens
- Buy fee is 50% so it pays 10 tokens
- Sell fee is 10% so it pays 2 tokens
- Since sell fee was the last recorded fee, the amount transferred to the recipient is (20 - 2) = 18
- Total transferred: 30 tokens
Recommendation
Instead of if-if, change the fee checks to if-else if
Resolution
Peapods Team: Resolved in the following code change.
-
L-25 Low DOS Of Native Bond And Stake Logic Error Resolved
Description
In
bondWeightedFromNative, when_stakeAsWellis true, only half of msg.value is passed to_swapNativeForTokensWeightedV2. However, later during_swapForIdxTokenthe entire contract's balance of ETH is converted to WETH.This results in a revert later on when _zapIndexTokensAndNative is called as _zap attempts to convert the remaining half of msg.value to WETH:
a Guardian proof of concept
Consequently, this protocol functionality becomes unusable.
Recommendation
If
_stakeAsWellis true, only half of msg.value should be converted to WETH initially.Resolution
Peapods Team: Resolved in the following code change.
-
L-26 Low Unsuccessful Asp Reward Swaps Protocol Acknowledged
Description
Flash loans from
UniswapV3pools are taken for the paired token of the leveraged pod. If the second token in that pool matches with the reward token for the ASP, the swap will fail because swapping will be locked since flashloan is taken from that pool.Recommendation
Make sure the Peapods team is aware of this
Resolution
Peapods Team: Acknowledged.
-
L-27 Low Asp Rewards Impact removeLeverage Protocol Resolved
Description
When leverage is being removed, the asp collateral has to be turned to
spTokensvia callingredeem. This action will trigger rewards distribution and will swappairedTokensfor pod tokens.The price of the
pairedTokenswill drop while the price of the pod tokens will go up. When thespTokenis unwrapped to LP token and liquidity is removed, because of the previous swap the user will receive more paired tokens and less pod tokens.Recommendation
This should be resolved with implementing the one-sided liquidity formula.
Resolution
Peapods Team: Resolved in the following code change.
-
L-28 Low LVF Bond Rounding Protocol Acknowledged
Description
When an
fTokenis bonded for a self lending pod, thebondfunction will transfer slightly lessfTokensfrom theLeverageManagerbecause of rounding. This will leave theLeverageManagerwith non-zero approval that will increase over time.Also, if all of the paired assets are used up, the refund if statement won't be executed and the
msg.senderwon't receive thesefTokens. They will be left in the contract and the next caller will have access to them.Recommendation
Document this behavior
Resolution
Peapods Team: Acknowledged.
-
L-29 Low Typos Logic Error Resolved
Description
remvoe instead of remove is within the NatSpec.
Recommendation
Fix the typos
Resolution
Peapods Team: Resolved.
-
L-30 Low Linear Interest Rate Will DOS Fraxlend DoS Acknowledged
Description
LinearInterestRatecannot be used because it implements theIRateCalculatorinterface where thegetNewRatesignature isgetNewRate(bytes,bytes).FraxlendPairCoreusesIRateCalculatorV2with signaturegetNewRate(uint256,uint256,uint64).Recommendation
Change the
LinearInterestRateto adhere to theIRateCalculatorV2interface.Resolution
Peapods Team: Acknowledged.
-
L-31 Low overrideBorrowAmt May Be Used Maliciously Logical Error Acknowledged
Description
In
addLeveragea user can intentionally borrow up to the solvency limit from Fraxlend by passing in desired_overrideBorrowAmt.The caller of
addLeveragewill then receive any additional borrow tokens while putting the leveraged position on the edge of liquidation. This opens up an attack surface where borrowed funds leave the system.This could also be abused by an approved account or if the user accidentally sets
isApprovedForAllto hispositionNFT.Recommendation
Unless there are strong reasons to do so, do not allow a user to override borrow amount. Else, consider restricting the override borrow amount to provide some additional buffer from liquidation.
Resolution
Peapods Team: Acknowledged.
No findings match.
Invariants 47
The review's fuzzing suite asserted 47 invariants. 35 held and 12 did not.
Every invariant tested
| ID | Invariant | Result |
|---|---|---|
POD-1 | LeverageManager::_acquireBorrowTokenForRe payment should never Uniswap revert | Broken |
POD-2 | LendingAssetVault::deposit/mint share balance of receiver should increase | Broken |
POD-3 | LendingAssetVault::withdraw/redeem share balance of user should decrease | Held |
POD-4 | vaultUtilization[_vault] == FraxLend.convertToAssets(LAV shares) | Broken |
POD-5 | post-update LendingAssetVault::totalAssetsUtilized totalAssetsUtilized == sum(all vault utilizations) | Held |
POD-6 | LendingAssetVault::whitelistDeposit totalAvailableAssets() should increase | Held |
POD-7 | LendingAssetVault::whitelistDeposit vault utilization should decrease accurately | Broken |
POD-8 | LendingAssetVault::whitelistDeposit total utilization should decrease accurately | Broken |
POD-9 | LendingAssetVault::whitelistWithdraw totalAvailableAssets() should decrease | Held |
POD-10 | LendingAssetVault::whitelistWithdraw vault utilization should increase accurately | Held |
POD-11 | LendingAssetVault::whitelistWithdraw total utilization should increase accurately | Held |
POD-12 | LendingAssetVault::global total assets == sum(deposits + donations + interest accrued - | Broken |
POD-13 | withdrawals) LendingAssetVault::withdraw/redeem User can't withdraw more than their share of total | Broken |
POD-14a | assets LendingAssetVault::donate Post-donation shares shouldn't have increased, but | Broken |
POD-14b | totalAssets should have by donated amount LendingAssetVault::donate Post-donation shares shouldn't have increased, but | Held |
POD-15 | totalAssets should have by donated amount LendingAssetVault::global FraxLend vault should never more assets lent to it from the | Held |
POD-16 | LAV that the allotted _vaultMaxPerc LendingAssetVault::whitelistDeposit Post-state utilization rate in FraxLend should have decreased (called by repayAsset in FraxLend) | Held |
POD-17 | (utilization rate retrieved from currentRateInfo public var) LendingAssetVault::whitelistWithdraw Post-state utilization rate in FraxLend should have increased or not changed (if called within | Held |
POD-18a | from a redeem no change, increase if called from borrowAsset) LeverageManager::addLeverage Post adding leverage, there totalBorrow amount and shares, as well as utilization should increase in | Held |
POD-18b | Fraxlend LeverageManager::addLeverage Post adding leverage, there totalBorrow amount and shares, as well as utilization should increase in Fraxlend | Held |
POD-19a | LeverageManager::removeLeverage Post removing leverage, there totalBorrow amount and shares, as well as utilization should | Held |
POD-19b | decrease in Fraxlend LeverageManager::removeLeverage Post removing leverage, there totalBorrow amount and shares, as well as utilization should | Held |
POD-20 | decrease in Fraxlend Post adding leverage, there should be a higher supply of spTKNs (StakingPoolToken) | Held |
POD-21 | Post adding leverage, there should be a higher supply of aspTKNs (AutoCompoundingPodLp) | Held |
POD-22 | Post adding leverage, the custodian for the position should have a higher | Held |
POD-23 | userCollateralBalance Post removing leverage, there should be a lower supply of spTKNs (StakingPoolToken) | Broken |
POD-24 | Post removing leverage, there should be a lower supply of aspTKNs | Held |
POD-25 | (AutoCompoundingPodLp) Post removing leverage, the custodian for the position should have a lower | Held |
POD-26 | userCollateralBalance FraxLend: cbr change with one large update == cbr change with multiple, smaller updates | Broken |
POD-27 | LeverageManager contract should never hold any token balances | Held |
POD-28 | FraxlendPair.totalAsset should be greater or equal to vaultUtilization (LendingAssetVault) | Held |
POD-29 | LendingAssetVault::global totalAssets must be greater than totalAssetUtilized” | Held |
POD-30 | repayAsset should not lead to to insolvency | Held |
POD-31 | staking pool balance should equal token reward shares | Held |
POD-32 | FraxLend: (totalBorrow.amount) / totalAsset.totalAmount(address(externalAsset | Held |
POD-33 | Vault)) should never be more than 100% FraxLend: totalAsset.totalAmount(address(0)) == 0 -> totalBorrow.amount == 0 | Held |
POD-34 | AutoCompoundingPodLP: mint() should increase asp supply by exactly that amount of | Held |
POD-35 | shares AutoCompoundingPodLP: deposit() should decrease user balance of sp tokens by exact | Held |
POD-36 | amount of assets passed AutoCompoundingPodLP: redeem() should decrease asp supply by exactly that amount of | Held |
POD-37 | shares AutoCompoundingPodLP: withdraw() should increase user balance of sp tokens by exact | Held |
POD-38 | amount of assets passed AutoCompoundingPodLP: mint/deposit/redeem/withdraw() spToken | Held |
POD-39 | total supply should never decrease AutoCompounding should not revert with Insufficient Amount | Broken |
POD-40 | AutoCompounding should not revert with Insufficient Liquidity | Broken |
POD-41 | AutoCompoundingPodLP: redeem/withdraw() should never get an InsufficientBalance or | Held |
POD-42 | underflow/overflow revert custodian position is solvent after adding leverage and removing leverage | Held |
POD-43 | TokenReward: global: getUnpaid() <= balanceOf reward token | Held |
POD-44 | LVF: global there should not be any remaining allowances after each function call | Held |
Put your code through the same review.
This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.
