For the complete documentation index, see llms.txt. This page is also available as Markdown.

DeepSeek

Tool: DeepSeek R1 (DeepSeek AI) Type: 3-round AI audit (Systematic → Economic → Triage) Contracts reviewed: EventlyProfiles.sol · EventlyMarkets.sol (LMSR b=200 + CLOB bids/asks) Chain: MegaETH (Chain ID 4326) — Solidity ^0.8.20 Date: March 2026 Status: Complete


Summary

Severity
Count

Critical

0

High

1

Medium

2

Low

2

Info

2

Total

7


Round 1 — Systematic Vulnerability Scan

M-01 — Reentrancy risk in resolution / cancellation functions

Severity: Medium Location: resolveMarket(), cancelMarket(), slashMarket()

function resolveMarket(uint256 _marketId, uint256 _winningOption) external {
    // ...
    _cancelAllOrders(_marketId); // calls _safeTransferFrom to sellers
}

Functions that change market state and then perform external transfers (ERC-1155 safeTransferFrom) are not protected by the nonReentrant modifier. Although state is updated before transfers, an attacker could re-enter through a malicious ERC-1155 receiver and execute operations that rely on the newly set MarketStatus. While most state-dependent functions check for Active or BettingClosed and would revert, the possibility of unexpected interactions remains — for example, a re-entrant call to claimCancelRefund after status is set to Cancelled but before all transfers have completed.

Recommendation: Add nonReentrant to resolveMarket, cancelMarket, and slashMarket.


M-02 — Dead order entries can permanently block new orders

Severity: High (categorized as M-02 but functionally High impact) Location: createSellOrder(), cancelSellOrder(), _cleanOrders()

When a sell order is cancelled, it is only marked active = false. The order ID is not removed from the _orderBook array for its (market, option) pair — the array length remains unchanged. An attacker can fill the array with 200 orders, cancel them all, and leave the array full of dead entries. After that, no new orders can be created for that (market, option) pair because oids.length < MAX_ORDERS_PER_BOOK will always fail. This permanently disables P2P trading for that option.

Recommendation:

  • Call _cleanOrders(marketId, optionIndex) inside cancelSellOrder after marking the order inactive.

  • Alternatively, allow creating a new order even if the array is at cap, provided enough dead entries exist (compact on-the-fly).


M-03 — Precision loss in LMSR pricing may cause insolvency in edge cases

Severity: Medium Location: getPrice(), _lmsrCost(), quoteBuy(), redeemWinnings()

The contract uses integer division inside wrap before exponentiation, truncating small values to zero. The binary search in quoteBuy has a tolerance of 1e15 shares. Over many trades, accumulated rounding errors could cause the actual pool balance (poolBalance + subsidyDeposited) to be slightly less than the theoretical cost function value. In the worst case, redeemWinnings might revert because the remaining subsidy is insufficient for the last claimant, locking their winning shares permanently.

Recommendation:

  • Add a small safety margin when checking fromSubsidy <= m.subsidyDeposited and emit a warning if the margin is approaching exhaustion.

  • Alternatively, use a slight over-allocation at creation (subsidy += epsilon).


L-01 — Inefficient order insertion gas pattern

Severity: Low Location: createSellOrder()

The insertion logic first pushes a dummy zero, then shifts elements rightward. With MAX_ORDERS_PER_BOOK = 200, the worst-case overhead is 200 SSTORE operations per insertion. Acceptable at current scale but suboptimal.

Recommendation: Consider using a sorted linked list or a packed structure that avoids element shifting.


I-01 — MAX_ORDERS_PER_BOOK cap not enforced against dead entries

Severity: Info Location: createSellOrder()

The cap MAX_ORDERS_PER_BOOK = 200 is enforced at creation time against the raw array length, not the count of active orders. Dead (cancelled) entries count toward the cap. This is a documentation / design gap that compounds with M-02.

Recommendation: Document that the cap applies to total order slots (active + dead) for a given pair, and that operators should be aware of the cleanup requirement.


Round 2 — Economic Attack Analysis

E-01 — Griefing attack via order book spam (dead entries)

Severity: High (see M-02 above)

