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: 67/131
Findings: 3
Award: $29.84
π 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
No one can modify TotalSupply, which will result in the borrower being unable to modify TotalSupply.
Since this function needs to be called by the controller, but the controller does not implement the function of calling this function, this function cannot be called.
/** * @dev Sets the maximum total supply - this only limits deposits and * does not affect interest accrual. * * Can not be set lower than current total supply. */ function setMaxTotalSupply( uint256 _maxTotalSupply ) external onlyController nonReentrant { // @audit high - this function donβt call MarketState memory state = _getUpdatedState(); if (_maxTotalSupply < state.totalSupply()) { revert NewMaxSupplyTooLow(); } state.maxTotalSupply = _maxTotalSupply.toUint128(); _writeState(state); emit MaxTotalSupplyUpdated(_maxTotalSupply); }
Manual Review
Just add relevant functions in the controller
Error
#0 - c4-pre-sort
2023-10-27T06:21:56Z
minhquanym marked the issue as duplicate of #162
#1 - c4-pre-sort
2023-10-27T06:58:14Z
minhquanym marked the issue as duplicate of #147
#2 - c4-judge
2023-11-07T13:49:32Z
MarioPoneder marked the issue as partial-50
#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
In addition to block,strangers can set their approve to WithdrawOnly
Except for those whose approve is block, this function will set the permission of the parameter lender to WithdrawOnly.
/** * @dev Update lender authorization for a set of markets to the current * status. */ function updateLenderAuthorization(address lender, address[] memory markets) external { for (uint256 i; i < markets.length; i++) { address market = markets[i]; if (!_controlledMarkets.contains(market)) { revert NotControlledMarket(); } WildcatMarket(market).updateAccountAuthorization(lender, _authorizedLenders.contains(lender)); } }
updateAccountAuthorization
/** * @dev Updates an account's authorization status based on whether the controller * has it marked as approved. */ 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); }
poc:
// SPDX-License-Identifier: NONE pragma solidity >=0.8.20; import './BaseMarketTest.sol'; import 'src/interfaces/IMarketEventsAndErrors.sol'; import 'src/libraries/MathUtils.sol'; import 'src/libraries/SafeCastLib.sol'; import 'src/libraries/MarketState.sol'; import 'solady/utils/SafeTransferLib.sol'; import 'forge-std/console.sol'; contract MyTest is BaseMarketTest, IWildcatMarketControllerEventsAndErrors { function test_updateLenderAuthorization() public { address borrower = address(1); address account = address(2); address[] memory markets = new address[](1); markets[0] = address(market); vm.prank(account); controller.updateLenderAuthorization(account, markets); if (market.getAccountRole(account) == AuthRole.WithdrawOnly) { console.log("success"); } } }
result:
Running 1 test for test/MyTest.t.sol:MyTest [PASS] test_updateLenderAuthorization() (gas: 72967) Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.83ms
Manual Review
It is recommended to check whether the updateLenderAuthorization function is called by borrower
Access Control
#0 - c4-pre-sort
2023-10-27T08:38:11Z
minhquanym marked the issue as low quality report
#1 - c4-pre-sort
2023-10-27T08:40:53Z
minhquanym marked the issue as duplicate of #400
#2 - c4-pre-sort
2023-10-27T08:51:21Z
minhquanym marked the issue as duplicate of #155
#3 - c4-judge
2023-11-07T12:54:40Z
MarioPoneder marked the issue as satisfactory
#4 - c4-judge
2023-11-14T13:59:00Z
MarioPoneder changed the severity to 3 (High Risk)
#5 - c4-judge
2023-11-14T14:00:47Z
MarioPoneder marked the issue as partial-50
#6 - c4-judge
2023-11-14T14:02:26Z
MarioPoneder marked the issue as duplicate of #266
π 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/WildcatMarketToken.sol#L36-L57
Users can bypass Escrow to claim target tokens, making WildcatSanctionsEscrow
useless.
When the market token performs transfer
or transferFrom
, it will not determine whether the caller is in the block state.
function transfer(address to, uint256 amount) external virtual nonReentrant returns (bool) { _transfer(msg.sender, to, amount); return true; } function transferFrom( address from, address to, uint256 amount ) external virtual nonReentrant returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for unlimited approvals. if (allowed != type(uint256).max) { uint256 newAllowance = allowed - amount; _approve(from, msg.sender, newAllowance); } _transfer(from, to, amount); return true; } ... 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); }
A sanctioned lender
can transfer market tokens to other users, and then change its approve
to WithdrawOnly
to finally receive the target token through the lax permission check of updateLenderAuthorization
Manual Review
When market tokens are transferred, determine whether msg.sender has the authority to transfer tokens.
Token-Transfer
#0 - c4-pre-sort
2023-10-27T08:00:39Z
minhquanym marked the issue as duplicate of #54
#1 - c4-judge
2023-11-07T14:41:27Z
MarioPoneder marked the issue as satisfactory
π Selected for report: 3docSec
Also found by: 0xCiphky, 0xbepresent, 0xbrett8571, Eigenvectors, MiloTruck, Toshii, Tricko, TrungOre, ZdravkoHr, b0g0, caventa, cu5t0mpeo, deth, ggg_ttt_hhh, gizzy, joaovwfreire, josephdara, serial-coder, smiling_heretic, stackachu
16.6643 USDC - $16.66
Make annualInterestBips change to a value outside the range, causing some unnecessary errors
setAnnualInterestBips does not do a check from minimumAnnualInterestBips to maximumAnnualInterestBips
/** * @dev Modify the interest rate for a market. * If the new interest rate is lower than the current interest rate, * the reserve ratio is set to 90% for the next two weeks. */ function setAnnualInterestBips( address market, uint16 annualInterestBips ) external virtual onlyBorrower onlyControlledMarket(market) { // If borrower is reducing the interest rate, increase the reserve // ratio for the next two weeks. if (annualInterestBips < WildcatMarket(market).annualInterestBips()) { TemporaryReserveRatio storage tmp = temporaryExcessReserveRatio[market]; if (tmp.expiry == 0) { tmp.reserveRatioBips = uint128(WildcatMarket(market).reserveRatioBips()); // Require 90% liquidity coverage for the next 2 weeks WildcatMarket(market).setReserveRatioBips(9000); } tmp.expiry = uint128(block.timestamp + 2 weeks); } WildcatMarket(market).setAnnualInterestBips(annualInterestBips); }
poc(The MaximumAnnualInterestBips of the file TestConstants.sol needs to be set to 9000. The test sample is completed in WildcatMarketController.t.sol):
function test_setAnnualInterestBips_range() public { MarketParameterConstraints memory constraints = controller.getParameterConstraints(); vm.prank(borrower); console.log("before:%d\n",constraints.maximumAnnualInterestBips + 1); controller.setAnnualInterestBips(address(market), constraints.maximumAnnualInterestBips + 1); console.log("after:%d\n",market.annualInterestBips()); assertEq(market.annualInterestBips(), constraints.maximumAnnualInterestBips + 1, 'APR'); }
Manual Review
/** * @dev Modify the interest rate for a market. * If the new interest rate is lower than the current interest rate, * the reserve ratio is set to 90% for the next two weeks. */ function setAnnualInterestBips( address market, uint16 annualInterestBips ) external virtual onlyBorrower onlyControlledMarket(market) { if (annualInterestBips < MinimumAnnualInterestBips || annualInterestBips > MaximumAnnualInterestBips) { revert OutFlowRange(); } // If borrower is reducing the interest rate, increase the reserve // ratio for the next two weeks. if (annualInterestBips < WildcatMarket(market).annualInterestBips()) { TemporaryReserveRatio storage tmp = temporaryExcessReserveRatio[market]; if (tmp.expiry == 0) { tmp.reserveRatioBips = uint128(WildcatMarket(market).reserveRatioBips()); // Require 90% liquidity coverage for the next 2 weeks WildcatMarket(market).setReserveRatioBips(9000); } tmp.expiry = uint128(block.timestamp + 2 weeks); } WildcatMarket(market).setAnnualInterestBips(annualInterestBips); }
Other
#0 - c4-pre-sort
2023-10-27T14:14:19Z
minhquanym marked the issue as duplicate of #443
#1 - c4-judge
2023-11-07T12:25:52Z
MarioPoneder marked the issue as satisfactory