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: 70/131
Findings: 3
Award: $23.35
🌟 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
According to WildcatMarket.closeMarket's definition, the function can only be called by controller
, but within WildcatMarketController.sol
contract, WildcatMarket.closeMarket
is not called.
In such case, it makes the borrower can't close the market, if the borrower pays more than his/her debt, event he/she can set annualInterestBips=0 and setReserveRatioBips=0, the remaining token will be stucked in the market.
WildcatMarket.closeMarket is defined as:
function closeMarket() external onlyController nonReentrant { <----------------- only controller can call this funciton MarketState memory state = _getUpdatedState(); state.annualInterestBips = 0; state.isClosed = true; state.reserveRatioBips = 0; if (_withdrawalData.unpaidBatches.length() > 0) { revert CloseMarketWithUnpaidWithdrawals(); } uint256 currentlyHeld = totalAssets(); uint256 totalDebts = state.totalDebts(); if (currentlyHeld < totalDebts) { // Transfer remaining debts from borrower asset.safeTransferFrom(borrower, address(this), totalDebts - currentlyHeld); } else if (currentlyHeld > totalDebts) { // Transfer excess assets to borrower asset.safeTransfer(borrower, currentlyHeld - totalDebts); } _writeState(state); emit MarketClosed(block.timestamp); }
As above code shows, onlyController
modifier is used here, which makes the function can only be called by controller, but within WildcatMarketController.sol
contract, WildcatMarket.closeMarket
is not called
VIM
Adding WildcatMarket.closeMarket
related code in WildcatMarketController.sol
diff --git a/src/WildcatMarketController.sol b/src/WildcatMarketController.sol index 55f62f6..a39565b 100644 --- a/src/WildcatMarketController.sol +++ b/src/WildcatMarketController.sol @@ -487,6 +487,13 @@ contract WildcatMarketController is IWildcatMarketControllerEventsAndErrors { WildcatMarket(market).setAnnualInterestBips(annualInterestBips); } + function closeMarket( + address market + ) external virtual onlyBorrower onlyControlledMarket(market) { + + WildcatMarket(market).closeMarket(); + } + function resetReserveRatio(address market) external virtual { TemporaryReserveRatio memory tmp = temporaryExcessReserveRatio[market]; if (tmp.expiry == 0) {
Other
#0 - c4-pre-sort
2023-10-27T07:12:18Z
minhquanym marked the issue as duplicate of #147
#1 - c4-judge
2023-11-07T14:02:02Z
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: 0xStalin
Also found by: 0xCiphky, 0xComfyCat, 0xbepresent, 3docSec, Eigenvectors, Fulum, HChang26, Infect3d, QiuhaoLi, SandNallani, SovaSlava, TrungOre, YusSecurity, ZdravkoHr, ast3ros, ayden, bdmcbri, cu5t0mpeo, elprofesor, gizzy, jasonxiale, kodyvim, marqymarq10, max10afternoon, nisedo, nobody2018, radev_sw, rvierdiiev, serial-coder, xeros
13.1205 USDC - $13.12
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketToken.sol#L36-L39 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarket.sol#L42-L77 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketWithdrawals.sol#L137-L188
When lender deposits into the market, he/she will get some WildcatMarketToken
, because WildcatMarketToken.transfer
doesn't stop sanctioned lender from transferring the WildcatMarketToken
, an sanctioned lender can transfer his token to another lender(who is not sanctioned), and then the second lender can withdraw the assets.
function depositUpTo( uint256 amount ) public virtual nonReentrant returns (uint256 /* actualAmount */) { ... // Cache account data and revert if not authorized to deposit. Account memory account = _getAccountWithRole(msg.sender, AuthRole.DepositAndWithdraw); <---- Here mint WildcatMarketToken account.scaledBalance += scaledAmount; _accounts[msg.sender] = account; ... }
from
or to
address are sanctionedfunction _transfer(address from, address to, uint256 amount) internal virtual { MarketState memory state = _getUpdatedState(); uint104 scaledAmount = state.scaleAmount(amount).toUint104(); if (scaledAmount == 0) { revert NullTransferAmount(); } Account memory fromAccount = _getAccount(from); fromAccount.scaledBalance -= scaledAmount; _accounts[from] = fromAccount; Account memory toAccount = _getAccount(to); toAccount.scaledBalance += scaledAmount; _accounts[to] = toAccount; _writeState(state); emit Transfer(from, to, amount); }
WildcatMarketWithdrawals.queueWithdrawal
, and after the withdrawalBatchDuration
she calls WildcatMarketWithdrawals.executeWithdrawal to withdraw her asset. But because she is sanctioned. the if branch in L164-L178 is executed, and her asset will be transferred to an escrow
.
To avoid her asset being transferred to escrow
, she can call WildcatMarketToken.transfer to transfer her asset to another lender address she owns, and withdraw her asset as other usersVIM
Other
#0 - c4-pre-sort
2023-10-27T03:15:34Z
minhquanym marked the issue as duplicate of #54
#1 - c4-judge
2023-11-07T14:36:22Z
MarioPoneder changed the severity to 3 (High Risk)
#2 - c4-judge
2023-11-07T14:37:32Z
MarioPoneder marked the issue as satisfactory
🌟 Selected for report: J4X
Also found by: 0x3b, 0xCiphky, 0xComfyCat, 0xStalin, 0xbepresent, 3docSec, DavidGiladi, DeFiHackLabs, Drynooo, Fulum, GREY-HAWK-REACH, InAllHonesty, MatricksDeCoder, Mike_Bello90, MiloTruck, Phantom, SHA_256, T1MOH, Udsen, VAD37, YusSecurity, ZdravkoHr, ast3ros, caventa, deepkin, deth, devival, ggg_ttt_hhh, inzinko, jasonxiale, josieg_74497, karanctf, ke1caM, nisedo, nobody2018, nonseodion, osmanozdemir1, radev_sw, rvierdiiev, serial-coder, t0x1c
10.1663 USDC - $10.17
WildcatMarketConfig.setMaxTotalSupply
is never be calledWildcatMarketConfig.setMaxTotalSupply
has a modifier which only can be called by controller, but after searching through controller's code, the function is never be called.
WildcatMarketControllerFactory.setProtocolFeeConfiguration
missing UpdateProtocolFeeConfiguration
eventFile:
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketControllerFactory.sol#L195C12-L217
Accoring to UpdateProtocolFeeConfiguration, an event UpdateProtocolFeeConfiguration
is defined for function setProtocolFeeConfiguration
, but the event is never used
WildcatMarketControllerFactory.deployController
missing NewController
eventFile:
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketControllerFactory.sol#L282-L301
According to comments and NewController, an event NewController
should be emitted in function deployController
, but the event is never emitted
WildcatMarketController.updateLenderAuthorization
, _authorizedLenders.contains(lender) can cachedFile:
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L188
In WildcatMarketController.updateLenderAuthorization, _authorizedLenders.contains(lender)
can be cached to save some gas within for-loop
diff --git a/src/WildcatMarketController.sol b/src/WildcatMarketController.sol index 55f62f6..7ce3c6f 100644 --- a/src/WildcatMarketController.sol +++ b/src/WildcatMarketController.sol @@ -180,12 +180,13 @@ contract WildcatMarketController is IWildcatMarketControllerEventsAndErrors { * status. */ function updateLenderAuthorization(address lender, address[] memory markets) external { + bool _isAuthorized = _authorizedLenders.contains(lender); for (uint256 i; i < markets.length; i++) { address market = markets[i]; if (!_controlledMarkets.contains(market)) { revert NotControlledMarket(); } - WildcatMarket(market).updateAccountAuthorization(lender, _authorizedLenders.contains(lender)); + WildcatMarket(market).updateAccountAuthorization(lender, _isAuthorized); } }
MathUtils.calculateLinearInterestFromBips
can be removedMathUtils.calculateLinearInterestFromBips is only called by FeeMath.sol#L34-L37, but the same function calculateLinearInterestFromBips
is defined in FeeMath.sol, which means we can remove MathUtils.calculateLinearInterestFromBips
LibStoredInitCode.create2WithStoredInitCode
doesn't check create2
return valueFile:
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/libraries/LibStoredInitCode.sol#L115
According to yul document, create2
might return 0, so the function should check create2
return value
#0 - c4-judge
2023-11-09T15:32:30Z
MarioPoneder marked the issue as grade-b