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: 108/131
Findings: 1
Award: $3.34
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
3.3357 USDC - $3.34
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L163
In function _blockAccount
in WildcatMarketBase.sol, the parameters for createEscrow
are passed in the wrong order leading to unintended behaviour of the newly created Escrow contract and opening up a lot of issues.
Let's see the part of the _blockAccount
function where we call createEscow
:
function _blockAccount(MarketState memory state, address accountAddress) internal { if (scaledBalance > 0) { account.scaledBalance = 0; address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow( accountAddress, borrower, address(this) ); }
while the createEscrow
function expects the parameters in the following order:
function createEscrow( address borrower, address account, address asset ) public override returns (address escrowContract) {}
Instead of borrower, we're passing the account and vice versa. This leads to multiple issues like the supposed blocked lender being able to release funds. The blockAccount
function is used in nukeFromOrbit
and results in not really being able to block someone.
Here is a POC that I added to WildcatMarketConfig.t.sol
:
You need to add this to the test file before that:
import { WildcatSanctionsEscrow, IWildcatSanctionsEscrow } from 'src/WildcatSanctionsEscrow.sol'; contract MockWildcatArchController { mapping(address market => bool) public isRegisteredMarket; function setIsRegisteredMarket(address market, bool isRegistered) external { isRegisteredMarket[market] = isRegistered; } }
And the test:
function test_nukeFromOrbitIssue() public { //deploying mock archController MockWildcatArchController archController; archController = new MockWildcatArchController(); //deploying mock SanctionsSentinel sanctionsSentinel = new MockSanctionsSentinel(address(archController)); //set this address as registered market archController.setIsRegisteredMarket(address(this), true); //deposit in market vm.prank(alice); market.deposit(1e18); //sanction alice sanctionsSentinel.sanction(alice); //nuke it and assert it's blocked market.nukeFromOrbit(alice); assertEq( uint(market.getAccountRole(alice)), uint(AuthRole.Blocked), 'account role should be Blocked' ); //create the escrow with the param order in blockAccount WildcatSanctionsEscrow escrow = WildcatSanctionsEscrow( sanctionsSentinel.createEscrow(alice, borrower, address(market)) ); //should be false as Alice is sanctioned by borrower //but returns true because what it checks actually is if borrower is sanctioned by Alice console.log("canReleaseEscrow: ", escrow.canReleaseEscrow()); }
Manual review, Foundry
Pass the parameters correctly:
function createEscrow(address borrower, address account, address asset)
Error
#0 - c4-pre-sort
2023-10-27T02:31:40Z
minhquanym marked the issue as duplicate of #515
#1 - c4-judge
2023-11-07T11:57:21Z
MarioPoneder marked the issue as satisfactory
#2 - c4-judge
2023-11-07T11:57:43Z
MarioPoneder marked the issue as partial-50