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: 69/131
Findings: 3
Award: $23.41
π 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.1213 USDC - $0.12
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarket.sol#L142 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketConfig.sol#L134
It's impossible to set a new max total supply or to close a market on an existing market.
The two functions WildcatMarketConfig::setMaxTotalSupply()
and WildcatMarket::closeMarket()
have the onlyController
modifier to restrict the access of theses methods to only WildcatMarketController
can call them. But if we check in the WildcatMarketController
contract, no function or arbitrary call can call theses functions. And because only the controller contract can call them, the two functions are unusable.
It lead to an impossibility to update the maximum total supply of the market and even worse to close a market.
It impossible for the borrower to restrict the deposit method / entry point to potential lenders inside in his market.
Manual Review
Add these two functions on WildcatMarketController
contract
Access Control
#0 - c4-pre-sort
2023-10-27T07:02:35Z
minhquanym marked the issue as duplicate of #147
#1 - c4-judge
2023-11-07T13:53:20Z
MarioPoneder changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-11-07T13:57:51Z
MarioPoneder marked the issue as satisfactory
#3 - 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#L64-L82 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketBase.sol#L163-L187
A lender can transfer his tokens before any potential call to nukeFromOrbit()
to avoid an escrow to be created with his funds and keep them even if he is sanctionned. He can make bad operations without fear of his tokens being escrowed.
Check this scenario:
WHCDAI
. He has two wallets (Wallet1 & Wallet2) of lenders accepted in the market.WildcatMarketConfig::nukeFromOrbit()
.nukeFromOrbit()
with his address to frontrun it with a WildcatMarketToken::transfer()
to transfer his WHCDAI
to his second wallet (Wallet2).
- He can simply make a transfer()
when he know his address is blacklisted by the Chainalysis oracle but not yet in the market.nukeFromOrbit()
is executed and because the Bob Wallet1.scaledBalance is now equal to zero, AuthRole.Blocked is set for the Wallet1, but escrow is not created and the sanctionned lenders keep all his funds but now on the Wallet2 and he can continue to take action on the market or withdraw.Check the _blockAccount()
function to better understand:
function _blockAccount(MarketState memory state, address accountAddress) internal { Account memory account = _accounts[accountAddress]; if (account.approval != AuthRole.Blocked) { uint104 scaledBalance = account.scaledBalance; account.approval = AuthRole.Blocked; emit AuthorizationStatusUpdated(accountAddress, AuthRole.Blocked); if (scaledBalance > 0) { account.scaledBalance = 0; address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow( accountAddress, borrower, address(this) ); emit Transfer(accountAddress, escrow, state.normalizeAmount(scaledBalance)); _accounts[escrow].scaledBalance += scaledBalance; emit SanctionedAccountAssetsSentToEscrow( accountAddress, escrow, state.normalizeAmount(scaledBalance) ); } _accounts[accountAddress] = account; } }
I don't know the exact scenarios which a lenders can be sanctionned by the Chainalysis oracle. But it's a possible problematic operation because if a lender have two registered wallets on the market (consciously or unconsciously for the borrower). He can potentially try to make bad operations with one address without fear of being sanctionned by transfering his claimable power to his second address.
Manual Review
Add a verification step into the custom transfer()
and transferFrom()
functions in WildcatMarketToken
to verify if a lender is sanctionned and prevent him to transfer his tokens when it's the case.
Context
#0 - c4-pre-sort
2023-10-27T03:23:05Z
minhquanym marked the issue as duplicate of #54
#1 - c4-judge
2023-11-07T14:40:31Z
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
Because some ERC20 tokens can be paused. In this case, the state.timeDelinquent
is still updated and an unfair APR penalty for the borrower may be added when the market is in delinquent state.
If a lender decide to use WildcatMarketWithdrawals::queueWithdrawal()
, he can make the market delinquent in sending the reserve ratio of the market below it's delinquent threshold. Like it's explained below in the docs:
https://wildcat-protocol.gitbook.io/wildcat/using-wildcat/delinquency#unclaimed-pending-withdrawals-and-delinquency
"The reserve ratio of a market can be temporarily raised depending on the amount of assets that are either earmarked for withdrawal by a lender, or are part of a pending withdrawal request. ... "
Now the market is in delinquent state and when the reserve ratio is not sufficient, the borrower have to send tokens in the market to return in a non-delinquent state. When the market is in delinquent state, the borrower have a grace period, it's a certain amount of time to return funds to the market before the penalty APR (additional interest rate penalty) must be applied and penalize the borrower.
The problem is a large number often used ERC20 tokens have functionnality to pause for preventing all interactions with the contract in case of problems/hack. It's the case for the USDT token contract, USDC, PAXG, WBTC, etc. If an ERC20 tokens used in collateral for Wildcat Market is paused, no transactions is permitted with it. Therefore the borrower can't send back tokens to the market and stop the delinquent state of the market. But during the time the ERC20 is paused, time goes by and impact the borrower by potentially get the market beyonf the grace period and adding penaltyAPR to the market. Even more if the borrower have set a very low grace period to boost lenders confidence in his market.
Manual Review
You can implement a function to inform the code that the asset contract is paused.
A potential way to make it is to add a function inside the WildcatArchController
that can be triggered manually by the owner (a borrower or lender can't have power on this feature) to specify the a specific asset is paused. Then in FeeMath::updateTimeDelinquentAndGetPenaltyTime()
, verify the paused status on WildcatArchController
and prevent the update of state.timeDelinquent
if the ERC20 token of the contract is paused.
This is a sensitive element that can be difficult to add to the code, but it's important to guarantee the borrower that he won't pay unjustified penalty.
ERC20
#0 - c4-pre-sort
2023-10-28T13:23:32Z
minhquanym marked the issue as low quality report
#1 - c4-pre-sort
2023-10-28T13:23:37Z
minhquanym marked the issue as sufficient quality report
#2 - minhquanym
2023-10-28T13:23:39Z
Consider QA
#3 - laurenceday
2023-10-31T23:52:30Z
Willing to acknowledge as QA.
#4 - c4-sponsor
2023-10-31T23:52:39Z
laurenceday (sponsor) acknowledged
#5 - c4-sponsor
2023-10-31T23:52:47Z
laurenceday marked the issue as disagree with severity
#6 - c4-judge
2023-11-07T22:14:11Z
MarioPoneder changed the severity to QA (Quality Assurance)
#7 - MarioPoneder
2023-11-07T22:16:46Z
This is a good feature suggestion to account for paused tokens.
However, the users the decide which tokens they want to use with this protocol, therefore it's a risk they have to bear and not a bug of the protocol per se.
#8 - c4-judge
2023-11-09T14:00:58Z
MarioPoneder marked the issue as grade-b