There are three states which mark the end of an order: settled, reclaimed and cancelled. When matching an order, the checkIsValidOrder function makes checks to ensure that:
reclaimed orders aren't matched. This is ensured by checking order.expiry
cancelled orders aren't matched. This is ensured by checking !canceledOrders[] require statement
It is never checked before matching if an offer has already been settled.
Vulnerability Detail
Attacker does the following steps:
Create order as a bear.
Match against their own order as the bull.
Settle the order by calling settleContract. This transfer NFTs to themself, along with collateral. Net cost = fees. This also sets settledContracts[contractId] = true;
Transfer away ownership to address(0) from the bull's side. This sets bulls[uint(orderHash)] = address(0)
Have another bull match. A bull can match since the bull of this order is address(0), which bypasses all the checks in checkIsValidOrder function
This bull has paid the premium, but cannot ever call reclaimContract function since the require(!settledContracts[contractId], "SETTLED_CONTRACT"); will never pass as the contract is already marked as settled.
The bear can also never settle the order, but their cost is very low (only the premium). This is essentially a stuck order
Impact
Trap orders which can never be settled or reclaimed, losing the bull their premium
Add a require statement to check for settled orders in checkIsValidOrder function
require(!settledContracts[uint(orderHash)], "Order already settled");
carrot
high
Missing settled orders check
Summary
There are three states which mark the end of an order: settled, reclaimed and cancelled. When matching an order, the
checkIsValidOrder
function makes checks to ensure that:order.expiry
!canceledOrders[]
require statementIt is never checked before matching if an offer has already been settled.
Vulnerability Detail
Attacker does the following steps:
settleContract
. This transfer NFTs to themself, along with collateral. Net cost = fees. This also setssettledContracts[contractId] = true;
bulls[uint(orderHash)] = address(0)
checkIsValidOrder
functionreclaimContract
function since therequire(!settledContracts[contractId], "SETTLED_CONTRACT");
will never pass as the contract is already marked as settled.Impact
Trap orders which can never be settled or reclaimed, losing the bull their premium
Code Snippet
https://github.com/sherlock-audit/2022-11-bullvbear/blob/main/bvb-protocol/src/BvbProtocol.sol#L734-L761
Tool used
Manual Review
Recommendation
Add a require statement to check for settled orders in
checkIsValidOrder
functionrequire(!settledContracts[uint(orderHash)], "Order already settled");
Duplicate of #114