Decentralized liquidity market that allows users to supply crypto assets and start earning a variable APY from borrowers.
Platform: Code4rena
Start Date: 21/02/2024
End Date: 11/03/2024
Period: 19 days
Status: Completed
Pot Size: $200,000 USDC
Participants: 36
Reporter: thebrittfactor
Judge: Trust
Id: 330
League: ETH
nonseodion | 1/36 | $37,528.13 | 6 | 2 | 1 | 3 | 2 | - | 0 | 0 |
0xStalin | 2/36 | $21,136.58 | 5 | 2 | 0 | 3 | 2 | 0 | 0 | 0 |
Dup1337 | 3/36 | $20,400.36 | 3 | 1 | 1 | 1 | 0 | - | 0 | 0 |
0xCiphky | 4/36 | $15,460.18 | 3 | 1 | 0 | 2 | 1 | 0 | 0 | 0 |
serial-coder | 5/36 | $10,899.43 | 5 | 1 | 0 | 3 | 1 | - | 0 | 0 |
NentoR | 6/36 | $8,036.28 | 3 | 0 | 0 | 2 | 1 | - | 0 | 0 |
Draiakoo | 7/36 | $7,970.35 | 2 | 1 | 0 | 1 | 1 | 0 | 0 | 0 |
00xSEV | 8/36 | $7,312.25 | 2 | 0 | 0 | 2 | 1 | 0 | 0 | 0 |
t0x1c | 9/36 | $7,145.11 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
SBSecurity | 10/36 | $5,772.78 | 4 | 0 | 0 | 3 | 0 | 0 | 0 | - |
Auditor per page
❗️❗️ Please note: if ANY valid High severity issues are found, the Total Prize Pool increases to $200,000 USDC. The HM award will increase to $154,565. All other awards will be capped at the amounts listed above.
The 4naly3er report can be found here.
Automated findings output for the audit can be found here within 24 hours of audit opening.
Note for C4 wardens: Anything included in this Automated Findings / Publicly Known Issues
section is considered a publicly known issue and is ineligible for awards.
The difference between indexed rewards for the PendlePowerFarmController and actual rewards is to be ignored and does not count as a bug.
The difference in rewards caused by a potential change in rewardTokenAddresses by pendleMarket itself is to be ignored and does not count as a bug.
For the PendlePowerFarmToken contract distribution of compounding rewards in the edge case of no interaction in a week in one lump is to be ignored and does not count as a bug.
(Part 1) One entity could force the current stepping direction by using flash loans and dumping a huge amount into the pool with a transaction triggering the algorithm (after three hours). In the same transaction, the entity could withdraw the amount and finish paying back the flash loan. The entity could repeat this every three hours, manipulating the stepping direction.Now, we changed it in a way that the algorithm runs before the user adds or withdraws tokens, which influences the shares. Thus, the attacker needs to put the tokens inside the pool one block before the update block AND needs to be the last transaction in that block to be sure to add the right token amount. Following a flash loan is not possible anymore because the user can't pay back the loan in the same transaction. The tokens need to stay at least one block inside the pool.This closes the attack vector, but there may still be a possible scenario. If the attacker has a lot of tokens, they can add the tokens before the update but need to time this (the last person in one block before the update). Then they can withdraw the tokens in the next block and also force the stepping to hold its direction. When the attacker wants to repeat this, they need to keep these tokens fluid every three hours; otherwise, their attack gets automatically reverted in the next update round because they removed the token amount after the last update, resulting in a reduction of shares. So the downsides for the attacker are:
(Part 2) Even when an attacker is doing this, we can set the pool params so that the curve is a static one (like Aave) when we set the _upperBoundMaxRate and _lowerBoundMaxRate rates very close. Or we can just reset them. Even though the attacker can't drain any funds with this attack and loses money over time, the only benefit for this entity is to annoy us. In summary, we are aware of this attack, but with all these arguments, we don't see it as a problem. This is because:
In case of liquidation is rounded against the liquidator when it comes to evaluating the amount of tokens they payback related to the borrowshares they payoff, which means any additional value from that effect gets transferred to all other borrowers. This is intended since the other way around would mean you syphon value out of other non participating users (not participating in the liquidation event) instead.
In all kind of interactions with the protocol, we round against the user and not against the pool, except for liquidation transfer amount. This is intentional. The reason for this is to cancel potential roundings which could be receptured by the liquidator. Because we round the shares against the liquidatee and lending shareprice is always greater or equal one, this will not extract value from the pool.
Since shareprice is limited massively this value extraction can be neglected since it only becomes relevant when the value of 1 share becomes in order of magnitude of of the total amounts of shares. The total amount of value extractable is also limited by the collateralamount itself. This is intended since the other option is to extract value from users who are not participating in the exchange.
Also excluded are external view functions which are not used for statechanges in the relevant contracts/files. E.g maximumBorrowToken...
Potential token or aToken stuck in the feeManger.sol is a known issue and therefore OOS.
The ghostshares with the magnitude of 10^3 aquire interest over time which is lost to the system since they capture a tiny part of the interest but can be neglected because of the exponential difference in orders of magnitude compared to a normally used pool and is therfor known and not part of the audit aswell.
Also excluded are centralizing effects of any kind relating to the behaviour of entities. E.g master can submit a malicious pool etc.
If any rounding errors in the incentives for paying back badDebt exist this is excluded aswell since this does not interact with any user funds since this is taken from admin fees.
If any rounding errors exist in the pendlePowerFarm token which dont create a way to extract value which is not planned is excluded aswell. This also includes compounding as an exclusion since it is related to incentives.
If you query borrowdata for lasa before it might change the stepping direction or reset it the extrernal view function may differ in calculation for the actual borrowrate taken for the current time period until the next time syncPool is triggered. This is necessary to prevent flashloan meddeling with the algorithm and is known and not considered a bug.
The attack from very well capitalized entity to block deposits or paybacks (normal or aaveHub) for other users is known and judged by us as impractical. Thus we declare it OOS.
If Aave freezes pools where we use the underlying as a pool new aTokens can't be minted and thus open borrow positions can't pay it back. If Aave unfreezes the pool the functionality is restored. This can be mitigated in the future by wrapping the aTokens into new tokens which also accept the underlying. Then after adding that to a pool this is mitigated. So the risk is known and not considered a bug and can be migrated after launch while its still not frozen.
Users can deposit dust into other peoples nfts which can increase the gasprice for using that nft for borrowing or withdrawing having an open borrow position. We have a function to set minDepositAmount to mitigate that if we see people are doing that. (Attacker loses money themselves (gas) by doing that and after mindepositAmount that amount aswell)
In case of withdrawing an uncollateralized asset if you happen to be above 95% debtratio anyway it will still fail (only then). Being above 95% basically means you are in liquidation mode and are therfore incentivized to to use your uncollateralized as collateral instead of removing it to save money and avoid liquidation. Therfore this is not seen as a bug by us.
If heartbeat of one of the lending or borrow tokens is dead and user has open borrow position user cant withdraw or borrow during that time period however since they can always payback either the current dead borrow token in full and thus remove it or payback all tokens they can still acess their funds and thus this is not counted as a bug and is marked as known.
If a user has a currently heartbeat dead token he cant be liquidated but since borrowing or depositing a deadtoken will revert you cannot use that fact to make your self not liquidatable and thus not exploitable. This is not considered a bug since we consider an offline feed more dangerous (see blizz finance ust incident). As soon as the feed returns liquidation is possible again.
Also excluded are secondary centralization effects e.g bugs in other used projects like uniswapV3 etc. The twap check for the oracle can result in momentary denial of service of withdrawing with an open borrow position or borrowing itself. This is intended for a discrepancy between both used oracles. No funds are locked since people can always payback and then withdraw even with no functioning oracle.
Also excluded are centralizing effects of any kind relating to the behaviour of entities. E.g master can submit a malicious pool, revokeMaster() etc.
Also the redundant curveSecurityCheck() for borrow tokens is known and not and issue, because all derivate tokens used for wiselending are deployed with _allowBorrow == false. This includes e.g. pendle PT, pendle LPs, Curve or any upcoming. For example after the exypiration day no new Pendle LP can be minted which is irrelevant because these pools are deployed with _allowBorrow == false. In the future we will remove the reduntand curveSecurityCheck for borrow tokens.
We bounded the borrowShare price from below by 5 * 10^17. Every interaction like payback and borrow are decreasing the borrowShare price (rounding against user but for the pool) but this is usually captured by intrest from open borrow position. Nevertheless, it is possible to force the sharePrice to this lower bound by looping borrows and/or paybacks. This could result in a DOS for the whole pool but this would requiere looping more than approx 10 ** 14 transaction for a "fresh" pool and therefore considerd impractical and thus OOS.
PowerFarms are only planned to have 1 collateral. Any "bugs" related to having more collaterals for powerFarms are out of scope.
The main usecase of blacklist is a kill switch pause for all pools. You can always payback everything and access your money but everything else including uncollateralized etc. is paused for security reasons".
Liquidations are enabled for blacklisted tokens. This is intentional.
Bad debt accrues interest is known and intended.
No specific ERC compatibility in mind
No support for fees on transfer (hello USDT)
For general infos one visit our gitbook: https://wisesoft.gitbook.io/wise
We us a dynamic way to set the borrow rate curve depending on the total lending share amount and its change over time. A theoretical basics are explained in the LASA white paper: https://github.com/wise-foundation/liquidnfts-audit-scope/blob/master/LASA-Paper.pdf
There is one master role which will be managed by a timelock contract in the future. This role maintains the system and sets all adjustable parameters. Additionally, there is the extra role of the securityWorker which can perform a security lock. This role can be only be assigned by the master.
The protocol will be deployed on ETH and Arbitrum. On ETH AaveHub will NOT be used.
ERC20 in scope: WETH, WBTC, LINK, DAI, WstETH, sDAI, USDC, USDT, WISE and may others in the future. (Also corresponding Aave tokens if existing)
Is there a need to understand a separate part of the codebase / get context in order to audit this part of the protocol?: True - Curve protocol, Pendle Finance
Describe any novel or unique curve logic or mathematical models your code uses: https://github.com/wise-foundation/liquidnfts-audit-scope/blob/master/LASA-Paper.pdf