OrderBook

First Flight #43
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Missing Decimal Normalization for ERC20 Tokens

Root + Impact:

The protocol does not handle or normalize token decimals for ERC20 tokens in the createSellOrder() function. It assumes that all tokens use 18 decimals, which leads to incorrect transfer values when dealing with tokens like WBTC (8 decimals) or USDC (6 decimals).

This can result in failed transfers, unintentionally small or large orders, and ultimately prevents users from interacting with the protocol using common tokens.

Description

  • The function accepts an _amountToSell parameter and directly calls:

    IERC20(_tokenToSell).safeTransferFrom(msg.sender, address(this), _amountToSell);
  • This assumes that the user-provided _amountToSell is already in the raw format of the token’s actual decimals. However, tokens like:

    • WBTC (8 decimals)

    • USDC (6 decimals)

    • WETH (18 decimals)

    ...have different levels of precision. If a user mistakenly provides 1e18 for a token like WBTC (which only supports 1e8 units for 1 WBTC), the transfer will revert due to insufficient balance even if the user owns 1 WBTC.

Risk

Likelihood: High

  • Any user interacting with this function using non-18-decimal tokens (e.g., WBTC, USDC) is very likely to encounter a revert or logic error.

Impact: Medium

  • Users will be unable to create orders using tokens with decimals other than 18, effectively locking them out of the protocol.

  • This could lead to a denial of service for legitimate users and protocols using common assets like WBTC

  • Can lead to logic bugs if some tokens are overcharged/undervalued silently in future price calculations or fills.

Proof of Concept

function testDecimalMismatchShouldFailOrOverchargeforWBTC() public {
uint256 AliceBalanceIn_18Decimals = 1e18;
console2.log("Amount of WBTC to sell:", AliceBalanceIn_18Decimals);
vm.startPrank(alice);
wbtc.approve(address(book), AliceBalanceIn_18Decimals);
vm.expectRevert();
book.createSellOrder(address(wbtc), AliceBalanceIn_18Decimals, 1e6, 2 days);
vm.stopPrank();
assert((wbtc.balanceOf(address(this)) != wbtc.balanceOf(alice)));
}

Recommended Mitigation

When processing token transfers (e.g., during order creation), scale the user-provided amount, assumed to be in 18-decimal format down to the token’s native decimal format using this mapping. This ensures accurate token transfers and prevents issues when interacting with non-standard tokens like WBTC (8 decimals)

Ensure the frontend always passes _amountToSell and _priceInUSDC in 18-decimal fixed-point format Introduce a tokenDecimals mapping that stores the decimal precision of each supported token.

function createSellOrder(
address _tokenToSell, // wbtc address
uint256 _amountToSell, // 2e8 (wrapped into 2e18)
uint256 _priceInUSDC, // 1 USDC 1e18
uint256 _deadlineDuration // 2 Days
) public returns (uint256) {
if (!allowedSellToken[_tokenToSell]) revert InvalidToken();
if (_amountToSell == 0) revert InvalidAmount();
if (_priceInUSDC == 0) revert InvalidPrice();
if (_deadlineDuration == 0 || _deadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
uint256 deadlineTimestamp = block.timestamp + _deadlineDuration;
uint256 orderId = _nextOrderId++; // @audit-check if _nextOrderId is actually incrementing
+ uint8 decimals = tokenDecimals[_tokenToSell];
+ if (decimals == 0) revert("Token decimals not set");
+ uint256 scaledAmount = _amountToSell / (10 ** (18 - decimals)); // 1e18 / (10 ** 18 - 8) => 1e8
- IERC20(_tokenToSell).safeTransferFrom(msg.sender, address(this), _amountToSell);
+ IERC20(_tokenToSell).safeTransferFrom(msg.sender, address(this), scaledAmount);
// Store the order
orders[orderId] = Order({ // q- why are we mapping 2 -> first order ??
id: orderId,
seller: msg.sender,
tokenToSell: _tokenToSell,
+ amountToSell: scaledAmount,
- amountToSell: _amountToSell,
priceInUSDC: _priceInUSDC,
deadlineTimestamp: deadlineTimestamp,
isActive: true
});
Updates

Lead Judging Commences

yeahchibyke Lead Judge
about 1 month ago
yeahchibyke Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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