DittoETH

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

Unbound loop in `LibOrders.sellMatchAlgo()` can lead to out of gas error causing `sellMatchAlgo()` to fail

Summary

In the LibOrders.sellMatchAlgo() function's loop, there's no explicit exit condition defined, so it will loop indefinitely until some condition is met. If incomingAsk.ercAmount is large and highestBid.ercAmount is small, the loop may need to execute a large number of times, each time consuming Gas

Vulnerability Details

The code below represents the logic for matching sell orders. It searches for suitable buy orders in the order book and executes matching operations.

function sellMatchAlgo(
address asset,
STypes.Order memory incomingAsk,
MTypes.OrderHint[] memory orderHintArray,
uint256 minAskEth
) internal {
AppStorage storage s = appStorage();
uint16 startingId = s.bids[asset][Constants.HEAD].nextId;
STypes.Order storage highestBidInitial = s.bids[asset][startingId];
if (incomingAsk.price > highestBidInitial.price) {
if (incomingAsk.ercAmount.mul(incomingAsk.price) >= minAskEth) {
addSellOrder(incomingAsk, asset, orderHintArray);
}
return;
}
// matching loop starts
MTypes.Match memory matchTotal;
while (true) {
STypes.Order memory highestBid = s.bids[asset][startingId];
if (incomingAsk.price <= highestBid.price) {
// Consider ask filled if only dust amount left
if (incomingAsk.ercAmount.mul(highestBid.price) == 0) {
updateBidOrdersOnMatch(s.bids, asset, highestBid.id, false);
incomingAsk.ercAmount = 0;
matchIncomingSell(asset, incomingAsk, matchTotal);
return;
}
matchHighestBid(incomingAsk, highestBid, asset, matchTotal);
if (incomingAsk.ercAmount > highestBid.ercAmount) {
incomingAsk.ercAmount -= highestBid.ercAmount;
highestBid.ercAmount = 0;
matchOrder(s.bids, asset, highestBid.id);
// loop
startingId = highestBid.nextId;
if (startingId == Constants.TAIL) {
incomingAsk.shortRecordId =
matchIncomingSell(asset, incomingAsk, matchTotal);
if (incomingAsk.ercAmount.mul(incomingAsk.price) >= minAskEth) {
addSellOrder(incomingAsk, asset, orderHintArray);
}
s.bids[asset][Constants.HEAD].nextId = Constants.TAIL;
return;
}
} else {
// If the product of remaining ercAmount and price rounds down to 0 just close the bid order
bool dustErcAmount = (highestBid.ercAmount - incomingAsk.ercAmount)
.mul(highestBid.price) == 0;
if (dustErcAmount || incomingAsk.ercAmount == highestBid.ercAmount) {
matchOrder(s.bids, asset, highestBid.id);
updateBidOrdersOnMatch(s.bids, asset, highestBid.id, true);
} else {
s.bids[asset][highestBid.id].ercAmount =
highestBid.ercAmount - incomingAsk.ercAmount;
updateBidOrdersOnMatch(s.bids, asset, highestBid.id, false);
}
incomingAsk.ercAmount = 0;
matchIncomingSell(asset, incomingAsk, matchTotal);
return;
}
} else {
updateBidOrdersOnMatch(s.bids, asset, highestBid.id, false);
incomingAsk.shortRecordId =
matchIncomingSell(asset, incomingAsk, matchTotal);
if (incomingAsk.ercAmount.mul(incomingAsk.price) >= minAskEth) {
addSellOrder(incomingAsk, asset, orderHintArray);
}
return;
}
}
}

In the loop, there's no explicit exit condition defined, so it will loop indefinitely until some condition is met.if incomingAsk.ercAmount (the quantity of the sell order) is very large, and highestBid.ercAmount (the current quantity of the buy order) is very small, it can indeed lead to multiple iterations of the loop, ultimately consuming all available Gas and triggering an "out of gas" error.

Impact

The function may be unavailable.

Tools Used

Vscode

Recommendations

Inside the loop, add a counter and set a reasonable maximum number of iterations. Forcefully exit the loop when the iteration count reaches the limit to prevent infinite looping.

Updates

Lead Judging Commences

0xnevi Lead Judge
almost 2 years ago
0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Invalidated
Reason: Known issues

Support

FAQs

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