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: 76/131
Findings: 2
Award: $16.72
🌟 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
market.closeMarket
should allow the borrower to repay all debts and then stop paying interest on deposits of lenders (i.e. set APR to zero). The purpose of this function is described in more detail here.
Assuming that constraints.minimumAnnualInterestBips
is non-zero and correctly enforced, closeMarket
might be the only way for the borrower to exit the market and stop paying interest.
However, because of onlyController
modifier on market.closeMarket
and no way to call this function from the WildcatMarketController
contract, borrower is unable to close the market.
This will force the borrower to pay interest forever (or at least as long as lenders want, or resolve the issue with help of lawyers).
market.closeMarket
function is onlyController
.msg.sender
is the WildcatMarketController
contract that has deployed the market.WildcatMarketController
contract and notice that none of them allow to call market.closeMarket
function (or simply search for "closeMarket" in this file).Manual Analysis
Change onlyController
modifier on closeMarket
function to onlyBorrower
. Alternatively, add a function to WildcatMarketController
that allows the borrower to close the market.
Access Control
#0 - c4-pre-sort
2023-10-27T07:06:14Z
minhquanym marked the issue as duplicate of #147
#1 - c4-judge
2023-11-07T13:59:11Z
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: 3docSec
Also found by: 0xCiphky, 0xbepresent, 0xbrett8571, Eigenvectors, MiloTruck, Toshii, Tricko, TrungOre, ZdravkoHr, b0g0, caventa, cu5t0mpeo, deth, ggg_ttt_hhh, gizzy, joaovwfreire, josephdara, serial-coder, smiling_heretic, stackachu
16.6643 USDC - $16.66
From the Main Invariants section of the competition description, we learn that "Market parameters should never be able to exit the bounds defined by the controller which deployed it."
Also in Attack Ideas, there's "Consider ways in which market interest rates can be manipulated to produce results that are outside of controller-specific limits".
These constraints are enforced when deploying new markets. However, they're not enforced in the controller.setAnnualInterestBips
function.
This allows the borrower to exit bounds defined by the controller for market.annualInterestBips
by simply calling the controller.setAnnualInterestBips
function with annualInterestBips
argument from outside of these bounds.
Edit constants in test/shared/TestConstants.sol
file:
// uint16 constant MinimumAnnualInterestBips = 0; // uint16 constant MaximumAnnualInterestBips = 10_000; uint16 constant MinimumAnnualInterestBips = 1_000; uint16 constant MaximumAnnualInterestBips = 8_000;
Add the following test to the test file: test/WildcatMarketController.t.sol
:
function test_setAnnualInterestBips_DecreaseBelowConstraints() public { vm.prank(borrower); controller.setAnnualInterestBips(address(market), DefaultInterest - 10); _check( DefaultInterest - 10, 9000, DefaultReserveRatio, block.timestamp + 2 weeks ); MarketParameterConstraints memory constraints = controller .getParameterConstraints(); assertEq(constraints.minimumAnnualInterestBips, 1000); assertEq(market.annualInterestBips(), 990); assertLt( market.annualInterestBips(), constraints.minimumAnnualInterestBips ); }
Run forge test --match-test test_setAnnualInterestBips_DecreaseBelowConstraints
Foundry, manual analysis
In controller.setAnnualInterestBips
function, enforce that market.annualInterestBips
stays between constraints.minimumAnnualInterestBips
and constraints.maximumAnnualInterestBips
.
Another thing to consider is: is setting reserveRatioBips
to 90% really ok, even if it happens to be outside of controller's constraints (constraints.minimumReserveRatioBips
, constraints.maximumReserveRatioBips
)?
Invalid Validation
#0 - c4-pre-sort
2023-10-27T14:10:42Z
minhquanym marked the issue as primary issue
#1 - c4-pre-sort
2023-10-27T17:14:23Z
minhquanym marked the issue as sufficient quality report
#2 - laurenceday
2023-10-31T23:23:58Z
Valid finding, well done everyone!
#3 - c4-sponsor
2023-10-31T23:33:10Z
laurenceday (sponsor) confirmed
#4 - c4-judge
2023-11-07T12:22:58Z
MarioPoneder marked the issue as satisfactory
#5 - c4-judge
2023-11-07T12:38:58Z
MarioPoneder marked issue #196 as primary and marked this issue as a duplicate of 196