Security score
A100 / 100

Scored by the DreamPad team against the rubric on the right, from the five review rounds below. Every finding, its fix and its regression test are listed on this page.

Findings
56 / 56 fixed
Tests
315
Rounds
5
Updated
2026-09-16

How the score is computed

Fixed rubric, updated only when a milestone lands. Each row says what earns the missing points.

PartPointsBasis
Every finding closed45 / 4556 of 56 findings fixed across 5 rounds, the exploit proofs kept as regression tests
Adversarial test suite20 / 20315 Foundry tests against real Uniswap V2, V3 and V4 bytecode, including every attack proof from the audit rounds
Static analysis15 / 15Slither reports nothing above informational on the current contracts
Keys and operations20 / 20the mainnet factories and oracles on Ethereum and Robinhood Chain are owned by a 2-of-2 Safe, which is also the treasury; a deploy script that refuses a live chain without a Safe as owner and treasury, a clean secret sweep of the whole git history, a nonce Content Security Policy and a browser key that locks itself
Total100 / 100A from 90, B+ from 80, B from 70, C from 55.

Scope: every contract in src/, the deployment scripts, and the web app in web/src. Method: a full manual read of the contracts, two independent adversarial passes over the contracts and one over the web app, every finding re-verified against the code, and a regression test for each contract fix (Foundry suite: 315 tests after round 5, the pair-asset guard and instant pools, real Uniswap V2, V3 and V4 bytecode). This is an internal review by the team that wrote the code. It is not a substitute for an independent audit before mainnet.

Severity: Critical = funds of other users can be taken. High = funds can be taken under a plausible precondition, or a coin can be bricked cheaply. Medium = value leaks, griefing or privilege abuse with bounded damage. Low / Info = hardening.

1 Critical7 High21 Medium23 Low4 Infoall fixed
Phase 1

internal audit

