Platform: Code4rena
Start Date: 16/10/2023
Pot Size: $60,500 USDC
Total HM: 16
Participants: 131
Period: 10 days
Judge: 0xTheC0der
Total Solo HM: 3
Id: 296
League: ETH
Rank: 44/131
Findings: 2
Award: $137.73
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xpiken
Also found by: 0xCiphky, 0xComfyCat, 0xStalin, 0xhegel, 0xkazim, 3docSec, AM, Aymen0909, CaeraDenoir, DeFiHackLabs, Drynooo, Eigenvectors, Fulum, HALITUS, HChang26, Jiamin, Juntao, LokiThe5th, Mike_Bello90, MiloTruck, QiuhaoLi, Silvermist, SovaSlava, SpicyMeatball, T1MOH, Toshii, TrungOre, TuringConsulting, Vagner, Yanchuan, ZdravkoHr, _nd_koo, almurhasan, audityourcontracts, ayden, cartlex_, circlelooper, crunch, cu5t0mpeo, deth, erictee, ggg_ttt_hhh, gizzy, gumgumzum, hash, jasonxiale, josephdara, ke1caM, kodyvim, lanrebayode77, marqymarq10, max10afternoon, nirlin, nonseodion, osmanozdemir1, peter, radev_sw, rvierdiiev, said, serial-coder, sl1, smiling_heretic, squeaky_cactus, stackachu, tallo, trachev, zaevlad
0.0606 USDC - $0.06
Due to the restriction on the closeMarket() function, only the controller is able to use it, but the controller contract has no way to call it.
Since the borrower becomes unable to close a market once it opened, it becomes a big issue as the lenders can accrue interest even when the borrower no longer wishes to borrow asset.
Borrower creates a market.
Alice, who is authorized by Borrower to be a lender, deposits 10,000 of asset.
Alice gets 10,000 of market tokens.
Borrower no longer wishes to borrow money, but is unable to close the market since no function is created on the marketController.
scaleFactor keeps increasing and borrower can not do anything to prevent it.
Alice has the 10,000 market tokens and is able to accrue interest for an infinite amount of time, making borrower liable for the interest.
Add a closeMarket function to the WildcatMarketController contract.
Context
#0 - c4-pre-sort
2023-10-27T07:33:41Z
minhquanym marked the issue as duplicate of #147
#1 - c4-judge
2023-11-07T14:10:09Z
MarioPoneder marked the issue as partial-50
#2 - c4-judge
2023-11-07T14:16:53Z
MarioPoneder changed the severity to 3 (High Risk)
🌟 Selected for report: MiloTruck
Also found by: CaeraDenoir, T1MOH, ast3ros, elprofesor, joaovwfreire, rvierdiiev, t0x1c, trachev
137.6749 USDC - $137.67
These QA Findings are based on markets set on specific conditions by the borrower, allowing some bad-faith borrowers to bypass some market configs. (Not listing a full 100% borrow by a bad-faith borrower here since it is of a higher severity, even though it is based on the same principles of a bad-faith borrower)
setAnnualInterestBips
to skew the Reserve RatioThe logic behind is: a borrower can deploy a market with a high Reserve ratio, then call setAnnualInterestBips to lower the ratio and be able to get access to more asset without making the market delinquent.
A function in src/WildcatMarketController.sol
function setAnnualInterestBips( address market, uint16 annualInterestBips ) external virtual onlyBorrower onlyControlledMarket(market) { // If borrower is reducing the interest rate, increase the reserve // ratio for the next two weeks. if (annualInterestBips < WildcatMarket(market).annualInterestBips()) { TemporaryReserveRatio storage tmp = temporaryExcessReserveRatio[market]; if (tmp.expiry == 0) { tmp.reserveRatioBips = uint128(WildcatMarket(market).reserveRatioBips()); // Require 90% liquidity coverage for the next 2 weeks WildcatMarket(market).setReserveRatioBips(9000); } tmp.expiry = uint128(block.timestamp + 2 weeks); } WildcatMarket(market).setAnnualInterestBips(annualInterestBips); }
When the borrower changes the annual interest, the function checks if the rate is being reduced, and in case it is, it sets the reserve ratio to 90%.
This behavior can be exploited by a bad borrower, by attracting lenders with a very high reserve ratio, and then lowering it by using the function.
It is worth to take into consideration that the bad actor can in fact promise a high Annual Interest with a high reserve ratio, and benefit both from lowering the interest and being able to borrow more of the asset lended.
Assume a new market is deployed, Ana is an approved lender, the reserve ratio is 99%, and has a 10% Annual Interest Rate.
Ana supplies the market with 50,000 of asset.
Supply 50,000, Reserve Ratio 99%, In Reserves: 50,000, Reserves Required: 49,500, Annual Interest : 10% Borrower can withdraw up to 500 asset at 10% annual interest without making the market delinquent.
Borrower then calls setAnnualInterestBips, and lowers the interest to 1%.
Supply 50,000, Reserve Ratio 90%, In Reserves: 50,000, Reserves Required: 45,000, Annual Interest : 1% Borrower can withdraw up to 5000 asset at 1% annual interest without making the market delinquent.
Borrower is able to withdraw more asset at a lower rate.
Don't allow setAnnualInterestBips to lower the Reserve Ratio under any circumstances.
This is a very niche situation that compromises lender assets, but there are a lot of things that must align for this to be possible, and should in theory be covered by other off-chain methods.
setAnnualInterestBips
to borrow at a lower interest.The logic behind is: a borrower can deploy a market with a high base Interest and a low delinquent interest, then borrow whatever is allowed by the Reserve Rate follower by a setAnnualInterestBips call to lower the base Interest.
A function in src/WildcatMarketController.sol
function setAnnualInterestBips( address market, uint16 annualInterestBips ) external virtual onlyBorrower onlyControlledMarket(market) { // If borrower is reducing the interest rate, increase the reserve // ratio for the next two weeks. if (annualInterestBips < WildcatMarket(market).annualInterestBips()) { TemporaryReserveRatio storage tmp = temporaryExcessReserveRatio[market]; if (tmp.expiry == 0) { tmp.reserveRatioBips = uint128(WildcatMarket(market).reserveRatioBips()); // Require 90% liquidity coverage for the next 2 weeks WildcatMarket(market).setReserveRatioBips(9000); } tmp.expiry = uint128(block.timestamp + 2 weeks); } WildcatMarket(market).setAnnualInterestBips(annualInterestBips); }
When the borrower changes the annual interest while borrowing asset, it is possible for the new Annual Interest rate + Deliquency rate to be lower than the promised % for the lenders, and by being delinquent the lenders are unable to exit either.
It is worth considering that this behavior makes the market go delinquent, so it raises some flags instantly.
Assume a new market is deployed, Ana is an approved lender, the reserve ratio is 20%, and has a 30% Annual Interest Rate with a 10% Delinquent Rate.
Ana supplies the market with 50,000 of asset.
Supply 50,000, Reserve Ratio 20%, In Reserves: 50,000, Reserves Required: 10,000, Actual Annual Interest : 30% Borrower: 0 asset Market: Not Delinquent
Borrower then calls borrows the maximum amount he is allowed
Supply 50,000, Reserve Ratio 20%, In Reserves: 10,000, Reserves Required: 10,000,Actual Annual Interest : 30% Borrower: 40,000 asset Market: Not Delinquent
Borrower then calls setAnnualInterestBips, and lowers the interest to 10%.
Supply 50,000, Reserve Ratio 20%, In Reserves: 10,000, Reserves Required: 45,000 Actual Annual Interest : 10%(new Annual Interest) + 10%(Delinquency Rate being applied) = 20% Borrower: 40,000 asset Market: Delinquent
There are a few ways to solve this issue. I would advise not to allow the borrower to lower the Interest if the market would enter delinquency after the function call.
This is a very niche situation that compromises lender interest, but there are a lot of things that must align for this to be possible, and should in theory be covered by other off-chain methods.
#0 - c4-judge
2023-11-09T16:00:14Z
MarioPoneder changed the severity to 2 (Med Risk)
#1 - c4-judge
2023-11-09T16:00:14Z
MarioPoneder changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-11-09T16:00:36Z
MarioPoneder marked the issue as duplicate of #497
#3 - c4-judge
2023-11-09T16:00:50Z
MarioPoneder marked the issue as satisfactory