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: 48/131
Findings: 3
Award: $104.42
🌟 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
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarket.sol#L142
The platform experiences a critical issue where markets are unable to be closed as intended. This leads to undesirable consequences, including the continuous ability for lenders to make deposits and collect interest, even when it should not be allowed.
The issue at hand is in direct contradiction to the documentation, which indicates that borrowers should have the ability to close a market when necessary. Per the documentation:
In the event that a borrower has finished utilising the funds for the purpose that the market was set up to facilitate (or if lenders are choosing not to withdraw their assets and the borrower is paying too much interest on assets that have been re-deposited to the market), the borrower can close a market at will.
closeMarket()
can only be called by WildcatMarketController due to onlyController
modifier.
function closeMarket() external onlyController nonReentrant {
However, closeMarket()
is not implemented anywhere on WildcatMarketController.sol. This function is not accessible to anyone and lenders can continue to deposit()
funds and borrower will have to pay interest on funds they are not utilizing.
Manual Review
Implement _closeMarket()
on WilcatMarketController.sol
function _closeMarket() external onlyBorrower { closeMarket(); }
Context
#0 - c4-pre-sort
2023-10-27T07:18:09Z
minhquanym marked the issue as duplicate of #147
#1 - c4-judge
2023-11-07T14:04:54Z
MarioPoneder marked the issue as partial-50
#2 - c4-judge
2023-11-07T14:16:54Z
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/main/src/market/WildcatMarketToken.sol#L64 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketConfig.sol#L74 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L182 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketConfig.sol#L112 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketWithdrawals.sol#L77
Users designated as "sanctioned" by the Chainalysis oracle can effectively circumvent their sanctioned status, enabling them to accrue interest and withdraw funds without restriction.
The Chainalysis oracle identifies users as "sanctioned" based on specific criteria. However, the market does not immediately register this status until a user initiates the nukeFromOrbit()
function. This function transfers a sanctioned user's market tokens into an escrow. These tokens are effectively locked until the user's sanctioned status is overridden.
The existing security mechanism fails to entirely prevent sanctioned users from accessing their tokens. The core issue lies within the _transfer()
function in WildcatMarketToken.sol. A sanctioned user can easily front-run the nukeFromOrbit()
function and then use transfer()
to send their tokens to another one of their accounts. This manipulation bypasses the security checks since the account.approval
status is not yet set to AuthRole.Blocked
.
function _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); }
Once this transfer is executed, the user can call updateLenderAuthorization()
, which triggers updateAccountAuthorization()
. This, in turn, permits the user to access queueWithdrawal()
. The outcome is that the "sanctioned" user can effectively avoid being banned from the market, continuing to accumulate interest and withdraw funds as if they were a regular, unsanctioned user.
function updateAccountAuthorization( address _account, bool _isAuthorized ) external onlyController nonReentrant { MarketState memory state = _getUpdatedState(); Account memory account = _getAccount(_account); if (_isAuthorized) { account.approval = AuthRole.DepositAndWithdraw; } else { account.approval = AuthRole.WithdrawOnly; } _accounts[_account] = account; _writeState(state); emit AuthorizationStatusUpdated(_account, account.approval); }
Manual Review
import { WildcatSanctionsSentinel } from './WildcatSanctionsSentinel.sol' into WildcatMarketToken.sol
initialize it with correct address and add isSanctioned()
checks for both from
and to
.
Context
#0 - c4-pre-sort
2023-10-27T08:07:19Z
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:41:36Z
MarioPoneder marked the issue as satisfactory
🌟 Selected for report: osmanozdemir1
Also found by: 0xCiphky, 0xStalin, HChang26, Infect3d, Jiamin, Juntao, QiuhaoLi, circlelooper, crunch, rvierdiiev
91.2409 USDC - $91.24
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L163 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketConfig.sol#L74 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/MarketState.sol#L87 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/MarketState.sol#L123 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/MarketState.sol#L138
The _blockAccount()
function fails to accurately adjust state.scaledTotalSupply
, resulting in a cascade of accounting inaccuracies. This miscalculation leads to inflated figures for borrowable assets, liquidity requirements, and total debt
The Chainalysis oracle designates users as "sanctioned" if they meet certain criteria. The key issue revolves around the _blockAccount()
function, which can be invoked by anyone through the nukeFromOrbit()
method:
In this process, account.scaledBalance
is reset to 0, and a corresponding escrow is created. The scaledBalance
value is then transferred into the escrow's _accounts[escrow].scaledBalance
. It's important to note that state.scaledTotalSupply
for the entire market is not reduced accordingly.
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; } }
The absence of an adjustment to state.scaledTotalSupply
triggers a series of internal accounting inaccuracies. It inflates critical metrics such as liquidityRequired()
, totalDebts()
, and borrowableAssets()
.
Manual Review
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; + state.scaledTotalSupply -= scaledBalance; emit SanctionedAccountAssetsSentToEscrow( accountAddress, escrow, state.normalizeAmount(scaledBalance) ); } _accounts[accountAddress] = account; } }
Math
#0 - c4-pre-sort
2023-10-27T17:10:52Z
minhquanym marked the issue as duplicate of #123
#1 - c4-judge
2023-11-07T18:17:11Z
MarioPoneder marked the issue as not a duplicate
#2 - c4-judge
2023-11-07T18:17:16Z
MarioPoneder marked the issue as unsatisfactory: Insufficient proof
#3 - Henrychang26
2023-11-13T16:51:00Z
Hi, this should be a valid dupe of #550. It mentions the core problem of the issue: scaledBalance
not being removed from scaledTotalSupply
. Similar valid dupes are #286, #416
#4 - MarioPoneder
2023-11-14T22:15:54Z
Thank you for your comment!
I apologize for being too quick and harsh with this one.
After re-consideration, it seems justified to restore the duplication.
#5 - c4-judge
2023-11-14T22:16:08Z
MarioPoneder marked the issue as duplicate of #550
#6 - c4-judge
2023-11-14T22:16:25Z
MarioPoneder marked the issue as satisfactory