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: 72/131
Findings: 3
Award: $19.85
🌟 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
No viable means to close markets.
Markets can be closed if there is no unpaid withdrawal batches and all debts would be paid. https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarket.sol#L142
function closeMarket() external onlyController nonReentrant {<@ 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); } }
The issue is that closeMarket
uses onlyController
for access control within the controller contract their no implementation that utilizes closeMarket
.
This breaks the closeMarket
functionality since no one can actually access that function.
Manual Review
Since borrowers creates and manage market they should be able to close them when it's no longer need and all debts are paid.
Change the modifier to onlyBorrower
Invalid Validation
#0 - c4-pre-sort
2023-10-27T07:29:42Z
minhquanym marked the issue as duplicate of #147
#1 - c4-judge
2023-11-07T14:06:52Z
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/main/src/market/WildcatMarketWithdrawals.sol#L165
Sanctioned users can transfer to escape sanctions
Lenders can be sanctioned if a sanctioned lender tries to withdraw there funds would be blocked and escrowed. but lender can simply escape sanction by transferring their funds to another account they control before the queue for withdrawal. https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketWithdrawals.sol#L165
function executeWithdrawal( address accountAddress, uint32 expiry ) external nonReentrant returns (uint256) { ...SNIP if (IWildcatSanctionsSentinel(sentinel).isSanctioned(borrower, accountAddress)) { @> _blockAccount(state, accountAddress); address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow( accountAddress, borrower, address(asset) ); asset.safeTransfer(escrow, normalizedAmountWithdrawn); emit SanctionedAccountWithdrawalSentToEscrow( accountAddress, escrow, expiry, normalizedAmountWithdrawn ); } else { asset.safeTransfer(accountAddress, normalizedAmountWithdrawn); } emit WithdrawalExecuted(expiry, accountAddress, normalizedAmountWithdrawn); // Update stored state _writeState(state); return normalizedAmountWithdrawn; }
One could argue that borrower would nukeFromOrbit to manually block the lender, then the borrower would have to nukeFromOrbit
from all markets in which the lender participates in which could be inefficient as it defeats the purpose of automatically blocking the lender when he/she tries to withdraw and could also be susceptible to frontrun.
Since _transfer does not check whether they user is sanctioned, they could simply transfer to another account.
Manual Review
Within _transfer
check if the from
has been sanctioned, if sanctioned revert.
Context
#0 - c4-pre-sort
2023-10-27T03:13:59Z
minhquanym marked the issue as duplicate of #54
#1 - c4-judge
2023-11-07T14:36:24Z
MarioPoneder changed the severity to 3 (High Risk)
#2 - c4-judge
2023-11-07T14:36:35Z
MarioPoneder marked the issue as satisfactory
🌟 Selected for report: YusSecurity
Also found by: 0xAsen, 0xCiphky, 0xDING99YA, 0xKbl, 0xSwahili, 0xbepresent, 3docSec, AS, Aymen0909, DeFiHackLabs, GREY-HAWK-REACH, KeyKiril, MiloTruck, QiuhaoLi, Silvermist, SovaSlava, TrungOre, VAD37, Vagner, Yanchuan, ZdravkoHr, ast3ros, cartlex_, d3e4, deth, ggg_ttt_hhh, gizzy, kodyvim, nirlin, nobody2018, rvierdiiev, serial-coder, sl1, tallo, xeros
6.6715 USDC - $6.67
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketWithdrawals.sol#L166 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L172
Escrowed funds would be wrongly sent to the borrower and can be triggered by anyone.
Escrow contract are used to lock funds for sanctioned user.
This issue is that when creating an escrow within market it mismatches the address of the borrower
and user
when call createEscrow
.
One Instance:
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketWithdrawals.sol#L166
function executeWithdrawal( address accountAddress, uint32 expiry ) external nonReentrant returns (uint256) { ...SNIP if (IWildcatSanctionsSentinel(sentinel).isSanctioned(borrower, accountAddress)) { _blockAccount(state, accountAddress); address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow( @> accountAddress, @> borrower, address(asset) ); asset.safeTransfer(escrow, normalizedAmountWithdrawn); emit SanctionedAccountWithdrawalSentToEscrow( accountAddress, escrow, expiry, normalizedAmountWithdrawn ); } else { asset.safeTransfer(accountAddress, normalizedAmountWithdrawn); } emit WithdrawalExecuted(expiry, accountAddress, normalizedAmountWithdrawn); // Update stored state _writeState(state); return normalizedAmountWithdrawn; }
Looking at createEscrow the params should be borrower followed by the account to be locked in.
function createEscrow( @> address borrower, @> address account, address asset ) public override returns (address escrowContract) { ...SNIP }
Due to this mismatch the borrower address would be seen as the lender address and vice versa within WildcatSanctionsEscrow
.
IChainalysisSanctionsList(chainalysisSanctionsList).isSanctioned(account);
would always return false.Another Instance of same issue: https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L172
function _blockAccount(MarketState memory state, address accountAddress) internal { ...SNIP 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; ...SNIP }
Manual Review
Place the borrower
and account
in the correct order as in the createEscrow
function.
Error
#0 - c4-pre-sort
2023-10-27T02:25:02Z
minhquanym marked the issue as duplicate of #515
#1 - c4-judge
2023-11-07T11:50:19Z
MarioPoneder marked the issue as satisfactory