24 findings · all fixed
#SeverityWhereFindingFixStatus
1CriticalDreamToken._update, BondingCurve.buyA curve buy could deliver tokens to any address, including a pre-created Uniswap pair. An attacker could pre-mint LP in the graduation pair (tokens from the curve plus their own WETH), own ~90% of the pool after graduation, and withdraw most of the raise.Pre-graduation the curve may not deliver into a pool of the coin (token0()/token1() check), the adapter, the vault or the token. Only the curve pulls tokens back. The curve announces graduation (startGraduation) so the adapter hand-off stays allowed. Test: test_setAmmPairOnlyAcceptsRealPools, graduation suites.Fixed
2HighUniswapV3Adapter._ensurePoolAnyone can create and initialise the V3 pool before graduation at any price without holding a token. The adapter seeded at that price, handing the coin's liquidity to whoever set it.The adapter pulls the pool to the curve's closing price first: on an empty pool the swap moves the price for free; against pre-placed liquidity it trades at most half of one side (buying cheap or selling dear) and seeds with what is left. Leftovers are computed from balances. Tolerance 0.1% on sqrt price. Tests: test_preInitialisedPoolCannotBlockGraduation, test_mispricedPoolWithLiquidityIsPulledTowardTheCurvePrice.Fixed
3HighBondingCurve.sellFor, DreamPadFactory.setZapsellFor trusted factory.zap() live. An owner (or a stolen owner key) could point it at a contract that sells every approved holder's curve tokens.Each curve snapshots the zap at launch; sellFor only honours that address. Tokens get a permissionless syncZap() to re-apply exclusions for a new zap.Fixed
4HighDreamToken.setTaxSplitThe holder / creator split was changeable after launch, contradicting the launch page, so a creator could move 90% of every fee to themselves.Removed. There is no setter for taxes or the split. Test: test_splitIsFixedForever.Fixed
5HighDreamZap refund leg, venue choiceThe pair-asset refund of a sell-out buy was sold back with no minimum, and venues were chosen by raw in-range liquidity, so a dust or one-tick pool could capture the route and the refund. An empty V2 pair also broke every ETH trade for that pair asset.Venues are chosen by quoted output for the actual amount; empty V2 pairs are not venues; the refund leg must return within 5% of the rate the same transaction bought at, else the buy reverts. Previews use the same selection.Fixed
6MediumDreamToken.transferCreatorThe new creator was not excluded from holder rewards, so a launch wallet could hand the role to a second wallet and earn both shares.Exclusions follow the role: the new creator is excluded from rewards and inherits the tax / max-wallet exemptions; every past creator stays excluded. Tests: test_transferCreatorMovesExclusions, test_creatorCanNeverEarnHolderRewards.Fixed
7MediumDreamToken.setExcludedFromRewards, setMinHoldForRewardsThe creator could re-include themselves, exclude every large holder, or set a minimum hold above everyone's balance but one, steering all holder rewards to one wallet.Creator wallets (past or present) can never be included; only contracts can be excluded; minimum hold capped at 0.1% of supply.Fixed
8MediumDreamToken.setAmmPairAny address could be flagged as a pool (taxing its transfers and stripping its rewards), and un-flagging any address re-included protected ones.Flagging requires a real pool of the coin; un-flagging requires a flagged pool.Fixed
9MediumDreamPadFactory.refreshQuoteAsset, registerQuoteAssetPermissionless re-sizing from a spot price could sandwich a launch into a wrong raise target; owner-disabled assets could be re-registered.Refresh once an hour, 25% per step; owner-disabled assets stay disabled. Tests: test_refreshFollowsPriceSlowly, test_ownerDisabledAssetCannotBeReregistered.Fixed
10MediumUniswapV3OracleSpot prices from a single pool; a shallow registered V4 pool (with any hook) displaced the real market for pricing and routing.10-minute TWAP when the pool keeps history; V4 registration only for hookless pools or template hooks, above minLiquidity; the deepest V4 pool always wins. Precision fixed for cheap 18-decimal assets against 6-decimal stables. Test: test_shallowRegisteredPoolCannotDisplaceTheRealMarket.Fixed
11MediumLiquidityVault.harvest, LiquidityVaultV3.harvest, DreamToken._swapBackFloor-less sales of the token side could be sandwiched, and they are triggered by anyone or at predictable moments.Per-sale size caps: 0.5% of the pair's token reserve (V2 harvest and swap-back) or a 1% sqrt-price move (V3 harvest); the remainder waits for the next call.Fixed
12MediumUniswapV3Adapter.uniswapV3MintCallbackThe callback trusted the pool address from calldata, so anyone could drain tokens parked on the adapter.Callback honoured only for the pool the adapter is mid-call with.Fixed
13LowDreamPadFactory.setLaunchesPausedOwner-only.Treasury (admin wallet) only, with a switch on the admin page. releaseTicker accepts owner or treasury.Fixed
14LowDreamPadFactory._deployCurveCurves launched through the zap recorded the zap as creator.The real creator is passed through.Fixed
15LowDreamToken.forceSwapBackA creator who disabled automatic swaps could hold holder rewards hostage.Anyone may trigger a swap-back once the pot reached the threshold.Fixed
16LowRewardsDistributor.processBatchA recipient the reward token refuses (USDC blocklist) stalled the batch forever.Refused transfers are skipped.Fixed
17Lowstuck fundsStray transfers to the curve, ETH sent to the token or distributor, and factory balance refunds.Only the curve pulls tokens; ETH enters only via WETH unwraps; refunds are delta-based.Fixed
18LowUniswapV3Adapter sqrt priceOverflow for a tiny low-decimal quote side.Shorter shift fallback.Fixed
Web app
#SeverityWhereFindingFixStatus
W1Highmetadata.ts, TokenLogoUntyped on-chain metadata (an image that is a number, an object description) crashed the React tree for every visitor of the explore page.Metadata is validated and capped field by field; only data:, ipfs:// and http(s):// sources load; remote metadata is bounded in size and time; an app-level error boundary contains any remaining render error.Fixed
W2Medium/api/uploadUnauthenticated proxy to the operator's Pinata account.Same-origin only, 1 MB cap with early content-length check, magic-byte sniffing (no SVG), per-address rate limit, upstream timeout, pins tagged for cleanup.Fixed
W3MediumheadersNo frame protection on an app that holds a hot key and fires one-click transactions.frame-ancestors 'none', X-Frame-Options: DENY, object-src 'none', base-uri 'self', nosniff, referrer and permissions policies.Fixed
W4Lowconfig.tsHidden, unconfigured networks were reachable by URL and persisted, pointing browsers at localhost RPCs.Only shown networks resolve from URLs or saved preferences.Fixed
W5Lowfast wallet resetReset only checked the balance on the current network.Reset requires exporting the key first and typing RESET, and warns that the key spans every network.Fixed
W6Lowlaunch formNo warning for community-registered pair assets.Unverified pair assets are flagged with what can go wrong.Fixed
Phase 2

stricter pass

5 findings · all fixed

Method: property-based invariants (test/Invariants.t.sol: curve solvency, reward backing, supply conservation, creator never earns holder rewards, split fixed, 1,920 random calls per property), oracle TWAP and precision tests, two independent proof-of-concept attack passes with runnable Foundry exploits (test/Attack.t.sol, test/Attack2.t.sol, test/AttackV3.t.sol, now kept as regression tests), and a second manual read. Suite after round 2: 190 tests; after round 3: 241. Attempted and defended in this round (each has a passing test or a written argument in the attack files): curve reserve accounting under mixed buys and sells; distributor solvency under exclusion, re-inclusion and minimum-hold churn with a full claim-out; creator wallets holding large bags; protocol reward drains; tax or split mutation; pre-graduation pool seeding through a curve buy; arbitrary AMM-pair flagging; V3 pools pre-initialised far above or below the curve price (empty); a 24-decimal pair asset through the whole curve and graduation; registry spam (the list is never iterated on-chain); reward magnitude overflow; zap fund theft through callbacks or sweeps.

V3 graduation against deep pre-placed quote-only liquidity (test_H6c, test_H6d). With the pool pre-initialised at a token price 16× the curve's and deep quote liquidity in between, the adapter's alignment budget (half of the token side) cannot reach the target, so the pool opens above the curve price. The PoC shows the attacker loses about 13 ETH doing it: the adapter sells the coin's tokens to them at 16× and the leftover quote is paid to holders through the revenue split, curve buyers can sell into that liquidity at the inflated price, and the vault's full-range position is intact. Opening a pool mispriced at the attacker's expense is accepted; a looped alignment is a possible refinement.

Sandwich residue. With the average-price floor, a front-run inside the 0.3% (V2) / 1%-of-sqrt (V3) tolerance still costs the attacker two pool fees on a trade larger than the sale, which the PoC shows as a loss at every size on a 0.3% pool. On a pool with lower fees the residue would be bounded by the tolerance times the sale (0.3% of at most 0.5% of the reserve).

Creator-exempted MEV bot. A creator may exempt any address from tax (a CEX, a vesting contract), including a bot of their own. With the floor above, what such a bot can take from swap-backs is the same bounded residue; it cannot take the pot.

#SeverityWhereFindingFixStatus
19MediumLiquidityVault.harvest, DreamToken._swapBack, LiquidityVaultV3.harvestThe round-1 size cap (finding 11) bounded each sale but not the sandwich: with no output floor, a caller could dump, trigger the permissionless sale, and buy back. On a 0% tax coin the PoC netted about 5% of every harvest, repeatable.Every token-side sale now runs at most once per ten-minute window and is floored at the pool's time-weighted average price over that window (V2: the pair's own cumulative price, exact x·y=k fair output, 0.3% tolerance; V3: swap limit 1% below the average sqrt price from the pool's observations, which the adapter switches on at graduation). A depressed price defers the sale (TokenSaleDeferred / SwapBackDeferred) instead of selling into it. PoC re-run: the best sandwich now loses money at every size. Tests: test_harvestCannotBeSandwichedForProfit, test_tokenSideWaitsWhenThePriceWasJustPushedDown, test_tokenSideIsSoldAtMostOncePerWindow, test_harvestHoldsTokenFeesWhenThePriceWasJustPushedDown, test_harvestSellsNoTokenFeesWithoutPriceHistory, test_H2_harvestSandwichNeverPays.Fixed
20MediumDreamPadFactory.registerQuoteAssetRound 1 (finding 9) rate-limited refreshes but the first sizing was still unbounded and, for assets priced from a V2 pair or a V4 pool, spot. A one-block dump of a shallow pool set a raise target 50× off, and refreshes could only walk it back 25% an hour.Community registration is two-step: the first sizing leaves the asset pending; refreshQuoteAsset at least an hour later must size it within 25% of the first before launches open (QuoteAssetActivated); a disagreeing sizing replaces the first and restarts the hour. Owner-configured assets are unaffected. Tests: test_aPushedFirstPriceDoesNotActivate, test_pendingAssetCannotBeReregisteredEarlyOrLaunchedOn, test_H1_pushedFirstSizingNeverOpensLaunches.Fixed
21MediumDreamZap.buyCurve, launchWithETHThe sell-out refund was measured as the zap's whole balance of the pair asset, so one unit of USDC (or any pair asset) sent to the zap became a "refund": every fee-only ETH-paid launch on that asset reverted (division by zero in the refund-rate check), and a larger donation was gifted to the next buyer.Refunds are measured as the balance change within the call; nothing bought this call means nothing is a refund. Test: test_strayPairAssetOnTheZapCannotBrickBuys.Fixed
22LowCurveMath.grossForNet, BondingCurve._buyThe gross-for-net rounding could exceed the true minimum by one unit, so a final buy whose amount already covered the last tokens computed a refund larger than the amount sent and reverted (underflow). One-wei window, but a wrong revert on the curve's most important buy.Exact closed form for the smallest covering gross, plus a clamp in the curve. Fuzz test: testFuzz_grossForNetIsTheSmallestCoveringGross.Fixed
23Lowscript/Deploy.s.solThe mainnet script never checked that the factory landed on the address the adapter and vault were bound to; a nonce that drifted (a resumed broadcast) would have silently produced a wired-to-nothing deployment on a one-shot mainnet deploy.The script now reverts unless the adapter's bound factory is the deployed factory.Fixed
Phase 3

final pre-mainnet pass

8 findings · all fixed

Method: three more independent adversarial passes with runnable proofs (the round-2 code: test/Attack3.t.sol; the rest of the protocol end to end: test/Attack4.t.sol; the web app), Slither over src/ (86 raw results, each read), the invariant suite at 18,000 random calls per property, fuzz tests for the floor maths (test/V2Twap.t.sol), and a manual re-read of everything round 2 changed. Attempted and defended in this round (each with a passing test in the attack files): distributor accounting under raising and lowering the minimum hold mid-life, refused and reverting recipients in batch and ETH claims, reentrancy through the ETH claim, pre-graduation tokens reaching any address other than the curve (EOAs, fake pools that claim token0() == this, a V4 PoolManager), the final curve buy when the remaining cost is below the minimum buy, exact-cost final buys leaving no dust, the zap's inner swap guarded by the outer minimum, a re-pointed zap against existing curves, ticker normalisation and reservation front-running, onGraduated spoofing, protocol-reward batch authorisation, the pause switch's scope, implementation initialisers and clone re-initialisation, TaxVault, FactoryBound and vault registration authorisation, creator transfers to contracts, V4 registration without a StateView, non-8-decimal and negative feeds.

V3 route selection by liquidity for the ETH / pair-asset leg (bestPool) can be won by a concentrated one-tick pool at another fee tier. Preview and execution use the same pool, and the single-range estimate overstates output across tick boundaries, so the app's minimum makes such a trade revert rather than lose money. Same class as the accepted "spot venues" note below; the coin's own leg no longer uses this lookup.

Ticker display spoofing. A symbol with a trailing control character is a distinct ticker key; it cannot take another coin's reservation but can look like it on screen. The app renders symbols as text and the explore page shows the address; a normalisation of control characters is a possible refinement.

An asset whose only market the attacker owns. The two-step activation makes a pushed price expensive to hold on a real market; on a pool with a single owner and no arbitrage a bogus price costs nothing to hold, so such an asset activates at whatever size its owner chooses (test_attackerControlledPoolActivatesAtChosenSize). This is the permissionless model: a creator chooses their pair asset, the launch form flags assets outside the stock lists as unverified, and the owner can disable one for good. A minimum external liquidity or holder count is a possible refinement.

Griefing the swap-back window. Someone can push the price more than the tolerance below the average right before each window closes, deferring the token-side sale again and again. It costs them two pool fees on a trade larger than the deferred sale every ten minutes and gains them nothing; holder rewards from the quote side keep flowing, and the token side sells as soon as they stop.

#SeverityWhereFindingFixStatus
24MediumDreamZap.buyPool, sellPoolAfter graduation the ETH / pair-asset leg was hard-wired to one venue (the V2 router, or V3 on V3 chains) while the previews used every venue. A coin paired with a V4-only asset (a Pons coin on Robinhood) or with a stock whose ETH market is V3 (every tokenized stock on Ethereum) showed a healthy quote and reverted on the trade once it graduated. No funds at risk; the advertised ETH in / ETH out was simply broken for those pairs after graduation.Both legs use the same selection as the curve paths: ETH / pair asset through the best of V2, V3 and V4; pair asset / coin on the coin's own graduation pool (recorded by the token), never a lookup by liquidity that a one-tick pool at another fee tier could win. On Ethereum the zap is now also given the V3 factory. Tests: test_postGraduationBuyPoolWorksForV4PairedCoin, test_postGraduationSellPoolWorksForV4PairedCoin.Fixed
25LowUniswapV3Oracle.priceUsdThe Chainlink branch checked the answer's sign but not its age; a feed that stopped updating kept pricing WETH.Answers older than feedMaxStaleness (owner-set, one day) are not a price. Test: test_oraclePriceUsdRejectsAStaleFeed.Fixed
26LowV2Twap.fairOut, DreamToken._swapBackTermsThe fair-output maths could overflow (revert) when the average price no longer fitted the pool's k, which needs a price collapse of more than 2^32 inside one window; the revert would have surfaced inside a user's sell. A token whose graduation could not record a window would never have started one.An unreachable floor (the sale is deferred) instead of a revert; the tolerance maths through mulDiv; the first swap-back attempt starts the window. Fuzz tests: testFuzz_fairOutNeverReverts, testFuzz_fairOutAtSpotMatchesTheRouterFormula, testFuzz_fairOutRisesWithTheAveragePrice.Fixed
27InfoRewardsDistributor._tryClaim, DreamToken.transferCreatorState written after an external call (Slither). Not exploitable: every claim entry point is non-reentrant and the distributor is our own contract.Checks-effects-interactions order; a refused batch transfer is unbooked.Fixed
Web app
#SeverityWhereFindingFixStatus
W7MediumuseProfile.tsThe profile page scanned every block since the first launch on the network, six filters at a time, for any address anyone typed in. On Robinhood's block times that is tens of thousands of log requests per visit, enough to exhaust the shared RPC for every visitor.90 days by default, older history on request.Fixed
W8Medium/api/uploadThe per-address limiter keyed on the client-controlled x-forwarded-for header, a flood reset everyone's budget, a body without Content-Length was buffered before the size check, and the visitor's address was written into the pin's metadata at Pinata.Platform-set client address only, budgets expire instead of resetting, Content-Length required, no address in pin metadata.Fixed
W9Lowmetadata.tsRemote metadata JSON was fetched by every visitor's browser from any URL a creator put on-chain (a tracking beacon), and read whole before the size check; SVG data URIs were accepted as logos while the upload route refuses SVG.Remote metadata only from IPFS through the gateway, no redirects, read no further than the cap; raster data URIs only, capped at 48 KB.Fixed
W10InfodependenciesAdvisories in transitive packages of the wallet libraries, none reachable from the app's code paths.Non-breaking updates applied; the remaining ones are in WalletConnect's server-side WebSocket and CDP packages that are aliased out of the bundle.Fixed
Phase 4

buyback-and-burn feature

4 findings · all fixed

Scope: the new BuybackVault (one per factory), V3Twap, and the changes that route a launch's buyback share to it (DreamToken: a fourth bucket in _splitRevenue, a pre-graduation rule letting the vault transfer to the dead address only, fixed exemptions; DreamPadFactory: buybackShareBps and burnCapBps in the launch parameters, setBuybackVault). Method: the feature's own suite (test/Buyback.t.sol, 10 tests including a sandwich sweep, plus the 12 proof tests of the pass kept as regressions), an independent adversarial pass with runnable proofs (test/Attack5.t.sol), and a manual read against the threat model of rounds 1 to 3. Design decisions that follow from those rounds:

The trigger is permissionless, so every buy is bounded like the fee sales. Once per ten-minute window; on the curve at most 0.5% of the virtual quote (a round trip pays the 2% curve fee twice, a 1% price move cannot pay for that); on a V2 pool at most 0.5% of the quote reserve, floored at the pair's time-weighted average price since the previous run with a 0.3% tolerance; on a V3 pool a swap limited to 1% above the pool's average sqrt price. A pushed-up price defers the buy. The feature's sandwich sweep shows a loss for the attacker at every size.

The share comes out of the creator's part, never the holders'. The launch form shows all four buckets; the contract checks that holders + creator + buyback = 100% of the non-platform part.

Nothing but bought coins leave the vault, and only to the dead address or the creator. withdraw moves only held (coins bought after the burn cap), only to the creator, and only after graduation because the token's pre-graduation rule allows the vault a single destination.

Per-coin ledgers. The vault holds many pair assets for many coins in one balance; pending is credited only through deposit, which pulls the exact amount from a launched coin, and a run spends only that coin's pending. Curve fees that flow straight back during a run (_depositedDuringRun) are separated from a sell-out refund.

Exemptions are fixed. The creator cannot tax, cap or re-include the vault; a wallet that buys on the curve can name the vault as recipient, which only strands (effectively burns) its own coins. Attempted and defended (tests in test/Attack5.t.sol): curve sandwiches at every size (the 2% curve fee twice beats the move), deferral griefing (two pool fees per window for nothing), per-coin ledgers across coins and through a sell-out refund with fees flowing back mid-run, burn and withdrawal bounds, replacing the vault (existing coins keep theirs, a hostile vault cannot deposit), share rounding, configure and deposit authorisation, callback pinning.

#SeverityWhereFindingFixStatus
28MediumV3Twap, BuybackVault._buyOnV3, LiquidityVaultV3A V3 pool can be created and initialised before graduation by anyone, at any price, with an observation aged as long as they like. That observation was the pool's "oldest" and so the whole price history the first buyback after graduation trusted; with no size cap on the V3 path the run spent all pending in one swap far past the price limit (PoC: all 0.37 ETH pending spent, a 451 bps sqrt move against a 100 bps limit, a sandwich taking 43% of the spend). The V3 harvest shared the library.Price history starts at the coin's graduation (notBefore): observations older than the coin's own market are ignored, and the first five minutes after graduation are never enough history. V3 buys are capped at 0.2% of the pool's quote balance; the V3 limits are 0.5% on sqrt price (about 1% on price, under the 2% a round trip through a 1% pool costs). Test: test_F2_preInitialisedV3PoolDoesNotSeedTheTwapOrUncapTheFirstRun.Fixed
29LowBuybackVault._v2Terms, LiquidityVault._saleTerms, DreamToken._swapBackTermsThe round-2 reasoning had the inequality backwards: a trade of 0.5% of a reserve moves the price about 1%, more than a V2 round trip costs, so pushing the price, holding it for one window (the average then equals the pushed price) and trading back paid on 0% tax coins: about 45% of a buyback's spend at the largest pushes. The cap was also computed on the reserve *after* the push, which the push itself inflates.Every V2 sale and buy is sized at 0.2% of the reserve as it was when the window started (the smaller of then and now), a move of about 0.4%, under the round-trip cost; the PoC sweep now loses at every push from 1% to 100% of the reserve. Tests: test_F3_v2HeldWindowSandwichNeverPays, the harvest and swap-back suites.Fixed
30LowDreamToken.setExcludedFromMaxWalletThe tax and reward exemptions of the plumbing were protected, the max-wallet ones were not: a creator could revoke the dead address's (or a vault's) exemption and make every burn, and every harvest, revert until the max-wallet period ended.The token, curve, vaults, adapter, zap and dead address are protected. Test: test_F1_creatorCannotRevokeTheDeadAddressOrVaultExemptions.Fixed
31InfoBuybackVault.withdraw, stray coinsA creator withdrawing held coins to the dead address was not booked as a burn; coins parked in the vault by a curve buy naming it as recipient were stranded outside the ledger.Withdrawals to the dead address count as burned; a permissionless burnStray burns and books coins that reached the vault outside a buyback.Fixed
Phase 5

full-codebase pass before mainnet

15 findings · all fixed

Scope: every contract, the web app, the API routes, the deploy scripts, the runbooks, the git history and the live Vercel project. Method: three independent adversarial passes with runnable proofs (test/Attack6.t.sol: token, curve, factory, zap, 22 tests; test/Attack7.t.sol: vaults, buyback, rewards, oracles, 7 tests; a web and operations pass that ran the app with hostile metadata under Playwright), Slither, npm audit, and a secret sweep of every commit, dangling object and untracked file. Every proof that found something is kept as a regression that now asserts the fix.

Key material. No private key, mnemonic, RPC key, Pinata JWT or Vercel token has ever been committed on any branch or dangling commit. The only 64-hex strings in the history are Anvil's two public developer keys in script/DeployLocal.s.sol. cache/, broadcast/, .env* and now every *.log are ignored; server secrets are read only inside the two API route handlers; nothing in a client module touches them. The treasury is an address the factory reports, held by a Safe on mainnet per the runbook; the deployer keeps no role once the Safe accepts ownership. Attempted and defended (tests in test/Attack6.t.sol and test/Attack7.t.sol): ETH-refund re-entrancy on the final buy and on unwrapped sells, pre-funded pair griefing, the max wallet against graduation, the dead address and the adapter, the final-buy refund maths and a CurveMath fuzz, sellFor and zap allowance abuse, swap-back floor DoS, creator cap tightening and role transfers, treasury and owner key powers (a table of every power is in the pass report), zap cross-venue routing leaving nothing behind, the vault and zap as max-wallet bypasses, buyback ledger inflation and fake coins, a buyback run that graduates the curve, spoofing or reverting onGraduated, the V3 held-window sandwich (a loss at every size), V3 run bounds, burnStray against a creator's held coins, and, on the web, stored XSS through every metadata field, SVG script execution, prototype pollution, SSRF through metadata, fast-key exfiltration (661 requests, none carried it), chain and contract confusion through crafted URLs, clickjacking, and docs search reflection.

#SeverityWhereFindingFixStatus
32HighBuybackVault.burnStrayTook any address. Passing a pair asset (WETH, USDC) burned the vault's entire balance of it, every coin's pending buyback share, and every later run reverted. Permissionless, free, repeatable per deposit.Only a coin the factory configured can be named (registered). Test: test_F1_burnStrayBurnsEveryCoinsPendingBuybackQuote.Fixed
33HighDreamPadFactory._launchThe launch parameter minHoldForRewards bypassed the 0.1% cap that the creator's setter has (finding 7). A minimum nobody could meet parked every holder reward; the creator then lowered it and a wallet of their own took the whole carry with two dust buys (0.089 ETH of other buyers' fees in the proof).The cap applies at launch; the form refuses it too. Test: test_F2_launchTimeMinHoldLetsTheCreatorSweepEveryHolderReward.Fixed
34MediumBuybackVault._observeThe V2 buy cap and TWAP floor were anchored to the reserve and price at the first run, chosen by whoever called it, and a deferred run re-anchored again. A push above 100% of the reserve, held for one window, paid (+0.0102 ETH on a 0.047 ETH spend).The coin seeds the vault's first window at graduation (onGraduated, best effort from DreamToken.graduate) and a deferred run never moves the anchor. Tests: test_v2FirstRunSnapshotInflationStealsPending, test_v2FirstRunSnapshotIsAttackerControlled, test_v2WindowSeededAtGraduationAndKeptThroughDeferrals.Fixed
35MediumDreamPadFactory.registerQuoteAsset / refreshQuoteAsset, UniswapV3OracleThe two-step community activation sampled two instants an hour apart; for an asset priced from a V2 pair (spot) an attacker pushed the pair at both instants and unwound in the same block. A real asset activated 16x too small ($1,884 raises instead of $30,000) for 0.09 ETH, with nothing held.The oracle reports the V2 pair it prices an asset from (v2Source); the factory records that pair's cumulative price at every sizing and sizes from the average since (at least an hour). A pending asset activates only when the instant agrees with the average and the average agrees with the previous sizing; an active asset is re-sized from the average, bounded to 25% a step. A push now has to be held for the whole hour against arbitrage. Test: test_F3_twoFlashPushesActivateARealAssetAtABogusSize.Fixed
36LowDreamToken.setExcludedFromRewardsThe zap, adapter, distributor and tax vault were not protected. A creator re-included the zap, which holds a seller's coins for the length of sellPool; a swap-back inside that call paid it holder rewards, and the next zap caller swept them (0.0126 ETH from one sell).They are protected, as are AMM pairs. Test: test_F4_creatorCanReincludeTheZapAndLeakHolderRewardsThroughIt.Fixed
37LowDreamToken.setExcludedFromTaxThe zap was not protected: one creator call removed the sell tax for every sell through the app's ETH-out route while the page still showed it.The zap, adapter, pair and AMM pairs are protected. Test: test_N18_creatorCanExemptTheZapAndRemoveTheSellTaxForEveryone.Fixed
38InfoDreamPadFactory._launchA max wallet below what the minimum buy purchases at the opening price locked everyone but the creator out of the curve.The factory refuses the combination (InvalidMaxWallet). Test: test_N14_capBelowMinBuyLocksOutsidersOutButNeverLocksTheCurve.Fixed
39MediumWeb: share-card rendererThe server rasterised creator SVG logos (the client refuses them); a 13 KB stacked-filter SVG took 18.7 s of CPU per request, cache-bustable, on a public route.Raster logos only, rendered at most once an hour per coin.Fixed
40MediumWeb: fast-trading keyThe key sat in plain text in localStorage and in the shared React context, with no script CSP: one XSS, one bad extension, and every fast wallet on the site was drained. No XSS was found; this is defence in depth.A per-request nonce Content Security Policy from middleware (scripts only from the app's own bundles; connections only to the configured RPCs, wallet relays, IPFS and analytics), the key removed from the context (the export action reads it explicitly), and an inactivity lock after an hour.Fixed
41Mediumscript/Deploy.s.sol, UniswapV3OracleOWNER defaulted to the deployer and nothing checked that the admin roles were contracts; the oracle's ownership transfer was single-step, so a mistyped OWNER lost its admin.On a live chain the script refuses an OWNER or TREASURY that is not a contract, or a deployer that is the treasury (ALLOW_EOA_ADMIN=true for rehearsals); the oracle is Ownable2Step; PRIVATE_KEY is optional so a hardware wallet can broadcast.Fixed
42LowWeb: zap trustThe app routed every ETH trade through whatever factory.zap() said at that moment and granted it max approvals, so a lost owner key could re-point users' trades with setZap.The zap is pinned per network (NEXT_PUBLIC_ZAP_ADDRESS_*); the app refuses to trade through any other and says so.Fixed
43LowWeb: upload routeThe same-origin check passed when Origin was absent; the rate-limit key collapsed to one bucket off Vercel.A request with neither Origin nor Sec-Fetch-Site is refused; no trusted client address means no upload.Fixed
44LowWeb: link normalisationhttps://good.com@evil.example/ rendered as a "good.com" link; IP literals and any host passed for social links.No userinfo hosts, no IP literals, X and Telegram links only on their own domains.Fixed
45LowOps: log hygieneRunbook-generated deploy logs were not ignored; cache/ keeps the full RPC URL (with its key) of a deploy.*.log ignored; the runbook writes logs outside the checkout and says to delete cache/ after a deploy.Fixed
46LowWeb: dependenciesTwo high and 23 moderate transitive advisories (ws, postcss, decode-uri-component).Overrides; npm audit --omit=dev reports none.Fixed
Disclosed

Accepted risks and design notes

  • Unlimited approvals in the trade panels are deliberate (one approval per token, as requested). Spenders are the curve, the zap and the Uniswap router read from the factory, never from the URL.
  • Spot venues in the zap are still Uniswap pools; a deep one-tick position can win a route and make a trade fail its slippage check (a revert, never a loss). Users see the same estimate and execution.
  • Ticker reservation can be front-run by a bundler who pays the launch fee first; the treasury can release such a ticker. A commit-reveal scheme was judged not worth the launch UX cost.
  • First eligible holder receives the carried rewards accumulated while nobody was eligible (a creator's excluded initial buy). Small, and it favours the first outside buyer.
  • Pair assets are third-party tokens. A pausable or blocklisting token can freeze a coin's rewards and trading. Community registrations go through PairAssetGuard, which refuses a transfer fee (a real probe transfer), a standard paused() getter and the usual blocklist getters, and launches on a paused asset are refused; a token that hides such controls behind non-standard names still passes, so the app keeps warning for community-registered assets. Owner-listed assets (the stablecoin, the issuers' stocks) have these controls by regulation and skip the guard.
  • Uniswap V2 protocol fee (feeTo, currently off on mainnet) would dilute the V2 vault's harvest measure by one sixth if ever enabled; the vault would then need the kLast formula.
  • Oracle falls back to spot where a pool keeps no history, and V2 pairs are always spot. It sizes raises and the $2 launch fee only; nothing settles against it.
  • Fast trading keys are stored unencrypted in the browser by design of the feature; the app says so and offers export. Since round 5 the page runs under a nonce Content Security Policy, the key is not exposed through the app's React context, and fast mode locks after an hour without a trade.
  • Community pair assets priced only from a Uniswap V4 pool are still sized from spot readings (V4 pools keep no price history the oracle can average). The owner can disable such an asset; assets with a V2 pair or a V3 pool are sized from a time-weighted average.
  • The buyback vault's graduation hook is best effort. If it ever failed, that coin's first V2 window would again open at the first run, bounded by the size caps (round 5, N16).
Audit sheet · DreamPad