An attacker fills the order book with the maximum number of orders, then cancels them all. This costs only gas (shares are returned on cancellation), with no USDm at risk. It effectively disables P2P trading for that market-option pair, forcing all trades through the AMM at potentially worse prices. On MegaETH with low gas costs, the attack is cheaper than on L1.

Recommendation: Same as M-02 — clean dead entries on cancellation.


E-02 — Creator can claim fees on cancelled markets

Severity: Low Location: claimCreatorFees(), cancelMarket()

If a market is cancelled after the betting deadline but before resolution, the creator can still call claimCreatorFees (if status is Finalized). A creator could intentionally generate volume via wash trading, then cancel the market to extract fees, with the fee cost borne by traders. The creator's collateral is returned on honest cancellation, reducing their net risk.

Recommendation: Consider forfeiting accrued creator fees to treasury on market cancellation, or require a minimum active duration before fees can be claimed.


E-03 — LMSR rounding edge case (see M-03)

The same precision concern from M-03 applies in the economic context: rounding errors accumulating across many binary-search iterations could cause a final redeemer to encounter an underflow in subsidyDeposited. See M-03 for the full analysis.


Round 3 — Adversarial Triage

A-01 — Can funds be stolen?

Finding: No direct theft vector found. All USDm transfers use safeTransfer and follow the CEI pattern under nonReentrant. Market pool balances are only decreased by redeemWinnings, claimCancelRefund, and sellToAMM. The only risk is the rounding-induced insolvency (M-03), which would block a redemption but not steal funds — they would remain locked in the contract.

A-02 — Can the contract be permanently bricked?

Finding: Partial bricking possible. The CLOB order book dead-entry flooding vector (M-02) has been resolved -- _cleanBook() is now called immediately on cancelOrder (F-DS-M02). No function permanently disables the entire contract. The admin can still withdraw treasury, create markets, and resolve disputes.

A-03 — Edge case: single bettor takes all

If a single user buys all shares of the winning option, they receive the entire pool on redemption. Correct and expected: redeemWinnings uses poolBalance + subsidyDeposited, which is guaranteed ≥ total winning shares by LMSR theory.

A-04 — Edge case: all bettors on losing side

If no shares of the winning option were purchased, the winning supply is zero — no one can redeem. The poolBalance and subsidyDeposited remain locked in the contract indefinitely. No sweep mechanism exists. This is a known design limitation of prediction markets, not a security flaw, but worth documenting.


Findings Table

ID
Severity
Contract
Title
Status

M-01

Medium

EventlyMarkets

Reentrancy risk in resolution / cancellation functions

Resolved — nonReentrant added to resolveMarket, cancelMarket, slashMarket (L-01)

M-02

High

EventlyMarkets

Dead order entries can permanently block new orders

Resolved — _cleanBook called in cancelOrder (F-DS-M02)

M-03

Medium

EventlyMarkets

Precision loss in LMSR pricing — edge case insolvency

Acknowledged — negligible (~1 wei/trade); LMSR solvency proven by cost function bound

L-01

Low

EventlyMarkets

Inefficient order insertion gas pattern

Acknowledged

E-02

Low

EventlyMarkets

Creator can claim fees on cancelled markets

Acknowledged — fees claimable only after Finalized; cancelled markets never reach Finalized

I-01

Info

EventlyMarkets

Order book cap not enforced against dead entries

Resolved — _cleanBook removes dead entries immediately on cancelOrder (F-DS-M02)

A-04

Info

EventlyMarkets

Unclaimed pool balance if winning option has zero supply

Acknowledged


Production Blockers

No production blockers remain. All identified issues have been resolved (L-01, F-DS-M02) or acknowledged as acceptable design tradeoffs.


Severity Scale

Severity
Description

Critical

Direct loss of user funds, permanent contract bricking, or complete bypass of access controls

High

Significant risk of funds being locked, DoS of core functionality, or major economic manipulation

Medium

Non-critical issues that could lead to unexpected behavior, minor DoS, or precision loss

Low

Code quality, gas optimization, or edge-case issues with minimal impact

Info

Recommendations, documentation gaps, or non-security design considerations

This audit was performed on the provided source code. No runtime testing or formal verification was performed.

Last updated

Was this helpful?