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: 24/131
Findings: 2
Award: $387.88
🌟 Selected for report: 0
🚀 Solo Findings: 0
377.709 USDC - $377.71
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarket.sol#L93-L131 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketBase.sol#L448-L453 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/libraries/FeeMath.sol#L97-L111
"WildcatMarket.collectFees()" and "WildcatMarket.borrow(uint256 amount)" can break flow of "state.isDelinquent" and "state.timeDelinquent" calculation. That affect grace period reduction logic and result penalties.
_writeState(state); asset.safeTransfer(feeRecipient, withdrawableFees); emit FeesCollected(withdrawableFees);
_writeState(state); asset.safeTransfer(msg.sender, amount); emit Borrow(amount);
function _writeState(MarketState memory state) internal { bool isDelinquent = state.liquidityRequired() > totalAssets(); state.isDelinquent = isDelinquent; _state = state; emit StateUpdated(state.scaleFactor, isDelinquent); } function totalAssets() public view returns (uint256) { return IERC20(asset).balanceOf(address(this)); }
state.updateScaleFactorAndFees( protocolFeeBips, delinquencyFeeBips, delinquencyGracePeriod, expiry );
if (state.isDelinquent) { // Since the borrower is still delinquent, increase the total // time in delinquency by the time elapsed. state.timeDelinquent = (previousTimeDelinquent + timeDelta).toUint32(); // Calculate the number of seconds the borrower had remaining // in the grace period. uint256 secondsRemainingWithoutPenalty = delinquencyGracePeriod.satSub( previousTimeDelinquent ); // Penalties apply for the number of seconds the market spent in // delinquency outside of the grace period since the last update. return timeDelta.satSub(secondsRemainingWithoutPenalty); }
Manual check.
Use "_writeState(state)" in the end of function, after all state changes and Underlying Assets changes applied. This will bring consistency between MarketState and Underlying Assets balance.
Other
#0 - c4-pre-sort
2023-10-27T15:08:50Z
minhquanym marked the issue as duplicate of #36
#1 - c4-judge
2023-11-07T15:16:20Z
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
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatArchController.sol#L85
function getRegisteredBorrowers( uint256 start, uint256 end ) external view returns (address[] memory arr) { uint256 len = _borrowers.length(); end = MathUtils.min(end, len); uint256 count = end - start; arr = new address[](count); for (uint256 i = 0; i < count; i++) { arr[i] = _borrowers.at(start + i); } }
This function is literally reused in multiple places like https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatArchController.sol#L128 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatArchController.sol#L171 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatArchController.sol#L214 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L125 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L204 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketControllerFactory.sol#L138
Incapsulate logic into lib function with AddressSet or list parameter. Code will be more readable and you will minimise refactoring error.
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/ReentrancyGuard.sol#L7
Keep dev doc clean and valid. Create your own blob if needed.
function _setReentrancyGuard() internal { // Ensure that the reentrancy guard is not already set. _assertNonReentrant(); // Set the reentrancy guard. unchecked { _reentrancyGuard = _ENTERED; } }
_setReentrancyGuard() use unchecked block to set current value but _clearReentrancyGuard()
function _clearReentrancyGuard() internal { // Clear the reentrancy guard. _reentrancyGuard = _NOT_ENTERED; }
While both _NOT_ENTERED and _ENTERED is integers.
Use consistent approach, unchecked here doesn't looks useful
if ( constraints.minimumAnnualInterestBips > constraints.maximumAnnualInterestBips || constraints.maximumAnnualInterestBips > 10000 || constraints.minimumDelinquencyFeeBips > constraints.maximumDelinquencyFeeBips || constraints.maximumDelinquencyFeeBips > 10000 || constraints.minimumReserveRatioBips > constraints.maximumReserveRatioBips || constraints.maximumReserveRatioBips > 10000 || constraints.minimumDelinquencyGracePeriod > constraints.maximumDelinquencyGracePeriod || constraints.minimumWithdrawalBatchDuration > constraints.maximumWithdrawalBatchDuration ) { revert InvalidConstraints(); }
Use constants like MathUtils.BIP.
function _storeControllerInitCode() internal virtual returns (address initCodeStorage, uint256 initCodeHash) { bytes memory controllerInitCode = type(WildcatMarketController).creationCode; initCodeHash = uint256(keccak256(controllerInitCode)); initCodeStorage = LibStoredInitCode.deployInitCode(controllerInitCode); }
function _storeMarketInitCode() internal virtual returns (address initCodeStorage, uint256 initCodeHash) { bytes memory marketInitCode = type(WildcatMarket).creationCode; initCodeHash = uint256(keccak256(marketInitCode)); initCodeStorage = LibStoredInitCode.deployInitCode(marketInitCode); }
Both used in constructor exactly once.
Put in the end to optimise contract gas usage.
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketBase.sol#L498-L551 Both functions pretty big and similar, duplication of such parts can bring inconsistency.
Use DRY principle and share main logic part.
#0 - c4-judge
2023-11-09T16:05:46Z
MarioPoneder marked the issue as grade-b