The Wildcat Protocol - circlelooper's results

Banking, but worse - a protocol for fixed-rate, undercollateralised credit facilities.

General Information

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

Wildcat Protocol

Findings Distribution

Researcher Performance

Rank: 50/131

Findings: 2

Award: $91.30

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarket.sol#L142-L161

Vulnerability details

Impact

Market can not be closed.

Proof of Concept

The closeMarket method in WildcatMarket.sol sets the market APR to 0% and marks market as closed.

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); }

However, this method can not be called directly, there is modifier onlyController so it can only be called by controller:

modifier onlyController() { if (msg.sender != controller) revert NotController(); _; }

This controller is WildcatMarketController:

/// @dev Address of the Market Controller. address public immutable controller;

and is initialized when the market is deployed by WildcatMarketController:

controller = parameters.controller; `` The problem is that closeMarket method is not called in WildcatMarketController, so there is no way to close a market. ## Tools Used Manual Review ## Recommended Mitigation Steps To mitigate this issue, an method should be defined to call closeMarket method from WildcatMarketController. ## Assessed type Access Control

#0 - c4-pre-sort

2023-10-27T07:25:53Z

minhquanym marked the issue as duplicate of #147

#1 - c4-judge

2023-11-07T13:53:21Z

MarioPoneder changed the severity to 2 (Med Risk)

#2 - c4-judge

2023-11-07T14:06:05Z

MarioPoneder marked the issue as partial-50

#3 - c4-judge

2023-11-07T14:16:53Z

MarioPoneder changed the severity to 3 (High Risk)

Findings Information

🌟 Selected for report: osmanozdemir1

Also found by: 0xCiphky, 0xStalin, HChang26, Infect3d, Jiamin, Juntao, QiuhaoLi, circlelooper, crunch, rvierdiiev

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
duplicate-550

Awards

91.2409 USDC - $91.24

External Links

Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketBase.sol#L163-L187

Vulnerability details

Impact

Should remove account's scaledBalance from market's scaledTotalSupply when an account is blocked, or borrower will pay unnecessary interest for the blocked account.

Proof of Concept

_blockAccount method is used to block an account:

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; } }

If the account's scaledBalance is not 0, then the account's scaledBalance will be set to 0. As the blocked account's supply is 0, no interest should be accrued. The problem is that market state's scaledTotalSupply is not updated accordingly, and the interest a borrower needs to pay is calculated based on scaledTotalSupply, as wen can see from totalDebts method, that means interest still accrues unnecessarily even if an account is blocked:

function totalDebts(MarketState memory state) internal pure returns (uint256) { return state.normalizeAmount(state.scaledTotalSupply) + state.normalizedUnclaimedWithdrawals + state.accruedProtocolFees; }

Tools Used

Manual Review

To mitigate this issue, remove account's scaledBalance from market's scaledTotalSupply when an account is blocked

Assessed type

Math

#0 - c4-pre-sort

2023-10-28T14:37:53Z

minhquanym marked the issue as duplicate of #123

#1 - c4-judge

2023-11-07T18:14:26Z

MarioPoneder changed the severity to 2 (Med Risk)

#2 - c4-judge

2023-11-07T18:18:08Z

MarioPoneder marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter