Polymarket security record

As of 30 Oct 2026, Polymarket has 7 audit reports by 2 security firms on Trustblock; the latest is by Quantstamp, dated 5 Oct 2026. Those reports list no open critical findings.

Critical findings
No open critical findings
Across the 7 reports, 2 critical findings were reported. Each is marked fixed by the firm that reported it. See the findings
Open the latest report
Your label, live
Get the label for your site

Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Trustblock does not review code.

Audits7

Newest first

AuditSecurity firmDateScopeCritical findingsReport
Polymarket v2Security firm: QuantstampDate: Scope: Not recorded (see the report)No open critical findingsOpen · certificate.quantstamp.com
Polymarket CTF Exchange v2Security firm: QuantstampDate: Scope: Not recorded (see the report)No open critical findingsOpen · certificate.quantstamp.com
UMA Sports OracleSecurity firm: ChainSecurityDate: Scope: Files named in the reportNo open critical findingsOpen · chainsecurity.com
NegRiskAdapterSecurity firm: ChainSecurityDate: Scope: Files named in the reportNo open critical findingsOpen · chainsecurity.com
Conditional TokensSecurity firm: ChainSecurityDate: Scope: Not recorded (see the report)No open critical findingsOpen · chainsecurity.com
Proxy Wallet FactoriesSecurity firm: ChainSecurityDate: Scope: Not recorded (see the report)No open critical findingsOpen · chainsecurity.com
ExchangeSecurity firm: ChainSecurityDate: Scope: Not recorded (see the report)2 critical findings, fixedOpen · chainsecurity.com

Findings43

Statuses are the security firm’s own, from each report.

SeverityOpenAcknowledgedFixed
Critical002
High002
Medium008
Low1822
CriticalORDER_TYPEHASH Is IncorrectExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
CriticalSignatures Are Valid for Any AddressExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
HighNon-Canonical Migration Condition IDs Allow pUSD DrainagePolymarket v2 · Quantstamp · 5 Oct 2026Fixed

Migrated NegRisk events can be resolved through `resolveMigrationCondition()`, which is permissionless once the corresponding legacy condition has resolved. The function takes a structured V2 `conditionId`, reads the legacy result by deriving the event and condition index from that id, and then stores the V2 result under the raw `conditionId` key. This creates an identity mismatch. A non-canonical alias such as `canonicalCondition | 1` is a different raw key for V2 result storage, but it still decodes to the same event and condition index for legacy lookup. If the legacy market resolves normally with one YES winner, an attacker can call `resolveMigrationCondition(aliasId)` for that YES condition. This increases `resultsSum[eventId]` to `1_000_000`, while the canonical winning condition remains unresolved in V2. Once `resultsSum[eventId] == 1_000_000`, anyone can call `resolveConditionToNo()` on the canonical conditions. This lets the canonical V2 event appear all-NO even though the legacy event resolved normally with one YES winner. Because already resolved positions can still be split, a user can repeat the flow by splitting canonical conditions, redeeming all NO positions for principal, and horizontally merging the YES basket for extra pUSD each time. The PoC below demonstrates one iteration ending with `totalInput + amount` after supplying only `totalInput`, but the damage is not capped to that single iteration and can continue draining pUSD until the system is paused. The impact can extend across chains. Once the non-canonical alias has a stored result on the source chain, `bridgeResult()` can bridge that raw alias result to supported spoke chains because it only checks `module.getResult(_conditionId).length == 2`. On the receiving chain, `_handleResultReceive()` forwards the raw `conditionId` to `reportResult()` from the bridge role. Since bridged results skip the NegRisk sum invariant inside `reportResult()`, the alias can seed the same corrupted event state on the spoke chain. An attacker can then repeat the same `resolveConditionToNo()`, split, redeem, and horizontal merge flow on each affected chain, draining pUSD from multiple deployments before detection and pause. The source of the regression is that the older implementation resolved through `BaseModule._resolveCondition()`, which called `_requireConditionPrepared()`. Since NegRisk event preparation set an oracle only for each canonical condition index, non-canonical aliases had `oracle[alias] == address(0)` and reverted. The newer migration path no longer enforces this canonical preparation check before storing the result, while legacy lookup still maps the alias back to the same legacy condition.

Open the report
HighFee Rate Not HashedExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
MediumLegacy All NO Migration Can Mint Unbacked pUSDPolymarket v2 · Quantstamp · 5 Oct 2026Fixed

Legacy NegRisk event resolution can finalize with zero winners, meaning every condition resolves to NO. This differs from the one-winner economic assumption used by the new NegRisk design. If users are able to split positions after such an all NO legacy resolution, they can hold both legacy YES and legacy NO for each condition, redeem the winning NO side for full collateral recovery, and still migrate the corresponding YES side into the new module. The new module YES basket can then be horizontally merged for additional collateral value. This means a user can recover collateral through the legacy NO redemption path and extract additional value through the new module YES merge path from the same post resolution split set. Even when strict one-winner checks prevent full migration resolution finalization, the collateral extraction path remains reachable before that finalization boundary. This creates a state where migration safety depends on a legacy oracle invariant that may not hold in practice, rather than being fully enforced by migration time economic constraints. The same attack is also feasible if an already resolved legacy NegRisk event with all NO outcomes is migrated, because migration enrollment does not change the underlying all NO payoff asymmetry between legacy redemption and migrated horizontal merge paths.

Open the report
MediumTakers Can Be Overcharged or Underfilled in COMPLEMENTARY pathPolymarket CTF Exchange v2 · Quantstamp · 25 Mar 2026Fixed

In the COMPLEMENTARY settlement path, the parameter `takerFillAmount` only updates the taker's `orderStatus.remaining` capacity but does not constrain the actual tokens flowing to or from the taker. The taker's real inflows and outflows are determined entirely by the makers' orders and the `makerFillAmounts[i]` supplied by the operator. Because `takerFillAmount` and `makerFillAmounts[i]` are independent inputs and are not in the same asset units in the COMPLEMENTARY path, there is no on-chain check that aggregate maker-side execution is economically consistent with the declared taker fill; as a result, settlements can violate the taker’s signed constraints without being rejected. The missing enforcement opens two categories of incorrect settlement: 1. **Overspend.** If the off-chain logic submits a small `takerFillAmount` (consuming little order capacity) with disproportionately large `makerFillAmounts`, the taker's actual collateral (or CTF) outflow, computed from the makers' order ratios, can exceed the taker's signed `makerAmount`. Because each such match consumes only a sliver of order capacity, the same taker order can be matched repeatedly with new makers, each time pulling more funds than authorized, until the taker's balance or approval is exhausted. 2. **Underpay.** If the off-chain logic submits a large `takerFillAmount` (consuming most or all order capacity) with insufficient maker fills, the taker's order is fully consumed while the taker receives a fraction of the tokens their ratio guarantees. The `taking` value (the minimum output the taker's ratio entitles them to) is computed by `_performOrderChecks()` but is never checked or enforced in the COMPLEMENTARY branch. Compare this to the non-COMPLEMENTARY path, where `_updateTakingWithSurplus(taking, takerAssetId)` at line 112 enforces the minimum. It is important to note that the MINT/MERGE path is not affected: there the taker transfers `takerFillAmount` to the exchange upfront, and `_updateTakingWithSurplus()` enforces the minimum output. In those paths, even a faulty off-chain submission cannot produce an out-of-bounds settlement.

Open the report
MediumUMA Oracle Allows Creating Undisputable GamesUMA Sports Oracle · ChainSecurity · 5 Jun 2025Fixed

The report gives no description here.

Open the report
MediumProxy Wallet DoSProxy Wallet Factories · ChainSecurity · 11 Apr 2024Fixed

The report gives no description here.

Open the report
MediumRelayHub Account Can Be DrainedProxy Wallet Factories · ChainSecurity · 11 Apr 2024Fixed

The report gives no description here.

Open the report
MediumFee Approval RequiredExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
Show 10 more
MediumUnintended Order Types PossibleExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
MediumZero Address EOA Signer Considered ValidExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
LowBatch Buy Surplus Can Over-Consume Remaining Taker Order AmountPolymarket v2 · Quantstamp · 5 Oct 2026Fixed

Batch order matching updates the taker order status in the `_matchBatchOrders()` function with the gross `takerFillAmount` before settlement completes. For batch buy matches that produce a collateral refund, `_executeBatchBuyMatch()` later computes the net `takerMakingAmount` as `takerFillAmount - collateralRefund`, but the stored order remaining value has already been reduced by the gross amount. The remaining calculation consumes a taker order faster than the net settled amount, potentially marking an order as filled even though only part of the signed economic size was executed.

Open the report
LowBatch Taker Events Can Overstate Builder Reward VolumePolymarket v2 · Quantstamp · 5 Oct 2026Fixed

Batch matches can settle a taker order for less than the gross amount reported in the taker `OrderFilled` and `OrdersMatched` events. In the batch path, `_matchOrders()` sets `takerMakingAmount` to `takerFillAmount` before settlement completes, then `_emitTakerEvents()` emits that gross amount even if the batch settlement refunds collateral or positions back to the taker. If builder rewards or volume accounting are calculated from taker `OrderFilled` events keyed by `builder`, downstream systems can attribute rewards using the gross requested fill instead of the net settled amount.

Open the report
LowBridged Markets Not Handled Correctly By External Helper FunctionsPolymarket v2 · Quantstamp · 5 Oct 2026Fixed

Structured position IDs derive their `conditionId` / `eventId` from a `baseHash`, and the `baseHash` includes the condition's original chain ID. The functions `NegRiskModule.getEventId()` and `BinaryModule.getConditionId()` hard-code the `_chainId` component of the computed `conditionId` to be `block.chainid`. For conditions that originate on one chain and are later bridged to another, the bridged positions retain their original source-chain IDs. Calling these helpers on the destination chain with the same market data will instead compute a different destination-chain ID. As a result, external integrations, indexers, frontends, or other offchain systems relying on these helpers may display incorrect market IDs, query incorrect balances/results, or build transactions against the wrong condition/event.

Open the report
LowMissing Storage Gaps in Upgradeable Inheritance ChainPolymarket v2 · Quantstamp · 5 Oct 2026Fixed

Several upgradeable contracts inherit from stateful base contracts/mixins that do not reserve storage space for future upgrades. This is especially relevant for `NegRiskModule`, whose storage layout places inherited state before state declared directly in `NegRiskModule`. If a future upgrade adds a state variable to `NegRiskMigrationMixin`, it will be inserted before the `NegRiskModule` contract’s own storage variables. This would shift the existing storage layout and corrupt the expected values stored in these slots. The same upgrade-safety concern applies to other stateful inherited contracts used by upgradeable implementations, including: - `BaseModule.sol` - `BaseMigrationMixin.sol` - `BinaryMigrationMixin.sol` - `OracleModule.sol` - `oracle/mixins/Pausable.sol` These contracts would also benefit from reserved storage space or an alternative storage isolation pattern.

Open the report
LowMutable NegRisk Migration Enrollment Can Create Insolvency RiskPolymarket v2 · Quantstamp · 5 Oct 2026Fixed

`prepareMigrationEvent()` binds migration state to a structured `eventId` that includes the current legacy condition count (encoded as arity), then snapshots legacy condition index mappings for that count. The same legacy event can later be enrolled again under a different structured `eventId` if the legacy condition count changes, because uniqueness is enforced per structured `eventId` key rather than per legacy event ID. This creates a moving migration target for a single legacy market: previously-migrated positions continue to exist under the earlier condition-count namespace, while future migrations of the same legacy conditions can be redirected into a new condition-count namespace. If the legacy layout changes after initial enrollment (for example, additional conditions are appended), migrated accounting and resolution assumptions can diverge from the original snapshot, and the protocol can end up operating multiple incompatible migrated views of one legacy event. Under specific outcome patterns, this divergence can produce migrated states that conflict with one-winner assumptions used for pUSD safety and can reintroduce the same collateral extraction risk class as all NO style migration outcomes.

Open the report
LowBuilder Volume Can Be MisattributedPolymarket CTF Exchange v2 · Quantstamp · 25 Mar 2026Fixed

The values emitted from the `OrderFilled` event are important for attributing correct builder rewards and fee sharing. The `builder` value itself is signed as part of the order payload and included in the EIP-712 struct hash. To ensure that these rewards are calculated correctly, it is important that the emitted volume value from the `OrderFilled` event match exactly the amounts executed in the trade. In v2, emitted taker volume is not always derived from per-match transfer deltas. In COMPLEMENTARY settlement, `_settleComplementaryTaker()` finalizes taker `OrderFilled` values from `takerFillAmount`. But actual transfers are executed in `_settleComplementaryMaker()` from `makerFillAmounts`. In non-COMPLEMENTARY settlement, `_updateTakingWithSurplus` can replace expected taking with full exchange balance for that asset before taker event emission. The following scenarios can result in incorrect `OrderFilled` volume: 1. Empty maker list in COMPLEMENTARY branch can still emit non-zero taker fill. 2. COMPLEMENTARY taker BUY can emit collateral-side input as `takerAmountFilled` instead of conditional tokens received. 3. COMPLEMENTARY `takerFillAmount = 0` with non-zero maker fills can emit zero taker fill while real transfers occur. 4. Non-COMPLEMENTARY taker fill can include pooled or pre-existing surplus, not only current-match execution. If rewards are paid from raw emitted volume, this can over-credit, under-credit, or misattribute builder performance.

Open the report
LowOperator Can Route Value Across Non-COMPLEMENTARY Matches and Drain Stranded InventoryPolymarket CTF Exchange v2 · Quantstamp · 25 Mar 2026Fixed

In non-COMPLEMENTARY matching (MINT/MERGE), settlement is pooled at the exchange level instead of isolated per match. In `_matchOrders()`, taker and maker assets move into shared exchange balances. `_settleMakerOrders()` then executes maker matches in batch, and `_settleTakerOrder()` pays the taker from the exchange inventory. During the non-COMPLEMENTARY MINT/MERGE matching flow, `_mint()`/`_merge()` use the `conditionId` supplied to `matchOrders()`, while payout assets are derived from order `tokenId`s. Non-COMPLEMENTARY matches can produce surplus from price crossing: in MINT, surplus exists when taker price + maker price > 1; in MERGE, surplus exists when taker price + maker price < 1. In v1, non-COMPLEMENTARY matches required the maker `tokenId` to be the complement of the taker `tokenId` via `validateComplement()`. In v2, the complement check was removed, allowing mixed maker composition on-chain. The matching logic assumes that the maker `tokenId`s in a match correspond to outcomes of the provided `conditionId`, so that any non-COMPLEMENTARY surplus remains confined to the intended match. Because this relationship is not enforced on-chain in v2, mismatches can occur even without malicious intent (e.g., batching mistakes or matcher bugs). When mixed batches do not align with the intended match composition, settlement can leave stranded inventory in the shared exchange balances. This breaks per-order value isolation because later matches can consume value created by earlier unrelated matches, even when each order still appears valid against its signed limit when checked individually. `_updateTakingWithSurplus()` can increase the taker payout from the match minimum to the exchange’s full balance of that taker asset. `_settleTakerOrder()` only refunds leftover taker `makerAssetId`. These functions together allow surplus and pooled balances to be routed across unrelated matches and across batches. Variants: 1. Accidental / matcher-bug variant (more likely): Same-market mixed maker composition (e.g., batching or matching errors) can strand side-token balances even when `conditionId` is correct. 2. Trust-failure variant (less likely): cross-market mixed batches with operator-provided `conditionId` can mint or merge one market while paying another, also creating stranded inventory. Operator extraction method: 1. After stranded inventory exists, submit a small follow-up order where taker asset equals the stranded asset. 2. `_updateTakingWithSurplus()` uses full exchange balance (not per-match delta), so payout can include that pre-existing stranded inventory. 3. `_settleTakerOrder()` only refunds taker `makerAssetId`, so other residual balances remain available for later extraction.

Open the report
LowOrders Where `cashValue == 0` Can Be Charged Uncapped FeesPolymarket CTF Exchange v2 · Quantstamp · 25 Mar 2026Fixed

The fee amounts charged on orders in the `CTFExchange` contract are dynamic and may be unknown to the user at the time the order is created. To limit the trust required of the operator, the total fee to be paid by an order is validated to be within some accepted `maxFeeRateBps` in the `validateFeeWithMaxFeeRate()` function. However, in scenarios where the `cashValue` of an order is zero, the `validateFeeWithMaxFeeRate()` function returns before these checks are made, meaning such orders can be charged an uncapped fee. As the `cashValue` is based on values supplied by the operator (`takerFillAmount` or `makerFillAmounts`) and not a value included in the order struct signed by the user, the user cannot reasonably prevent this.

Open the report
Show 10 more
LowOverflow in `calculateTakingAmount()` Can Produce Incorrect Settlement AmountsPolymarket CTF Exchange v2 · Quantstamp · 25 Mar 2026Fixed

The `CalculatorHelper.calculateTakingAmount()` function uses inline assembly to compute `(makingAmount * takerAmount) / makerAmount`. The intermediate product `makingAmount * takerAmount` can silently overflow and wrap to a smaller value when it exceeds `type(uint256).max`. Off-chain validation may restrict the range of order parameters such that overflow does not occur in practice, but there is no on-chain logic that enforces or validates this. The affected `takingAmount` is returned by `_performOrderChecks()` and is used inside both COMPLEMENTARY and non-COMPLEMENTARY paths to determine transfer amounts. The overflow requires `order.makerAmount * order.takerAmount >= 2^256`. Since both amounts are price parameters in the signed order (not a transferred balance), it is not constrained by real token supply. No scenario was identified in which this overflow could be used to systematically extract value from the opposing side of a match, if strict off-chain overflow checks are always applied to order and fill amounts.

Open the report
LowCancel Does Not Refund Game CreatorUMA Sports Oracle · ChainSecurity · 5 Jun 2025Fixed

The report gives no description here.

Open the report
LowMissing Arguments in EventsUMA Sports Oracle · ChainSecurity · 5 Jun 2025Fixed

The report gives no description here.

Open the report
Lowready() Can Return True When Request Can Not Be SettledUMA Sports Oracle · ChainSecurity · 5 Jun 2025Fixed

The report gives no description here.

Open the report
LowCode ReplicationExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
LowDomain Separator CachedExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
LowFeeCharged Event Not Emitted in fillOrderExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
LowFloating PragmaExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
LowisCrossing Incorrect When takerAmount Is 0Exchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
LowNon-optimized Libraries UsedExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
Show 4 more
LowOrder Status Possibly IncorrectExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
LowOrderStruct.taker Specification InconsistentExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
LowStruct Order Has Redundant FieldsExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report
LowWrong Notice on Order.feeRateBpsExchange · ChainSecurity · 3 Nov 2022Fixed

The report gives no description here.

Open the report

Contracts

No deployed address is recorded for these audits.

The reports name a repository or files. Open a report to see its scope.

The repositories and files the reports name

Show this on your site

Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.

Content

Facts

Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.

Fixed as
Language

Style

Layout
Theme
Accent
Corners

Format

Size
Date
Embed as
HTML
<head>
<script src="https://widget.trustblock.run/project-widget.min.js" defer></script>
</head>
<body>
<div
data-tb-project-widget
data-tb-project-slug="polymarket"
></div>
</body>

Paste the code where the label should appear. It uses no API credit and updates by itself.

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us