
Loading...
Loading
Loading...
LoadingLoading audit report...

WalletGuard.ai, powered by Gestalt Labs
Findings selected for deep verification. Where possible we generated a Solidity proof-of-concept and executed it against a forked mainnet.
contracts/StablecoinDEX.solFunction: emergencyWithdrawLines: 305-318contracts/StablecoinDEX.solFunction: fillOrderLines: 289-298contracts/StablecoinDEX.solFunction: _processWithdrawalLines: 322-328contracts/StablecoinDEX.solFunction: placeOrderLines: 47, 215-241contracts/StablecoinDEX.solFunction: placeOrderLines: 211-239contracts/StablecoinDEX.solFunction: fillOrder, cancel, _cancelOrderLines: 272-298, 250-261, 263-284The StablecoinDEX is a liquidity infrastructure contract for TIP-20 stablecoin swaps on the Tempo blockchain. The analysis identified 6 critical, 15 high, 11 medium, 9 low, and 2 informational findings. The most dangerous pattern is the complete absence of balance validation in emergencyWithdraw() combined with unchecked arithmetic, which enables arbitrary fund theft and complete protocol drain. Additional critical issues include unsafe uint128 downcasts that silently truncate payment amounts, division-before-multiplication precision loss in price calculations, and a missing validation on MAX_PRICE_DEVIATION that the contract defines but never enforces. The contract is unsafe for production use without substantial remediation of the critical findings.
The emergencyWithdraw function allows any user to withdraw far more tokens than they deposited. If you deposit 100 tokens and call emergencyWithdraw requesting 200, due to a coding error (unchecked arithmetic), you get the full 200 tokens out while the contract thinks you now owe them negative tokens. An attacker can drain the entire DEX by calling this function repeatedly with amounts exceeding their balance.
When filling an order, the contract calculates how much quote token the buyer should pay, but then silently truncates this number when storing it if it's too large. An attacker can craft orders with large amounts and prices such that the truncation causes them to pay far less than they should, stealing the difference from order makers.
When calculating order prices using division, the contract truncates fractions that should be paid to makers. For example, selling 3 tokens at 1/3 price per token should yield 1 token, but due to integer rounding it yields 0. Each partial fill of an order loses some value for the maker. Over many fills, this becomes significant systematic theft.
An attacker deposits 1 wei of any token, then calls emergencyWithdraw with an amount exceeding their balance (e.g., 1M tokens). Due to unchecked arithmetic at line 315, balances[attacker][token] underflows to a very large number (2^128 - (1M - 1)). The safeTransfer then sends 1M real tokens to the attacker. The attacker repeats this for different token addresses or uses multiple accounts, progressively draining all liquidity from the DEX. Each call extracts real ERC20 tokens while corrupting the accounting so no legitimate withdrawal can succeed.
An attacker places an order selling 1B tokens at price 1.5e18. When a taker fills this order: (1) the division-before-multiplication causes quoteAmount to lose precision, (2) the uint256->uint128 cast then truncates the already-reduced quoteAmount further if it exceeds 2^128. The order maker receives far less than they should. By repeating with many orders across different prices and amounts, the attacker systematically drains all maker funds.
The contract defines MAX_PRICE_DEVIATION (1% from 1:1 parity) but never validates it. An attacker places orders at extreme prices (e.g., 1e14 or 1e20). Users confused by the orderbook see these 'bargain' prices and fill them, losing massive value. Even users who don't fill the orders have their liquidity locked in these junk orders if the attacker front-runs position management.
An attacker places a self-swap order where baseToken == quoteToken (no validation prevents this). They lock 1M tokens. When filled, tokens move from balance to balance (net zero). When canceled, they receive a refund of the remaining amount. The attacker can abuse this to extract tokens by: (1) placing the self-swap, (2) partially filling it, (3) canceling to get refund, (4) repeating to extract more than deposited.
The contract prices all orders in fixed 18-decimal units without validating or normalizing for actual token decimals. An attacker observes that mUSD (6 decimals) and another 6-decimal stablecoin are available. They place an order selling 1B mUSD at a price of 1e15 (effectively 0.001 quote tokens per mUSD in 18-decimal space). Due to the mismatch between price format (18 decimals) and actual token behavior (6 decimals), the implied exchange rate is drastically wrong, allowing the attacker to extract massive arbitrage value.
An attacker exploits the unchecked arithmetic in emergencyWithdraw (Finding 6) combined with the reentrancy vulnerability in withdraw (Finding 14) to drain all liquidity. Step 1: Attacker calls emergencyWithdraw(token, balance+1), causing unchecked underflow where balances[attacker][token] becomes 2^128-1. Step 2: safeTransfer sends balance+1 tokens to attacker, draining pool. Step 3: Attacker's corrupted balance allows them to call emergencyWithdraw again, repeating the drain. Step 4: Meanwhile, legitimate users calling withdraw during a malicious ERC20 reentrancy trigger double-decrements of totalDeposits (Finding 14), further corrupting accounting. Result: All protocol liquidity is drained, totalDeposits and balances diverge, other users cannot withdraw.
An attacker exploits Finding 1 (division-before-multiplication in quoteAmount) combined with Finding 10 (missing token decimal validation). Step 1: Attacker identifies a baseToken with 6 decimals and quoteToken with 18 decimals. Step 2: Attacker places order: sell 1e6 baseToken at price=1e12 (0.000001 in 18-decimal notation). Step 3: A taker calls fillOrder: quoteAmount = (1e6 * 1e12) / 1e18 = 1e18 / 1e18 = 1 (truncates correctly). But if attacker sets price=1e11 instead: quoteAmount = (1e6 * 1e11) / 1e18 = 1e17 / 1e18 = 0 (truncates to zero). Step 4: Taker receives 1e6 baseToken for 0 quoteToken, draining 1e6 from maker. Step 5: Attacker repeats with multiple orders, systematically draining all maker liquidity via precision loss exploitation. With 1000 orders, attacker extracts 1000 * 1e6 tokens for zero payment.
An attacker exploits Finding 4 (no price validation against MAX_PRICE_DEVIATION) combined with Finding 3 (cross-decimal arithmetic without normalization). Step 1: Attacker places order: baseToken=USDC (6 decimals), quoteToken=mUSD (6 decimals), amount=1e9, price=1e14 (0.0001% of parity). Step 2: Attacker locks 1e9 USDC, offering to sell at 1e-4 price. Step 3: Taker calls fillOrder: quoteAmount = (1e9 * 1e14) / 1e18 = 1e23 / 1e18 = 1e5 mUSD (0.0001 mUSD for 1 USDC). Step 4: If an automated market-making bot fills this order, taker pays only 1e5 mUSD for 1e9 USDC worth ~1e9 mUSD, extracting ~999,900 mUSD of value. Attacker can repeat with multiple extreme prices to drain all quoteToken liquidity. Additionally, if attacker sets price=type(uint128).max (10^38), the calculation overflows and truncates (Finding 2), resulting in zero payment for large fills.
An attacker exploits Finding 5 (reentrancy in fillOrder via cancel) combined with Finding 7 (linked list head corruption) and Finding 12 (incomplete order deletion). Step 1: Attacker places order A (orderId=10) in the list: HEAD -> 10 -> 20 -> 30. Step 2: Attacker places order B (orderId=20). Step 3: Taker calls fillOrder(10, halfAmount), which triggers _removeFilledOrder if order becomes fully filled. Step 4: During _removeFilledOrder execution, order.maker is set to address(0) but order.remaining is NOT deleted (Finding 12). Step 5: Before _removeFilledOrder completes, attacker calls cancel(10) in a reentrant callback, which checks order.maker != address(0). Step 6: If cancellation races with removal, the linked list pointers become inconsistent: orders[20].prev may still point to 10, but orders[10].next was updated. Step 7: Subsequent getOrders calls read the corrupted list and skip valid orders (Finding 7) or enter infinite loops (Finding 30). Step 8: The attacker places a new order C (orderId=35) but due to list corruption, it inserts at the wrong position, potentially being filled before its price is reached.
An attacker exploits Finding 13 (missing baseToken != quoteToken validation) combined with Finding 1 (division-before-multiplication precision loss). Step 1: Attacker places order: baseToken=USDC, quoteToken=USDC, amount=1e6, price=1e18 (1:1 self-swap). Step 2: balances[attacker][USDC] -= 1e6 (locked). Step 3: Taker calls fillOrder(orderId, 5e5): quoteAmount = (5e5 * 1e18) / 1e18 = 5e5 (correct). Step 4: balances[taker][USDC] -= 5e5, balances[taker][USDC] += 5e5 (net zero), balances[attacker][USDC] += 5e5. Step 5: attacker now has 5e5 from fill. Step 6: Attacker calls cancel to refund remaining 5e5. Step 7: attacker receives 5e5 from cancel, total gain = 5e5 + 5e5 = 1e6 net gain from the fill + cancel without real trading. Repeated 100x: attacker steals 100e6 USDC by exploiting self-swap accounting.
An attacker exploits Finding 3 (cross-decimal arithmetic without normalization) combined with Finding 4 (no MAX_PRICE_DEVIATION enforcement). Step 1: Protocol has two 6-decimal stablecoins: USDC and mUSD. Step 2: Attacker places order: baseToken=mUSD (6 decimals), quoteToken=USDC (6 decimals), amount=1e8, price=1e15 (0.001 in 18-decimal representation). Step 3: The price formula is: quoteAmount = (1e8 * 1e15) / 1e18 = 1e23 / 1e18 = 1e5 USDC (0.0001 USDC). Step 4: Taker fills: receives 1e8 mUSD (100 actual tokens at 6 decimals) for 1e5 USDC (0.0001 actual tokens). This is a 1,000,000x arbitrage. Step 5: Attacker drains all mUSD liquidity by placing multiple such orders, extracting orders at massive discounts by exploiting the lack of decimal scaling.
| Agent | Status | Findings | Severity | Confidence | Duration | Coverage |
|---|---|---|---|---|---|---|
| reentrancy | success | 29 | 1H | 85% | 3.7m | Integer overflow and underflow in arithmetic operations (fillOrder quoteAmount calculation, _cancelOrder unchecked arithmetic), Unsafe type casting (uint256 to uint128 without bounds checking), Cross-function reentrancy (fillOrder vs cancel), State management and linked list integrity (order pointers, head corruption), Input validation (price bounds, amount validation, token decimal handling), Balance tracking inconsistencies (totalDeposits vs individual balances), Emergency withdrawal bypass (missing balance checks in _processWithdrawal), Order state transitions and refund logic, Cross-function reentrancy in fillOrder() and _cancelOrder() with state update ordering, Integer overflow and underflow in quoteAmount calculation and balance updates, Unsafe type casting from uint256 to uint128 without bounds validation, Price validation and MAX_PRICE_DEVIATION enforcement, Self-fill vulnerability and order validation, Linked list integrity and order pointer management, Withdrawal path ordering (CEI pattern compliance), totalDeposits accounting and invariant maintenance, Partial order deletion and state consistency, Cross-function reentrancy (deposit, withdraw, fillOrder, placeOrder, cancel), State mutation ordering (Check-Effects-Interactions in order management), Unchecked arithmetic blocks and underflow/overflow risks, Balance tracking and accounting invariants (balances vs. totalDeposits), Linked list manipulation and order book integrity, Decimal handling and cross-asset arithmetic (price calculations), Input validation and error handling on external parameters, Math precision and rounding direction in swap calculations, Token pair validation and order constraints, Locked vs. available balance semantics |
| access control | success | 28 | 1C | 87% | 3.7m | Access control on deposit, withdraw, placeOrder, fillOrder, cancel functions, Linked list integrity and order state management, Balance tracking and token accounting across operations, Integer arithmetic safety (unchecked blocks, underflows, truncation), Cross-token decimal handling in swap calculations, Order cancellation and removal state clearing, Self-swap validation and base/quote token pairing, Price validation and deviation checks, Reentrancy risks in external token transfers (SafeERC20 used), Invariant maintenance (totalDeposits vs sum of balances), View function safety (getOrders linked list traversal), Order placement and validation (placeOrder function), Order filling and swap execution (fillOrder function), Order cancellation and linked list management (cancel, _cancelOrder functions), Deposit and withdrawal flows (deposit, withdraw, emergencyWithdraw functions), Price calculation and decimal handling (quoteAmount calculation), Linked list traversal (getOrders function), Balance tracking and accounting invariants, Cross-function interactions and state consistency, emergencyWithdraw function - unchecked arithmetic and missing authorization, fillOrder price calculation - division truncation leading to zero-cost trades, fillOrder balance accounting - invariant violations and double-spend vectors, placeOrder validation - missing self-swap prevention, Linked list integrity - deletion and traversal consistency, Order struct deletion - incomplete field cleanup, Cross-function state consistency - balance accounting across deposit/withdraw/fill/cancel, DEX invariants - sum-of-balances vs totalDeposits, order remaining vs refunds, Access control on all public functions - particularly those modifying state, Arithmetic safety - unchecked blocks, truncation, rounding direction |
| economic | success | 26 | 85% | 3.2m | Decimal handling and cross-asset arithmetic, Price calculation precision (division before multiplication), Balance management and accounting consistency, Order management and linked list integrity, Unsafe type casting (uint256 to uint128), Reentrancy vulnerabilities in withdraw/deposit, Order validation (price bounds, order size limits), Integer overflow/underflow in unchecked blocks, Self-fill and order manipulation patterns, Authorization and access control patterns in emergencyWithdraw and fillOrder, Unchecked arithmetic and underflow/overflow in withdrawal and fill functions, Decimal handling and cross-token price calculations, Token amount casting from uint256 to uint128 without bounds checking, Division-before-multiplication precision loss patterns, Price validation against MAX_PRICE_DEVIATION constant, Linked list integrity and concurrent modification vulnerabilities, Order state consistency across cancel/fill operations, CEI (Checks-Effects-Interactions) pattern compliance, Self-fill and wash trading vulnerabilities, Minimum order size enforcement across partial fills, Linked list data structure integrity and invariants (prev/next pointers), Arithmetic precision in price calculations and rounding direction (division-before-multiplication patterns), Decimal normalization across heterogeneous token pairs, Unchecked arithmetic blocks and underflow vulnerabilities, Race conditions and atomic check-then-act patterns in order filling, Input validation bounds (price deviation, order size), Balance accounting consistency across deposit/withdraw/transfer, Order state machine (creation, partial fill, removal, cancellation) | |
| logic validation | success | 28 | 86% | 3.2m | Input validation on all public functions (deposit, withdraw, placeOrder, fillOrder, cancel, emergencyWithdraw), Arithmetic safety in fillOrder quote calculation and balance updates, Linked list state machine integrity (placeOrder, _cancelOrder, _removeFilledOrder), Integer truncation risks in uint128 casts, Price validation and MIN_ORDER_SIZE checks, Unchecked arithmetic in _processWithdrawal, Rounding direction in quote amount calculation, Token decimal handling in cross-token swaps, Reentrancy and external call patterns (safeTransferFrom, safeTransfer), View functions and getOrders linked list traversal, Arithmetic safety: overflow, underflow, precision loss in division and casting, Input validation: bounds checks on price, amount, token addresses, State machine integrity: order linked list corruption, order state consistency, Balance accounting: available vs locked balance tracking, withdrawal validation, Cross-decimal price calculation: hardcoded 18-decimal assumption vs actual token decimals, Order management: placement, cancellation, filling, removal from linked list, Unsafe type casts: uint256 to uint128 without bounds validation, Authorization and access control: self-fills, unauthorized withdrawals, Linked list invariants: pointer validation, cycle detection, partial deletion, View functions: getOrders iteration, validation of order pointers, Unchecked arithmetic in _processWithdrawal and emergencyWithdraw functions, Integer precision loss in fillOrder quoteAmount calculation via division-before-multiplication, Price bounds and unsafe type casting in fillOrder, Order state management consistency across placeOrder, cancel, and fillOrder, Linked list integrity in order book structure, Self-swap vulnerability via baseToken == quoteToken, Balance and totalDeposits accounting invariants, Cross-function exploit chains combining math and state machine issues | |
| code quality | success | 28 | 84% | 4.4m | ERC-20 compliance (deposit/withdraw using SafeERC20), Linked list order management (insertion, deletion, traversal), Balance tracking and accounting for user deposits and locked amounts, Order filling with price calculations and token swaps, Integer arithmetic (multiplication, division, downcast operations), Access control (implicit — functions are permissionless or check msg.sender), Reentrancy (SafeERC20 used, no external calls in unsafe patterns), Fee asymmetry and price validation, Cross-function reentrancy in fillOrder with order.remaining state update timing, Unsafe uint256 to uint128 downcasts in fillOrder without bounds checking, Unchecked arithmetic in _processWithdrawal and emergencyWithdraw, Missing price validation against MAX_PRICE_DEVIATION constant, Incomplete order deletion and linked list corruption in _cancelOrder and _removeFilledOrder, Cross-decimal token pair handling and price normalization, Division-before-multiplication precision loss patterns, Authorization gaps in fillOrder (self-fill) and emergencyWithdraw (no balance check), Order state machine consistency and atomicity, Token decimal assumptions in swap calculations, ERC-20 transfer safety and balance accounting, Order state lifecycle (place, cancel, fill, remove), Linked list data structure integrity, Precision and rounding in price-to-quote conversion, Decimal normalization for cross-asset trades, Unchecked arithmetic and underflow/overflow risks, Access control and authorization checks, View function consistency and traversal safety, Storage mutation ordering and atomicity, Type casting and downcast overflow | |
| compiler bugs | success | 8 | 97% | 1.4m | Deposit and withdrawal flows for integer underflow/overflow, Order placement, cancellation, and removal linked list integrity, fillOrder price calculation and rounding direction (division-before-multiplication), Balance tracking consistency across partial fills and order cancellations, ERC20 token transfer safety with SafeERC20, Cross-token decimal handling (6-decimal USDC vs 18-decimal price format), Linked list state consistency (prev/next pointers after order removal), Zero-address validation on token inputs, Order state deletion and potential re-entrancy via external calls, unchecked arithmetic usage in _processWithdrawal, emergencyWithdraw() and _processWithdrawal() unchecked arithmetic flow, fillOrder() quoteAmount calculation and uint256 to uint128 casting, Balance update atomicity across deposit/withdraw/fillOrder, Order state deletion and linked list integrity, Cross-function reentrancy in cancel/fillOrder interaction, Root cause analysis of prior findings for escalation or chaining, Deposit and withdrawal flow for integer underflow/overflow, Order placement and linked-list invariants, Order filling math and rounding direction, Emergency withdrawal unchecked arithmetic, Cross-asset decimal normalization in price calculations, Balance accounting consistency across state mutations, Order cancellation and removal logic, Total deposits tracking and inflation/deflation vectors | |
| assembly safety | success | 28 | 85% | 3.5m | Deposit and withdrawal functions (overflow, underflow, authorization), Order placement and cancellation (state cleanup, struct field deletion, refund logic), Order filling (quote amount calculation, cast safety, precision loss), Balance tracking and accounting invariants, Access control and authorization checks, Linked list management and order book consistency, Emergency withdrawal function and its integration with internal processor, Integer overflow/underflow in arithmetic operations, Unchecked arithmetic blocks and their impact on security, Type casting safety (uint256 to uint128), Input validation (amounts, prices, token addresses), Integer overflow and underflow in fillOrder(), emergencyWithdraw(), and _processWithdrawal(), Unsafe downcasting from uint256 to uint128 without bounds checking, Missing balance validation in emergencyWithdraw() and withdraw(), Token decimal mismatches in price calculations, Price deviation constraints (MAX_PRICE_DEVIATION never enforced), Linked list integrity and order state management, Order cancellation and removal edge cases, Self-fill and wash trading opportunities, Precision loss and rounding errors in fillOrder(), Cross-function invariant violations (totalDeposits, balances), View function iteration and DOS vectors, Deleted order struct use-after-free patterns, Deposit and withdrawal functions for overflow/underflow in unchecked blocks, Order creation (placeOrder) for input validation gaps (baseToken == quoteToken, price bounds), Order cancellation (cancel) for unauthorized access control, Order filling (fillOrder) for precision loss in price calculations (division-before-multiplication), Order removal and cleanup (_removeFilledOrder) for missing refund logic, Balance accounting consistency across deposit, withdraw, fillOrder, and cancel, Linked list management for invariant violations, Price validation against MAX_PRICE_DEVIATION constant, Cross-function state mutations and race conditions, Decimal handling and cross-asset math, Emergency withdrawal path for unchecked arithmetic safety | |
| l2 specific | success | 23 | 88% | 3.5m | Deposit and withdrawal functions for balance tracking accuracy, Order creation and cancellation for linked list integrity, Order filling logic for arithmetic correctness and balance updates, Unchecked arithmetic in _processWithdrawal and its caller emergencyWithdraw, Price calculations and decimal handling, Type casting from uint256 to uint128 for overflow risks, Linked list pointers (prev/next) for deletion and traversal bugs, Validation of function inputs (amount, price, token addresses), emergencyWithdraw() critical vulnerabilities: unchecked arithmetic + missing balance validation + missing authorization, fillOrder() unsafe casts and precision issues: uint256->uint128 truncation without bounds check, withdraw() atomicity and double-decrement issues: totalDeposits tracking inconsistency, Price validation enforcement: MAX_PRICE_DEVIATION constant defined but never used, Order state management: maker field check TOCTOU in fillOrder, self-swap orders, decimal mismatch, Cross-function balance tracking: withdraw vs emergencyWithdraw double-decrement, Linked list integrity under concurrent operations, ERC20 token decimal compatibility in swaps, Deposit and withdrawal logic for underflow/overflow and balance tracking, Order placement and linked list insertion order (LIFO vs FIFO), Order filling with decimal precision and rounding direction, Order cancellation and state cleanup completeness, Price validation and MAX_PRICE_DEVIATION enforcement, Emergency withdrawal access control and unchecked arithmetic, Dust-sized order fills and order book bloat DoS, Cross-function consistency between public withdraw and emergencyWithdraw, Unsafe type casts in balance updates (uint256 to uint128) | |
| math verification | success | 18 | 75% | 4.2m | Deposit/withdraw balance updates and totalDeposits accounting, Order placement, cancellation, and filling logic, Linked list integrity for order management, Price validation against MAX_PRICE_DEVIATION constant, Quote amount calculation in fillOrder() with cross-decimal arithmetic, Balance overflow/underflow on uint128 casts, Emergency withdrawal without balance checks and unchecked arithmetic, Fee-on-transfer token handling (none present, but noted), Access control on order cancellation and filling (maker vs taker validation), Integer overflow/underflow in quoteAmount calculation (fillOrder), Unsafe uint256 to uint128 casting (fillOrder, emergencyWithdraw), Missing balance validation in emergencyWithdraw, Unchecked arithmetic in _processWithdrawal, Division-before-multiplication precision loss (fillOrder), Price validation and MAX_PRICE_DEVIATION enforcement, baseToken == quoteToken validation, Order linked list integrity, Cross-decimal token pair handling, Emergency withdrawal authorization, Deposit and withdrawal functions (balance tracking, state transitions), Order placement and linked-list insertion logic (validation, invariants), Order filling logic (quote amount calculation, precision, decimal handling), Order cancellation and removal (linked-list unlinking, refunds), Cross-decimal arithmetic (base token vs quote token decimals in fillOrder), Rounding direction in division operations (quote amount calculation), Linked-list traversal in getOrders (cycle detection, gas efficiency), Price validation (use of MAX_PRICE_DEVIATION constant), Unchecked arithmetic blocks (_processWithdrawal), State consistency invariants (totalDeposits vs sum of balances), Minimum order size and price floors (dust prevention), Emergency withdrawal function (access control, balance checks) |
This automated audit has inherent limitations. The following areas are not covered.
This report is an automated point-in-time assessment and does not guarantee protection against all possible attacks. It does not cover off-chain components, economic modeling, or business logic correctness unless explicitly noted. Changes to the contract after the audit commit are not reviewed. This is not financial or legal advice. WalletGuard, powered by Gestalt Labs, provides this analysis as-is with no warranty of completeness.
[](https://walletguard.ai/audit/e45a98e1-a2a6-40d8-9fb7-e3ba0f642dbe)
<a href="https://walletguard.ai/audit/e45a98e1-a2a6-40d8-9fb7-e3ba0f642dbe"> <img src="https://walletguard.ai/api/badge/e45a98e1-a2a6-40d8-9fb7-e3ba0f642dbe" alt="WalletGuard Audit Badge" /> </a>