Urban Fiery Guppy
High
createOrder uses generateOrderId, which returns the same value within one block for the same recipient:
///@notice generate a random and unique order id
function generateOrderId(address sender) external view override returns (uint96) {
uint256 hashedValue = uint256(
keccak256(abi.encodePacked(sender, block.timestamp))
);
return uint96(hashedValue);
}
As there is no access control for order creation, and funds are transferred from the recipient (not msg.sender), it is possible to drain all approvals by creating orders with minAmountOut = 0
.
Missing msg.sender == order.recipient
check in createOrder
; usage of only recipient
and block.timestamp
for orderId derivation.
No response
Any user who approves any whitelisted token to the OracleLess/Bracket/StopLimit contract.
-
User approves 10_000 USDC to OracleLess contract, creates an order for 10_000 USDC
-
Attacker's contract iterates through pendingOrderIds, starting from the last element, records all orders that were created during the current block.
-
In the same transaction, for each such order, Attacker's contract calls
createOrder
, with the same data butminAmountOut = 0
.generateOrderId
generates the same id, because it depends only on theblock.timestamp
andrecipient
, so the attacker'screateOrder
overwrites the order created by the victim. -
In the same transaction, attacker's contract calls
fillOrder
, and swaps all victim's funds for 0.
Alternatively, attacker can monitor approvals of whitelisted tokens to the contract, and createOrder
with minAmountOut = 0
, and amount = approvedAmount
.
All users who approved a whitelisted token to contracts OracleLess/Bracket/StopLimit, will lose the whole approved amount.
Use nonce for deriving orderId, and validate msg.sender == recipient.