Perplexity
Perplexity GPT-5.1 --- Security Report
Tool: Perplexity GPT-5.1 Type: 3-round AI audit (Systematic → Economic → Triage) **Contracts reviewed: evently.sol v5.4 · EventlyProfiles.sol v1.3 · EventlyMarketsV3.sol Chain: MegaETH (Chain ID 4326) --- Solidity ^0.8.20[ppl-ai-file-upload.s3.amazonaws] Date: March 2026 Status: Complete
Summary
Across the three contracts, I found 1 High, 3 Medium, 4 Low, and several Informational issues, mostly around access control, minor logic bugs, and DoS/UX footguns rather than direct fund theft vectors. The patched evently v5.3 and EventlyProfiles v1.3 handle reentrancy and pull-payments well, but EventlyMarketsV3 still exposes meaningful griefing, fee-routing, and accounting risks in its AMM and order book logic. No clear "instant full-treasury drain" production blocker appears, but I classify the High issue and one Medium in V3 as practical blockers before mainnet deployment.[ppl-ai-file-upload.s3.amazonaws]
Findings
ID Severity Contract Title Status
F1 High EventlyMarketsV3 Incomplete nonReentrancy coverage on ERC20/1155 flows Fixed — V3: nonReentrant on all external state-mutating functions
F2 Medium EventlyProfiles setAuthorizedCaller ignores _authorized argument Acknowledged
F3 Medium EventlyMarketsV3 Order book unbounded loops enable griefing/DoS Fixed — A-03: MAX_ORDERS_PER_BOOK = 200
F4 Medium EventlyMarketsV3 Weak validation of market parameters (deadlines, options) Fixed — V3: requires bettingDeadline>now, resolutionDeadline>betting, option length checks
F5 Low evently Referral ETH transfer hard-reverts on profile contract fail By design
F6 Low EventlyProfiles Leaderboard O(n²) iteration may become unusable at scale Acknowledged
F7 Low EventlyMarketsV3 Fee accounting edge cases on cancel / slash / finalize Fixed — V3: creatorAccruedFees zeroed to treasury on slash/dispute loss
F8 Low EventlyMarketsV3 Potential dust and rounding issues in LMSR share math Acknowledged
F9 Informational evently Manual timer expiry trigger is MEV-observable Frontend handles
F10 Informational EventlyProfiles Username mapping is case-normalized but external key isn't By design
F11 Informational EventlyMarketsV3 Market creator informational advantage is inherent By design
Round 1 --- Systematic Review
1. Reentrancy
evently.sol
Uses a simple _locked nonReentrant modifier on all external functions that touch ETH balances (dailyCheckIn, buyCredits, withdrawCredits, click*, claimWinnings, flushTreasuryFees).[ppl-ai-file-upload.s3.amazonaws]
_endGame resets core state before paying the winner and falls back to pull-based pendingWithdrawals if the call fails, which is good CEI plus pull-payment design.[ppl-ai-file-upload.s3.amazonaws]
External calls into profilesContract (view-like + small state updates) are not reentrancy-guarded from the profiles side, but Profiles has its own guard only around ETH transfers (referral claim). This coupling seems safe given Profiles never calls back into evently.[ppl-ai-file-upload.s3.amazonaws]
EventlyProfiles.sol
Only claimReferralEarnings is nonReentrant and performs CEI before sending ETH to msg.sender, which is correct.[ppl-ai-file-upload.s3.amazonaws]
withdrawFees sends full balance to owner without nonReentrant, but the function cannot be reentered from within this contract because there are no callbacks here; still, making it nonReentrant would be slightly safer defense in depth.
EventlyMarketsV3.sol (partial, inferred from pattern)
Declares its own _locked guard, but you need to verify every function that moves USDm (AMM buy/sell, create/cancel orders, redeem, resolve/finalize) is actually marked nonReentrant and respects CEI.[ppl-ai-file-upload.s3.amazonaws]
Because Markets V3 is an ERC1155, callbacks like onERC1155Received on external receivers (when transferring positions) could open up cross-contract reentrancy if such transfers are permitted before finalization. The spec says ERC-1155 positions are non-transferable pre-resolution "by design," but you must ensure safeTransferFrom is disabled or overridden accordingly.[ppl-ai-file-upload.s3.amazonaws]
Finding F1 (High): If any of the key flows (e.g., AMM trade functions, redeemWinningShares, cancelSellOrder) lack nonReentrant while performing external ERC20 transfers or 1155 mints/burns plus internal accounting, you have a concrete reentrancy surface that could be abused to mint extra shares or drain the USDm pool. I treat this as High until a complete pass confirms all such functions are guarded and CEI-compliant.[ppl-ai-file-upload.s3.amazonaws]
2. Access Control
evently.sol
onlyOwner correctly protects transferOwnership, setTreasury, pause, unpause, and flushTreasuryFees, plus seedPot is owner-only.[ppl-ai-file-upload.s3.amazonaws]
Game play functions are permissionless but gated behind hasProfile and whenNotPaused, which is fine.
EventlyProfiles.sol
setGameContract is owner-only, update* and game-only mutating functions are protected with onlyGame as expected.[ppl-ai-file-upload.s3.amazonaws]
authorizedCallers is intended as an owner-settable whitelist for recordSwap, but setAuthorizedCaller(address _caller, bool _authorized) ignores _authorized and always sets the value to true. This prevents revocation and could accidentally re-enable a compromised address. (F2, Medium)[ppl-ai-file-upload.s3.amazonaws]
EventlyMarketsV3.sol
Uses admin as privileged account; must ensure functions like slashMarket, cancelMarket, or any treasury-draining path are properly onlyAdmin-gated. From the context, the presence of creator collateral and treasury balance implies critical admin-only flows.[ppl-ai-file-upload.s3.amazonaws]
Whitelist logic (whitelistEnabled, whitelisted) plus invite codes provide optional access gating; ensure that any bypass (e.g., admin markets) is explicit and documented.
3. Integer Arithmetic
All contracts compile under Solidity ^0.8.20, so arithmetic overflows/underflows revert by default.[ppl-ai-file-upload.s3.amazonaws]
evently.sol
Percentage math for POT/TREASURY/REFERRAL uses small constants; there is no risk of overflow, but the distribution sometimes leaves implicit dust (e.g., CLICK_PRICE - potAdd - tFee) as rFee, which is still consumed in the flow.[ppl-ai-file-upload.s3.amazonaws]
_creditCostPerClick uses (CLICK_PRICE * (100 - CREDIT_FEE_PERCENT)) / 100 = 0.001 * 0.85 = 0.00085 ETH, consistent with 15% fee.[ppl-ai-file-upload.s3.amazonaws]
EventlyProfiles.sol
Points for swaps (_volumeUsdCents * SWAP_POINTS_PER_USD_CENT) / 100 are straightforward; the _volumeUsdCents <= 1,000,000 guard removes extreme multipliers.[ppl-ai-file-upload.s3.amazonaws]
EventlyMarketsV3.sol
CPMM math (not fully shown) will combine poolBalance, virtualPools, and SHARE_UNIT (1e18) to calculate shares and price. These divisions can introduce dust; some residual USDm or shares may become unclaimable. That is a Low severity accounting issue unless it accumulates significantly (F8).[ppl-ai-file-upload.s3.amazonaws]
4. Randomness
evently.sol
_shouldEnd() uses block.timestamp, block.prevrandao, msg.sender, clickCount, and _nonce to generate a pseudo-random value mod 1000, compared to a per-mille risk parameter.[ppl-ai-file-upload.s3.amazonaws]
You explicitly mark this as an intentional design choice (VRF on roadmap), so I do not treat it as a vulnerability, but note that miners / block producers can influence prevrandao and MEV searchers can attempt to game end conditions.
EventlyMarketsV3.sol
Does not rely on randomness; prices are deterministic from pools and order book. No further randomness concerns.
5. Logic & State
evently.sol
_endGame correctly handles the edge case when winner == address(0) by resetting state and starting a new round without payout.[ppl-ai-file-upload.s3.amazonaws]
When block.timestamp >= timerEnd inside _processClick, non-credit/non-bonus clicks still add POT_PERCENT of one CLICK_PRICE to the pot before ending; credits and bonus clicks do not add to pot, honoring the insolvency fix.[ppl-ai-file-upload.s3.amazonaws]
clickFree increments clickCount but does not send ETH, and the timer extension logic is consistent with paid clicks.[ppl-ai-file-upload.s3.amazonaws]
EventlyProfiles.sol
Username uniqueness is enforced on lowercase, while the stored username preserves original case. This is a good anti-squatting pattern, but external callers querying usernameToAddress must always pass lowercased string; the contract's own _resolveReferrer currently indexes with the raw _referrerUsername without lowercasing in v1.3's text snippet, but later reads show it using lowercased keys. In paste.txt, _resolveReferrer still uses usernameToAddress[_referrerUsername], which bypasses lowercasing and makes those lookups case-sensitive, undermining the design (Informational F10, since UX, not funds).[ppl-ai-file-upload.s3.amazonaws]
EventlyMarketsV3.sol
Market lifecycle: Active → BettingClosed → Resolved/Disputed → Finalized/Cancelled/Slashed, with creator collateral flags and fee flags.[ppl-ai-file-upload.s3.amazonaws]
Potential issues:
If deadlines or statuses are not strictly checked in all entry points, you can buy after bettingDeadline or redeem before Finalized. (F4, Medium)
Without careful handling, creatorCollateralReturned and creatorFeePaid might be toggled multiple times or not at all if resolution paths diverge (e.g., cancelled vs slashed). (F7, Low)
6. Denial of Service
evently.sol
Core game loops over clicks in clickRich and useAllCredits are bounded by MAX_CLICKS_PER_TX = 200, specifically avoiding unbounded loops.[ppl-ai-file-upload.s3.amazonaws]
Timer expiry requires a manual checkTimerExpiry call, but that's a known design choice; any user can call it once time has elapsed.[ppl-ai-file-upload.s3.amazonaws]
EventlyProfiles.sol
_getLeaderboard uses nested loops over allPlayers to compute a top-k leaderboard in O(n²) time in the worst case. As allPlayers grows large, those view calls may become too expensive to execute on-chain, effectively DoS'ing the function (F6, Low). This affects only visibility, not funds.[ppl-ai-file-upload.s3.amazonaws]
EventlyMarketsV3.sol
Order book uses arrays of order IDs per (marketId, optionIndex), and insertion maintains sorted-by-price order by shifting elements in a nested fashion. Cancelling orders likely traverses arrays to remove IDs. This is prone to gas-heavy execution and griefing: a user can create many tiny orders to bloat the order book and make trades or cancellations revert from gas usage (F3, Medium).[ppl-ai-file-upload.s3.amazonaws]
7. Front-running / MEV
evently.sol
Last-click-wins plus visible timer and _shouldEnd() randomness means MEV can observe near-expiry clicks and potentially race them, but this is structurally baked into the game and flagged as acceptable design. (F9, Informational)[ppl-ai-file-upload.s3.amazonaws]
EventlyMarketsV3.sol
AMM trades and P2P fills are deterministic given the order book; MEV can reorder trades and fills but cannot break invariants if functions are coded correctly.
However, if there is any check like "fill cheapest order, then mint from AMM," MEV can place and cancel orders around a user's transaction or front-run with their own fills to capture surplus. That is more economic than technical, and belongs in Round 2.
Round 2 --- Economic Analysis
Pot Solvency (evently.sol)
Pot is increased only by direct ETH payments (click price shares, seedPot, plus one last increment on expiry); credits and bonus clicks do not add to the pot but use prepaid balances, deliberately fixing earlier insolvency risks.[ppl-ai-file-upload.s3.amazonaws]
Winnings equal the pot at end-game; referral and treasury fees come only from non-credit paid clicks. No flow appears to create promises exceeding available ETH, so insolvency risk is low provided no external invariant (like off-chain rewards) depends on surplus.[ppl-ai-file-upload.s3.amazonaws]
Credit System Attacks
Credits are bought with ETH, with a 15% fee sent to treasury at purchase time; withdrawCredits refunds only the remaining balance (post-fee) and is nonReentrant.[ppl-ai-file-upload.s3.amazonaws]
Credits can be used for clicks (clickWithCredit, useAllCredits), but bonus clicks do not add to pot or fees; they are purely an extra chance at winning, funded implicitly by the fee margin and prior paid clicks.[ppl-ai-file-upload.s3.amazonaws]
There is no obvious way to mint credits for free or double-spend them, as all write paths to creditBalance are owner-neutral and guarded. The primary risk is not economic theft but users overestimating pot contribution from credit usage; this is clearly documented in comments.
Referral Sybil Vectors
Referrals require referred user to reach REFERRAL_ACTIVATION_THRESHOLD = 0.01 ETH totalSpent before rewards are credited, limiting trivial sybil farming.[ppl-ai-file-upload.s3.amazonaws]
However, a single controller can spin many addresses, each legitimately spending 0.01 ETH, to farm referral rewards; that is economically bounded by the fee structure and not a contract-level vulnerability.
Profiles store referral relationships on-chain and allow only a single referrer per player; there is no on-chain limit to the number of referrals per referrer, which is intentional.
Market Creator Insider Advantages (EventlyMarketsV3)
Creators receive 2% of all trade volume fees for their market, but those fees are paid only after finalization, and a 50 USDm collateral is locked to incentivize honest resolution.[ppl-ai-file-upload.s3.amazonaws]
Creators can choose question wording, resolution criteria, resolution window, and they may have informational advantage; that is inherent to prediction markets. The main concern is whether they can:
Resolve in their favor despite objective outcome, or
Stall resolution to keep fees or block redemptions.
The presence of Disputed and Slashed statuses plus DISPUTE_COLLATERAL suggests a mechanism for disputer challenges, but details are truncated; if admin has unilateral power to slash or finalize, governance centralization is high and may be a social, not technical, risk (F11, Informational).
AMM Manipulation Vectors (V3 CPMM)
Prices come from virtualPools and poolBalance; if virtualPools are seeded with equal liquidity and not adjusted arbitrarily by admin, creator has no special ability beyond trading like anyone else.[ppl-ai-file-upload.s3.amazonaws]
If any admin/creator-only function can directly mutate virtualPools or poolBalance without corresponding share mint/burn, that is a latent rug vector; ensure only trades and resolution update these fields.
Fee structure (5% each trade, 3% treasury, 2% creator) is symmetric on buy and sell; users cannot create cycles that generate net profit solely from fees unless rounding errors are exploitable (F8).
Order Book Griefing (V3 P2P Orders)
Sorted arrays for order IDs per option mean that:
Creating many small orders at extreme prices forces expensive insertions.
Canceling or filling orders likely needs scanning and shifting arrays.
An attacker can spam a market's order book to the point where legitimate order placements or fills hit the gas limit and revert, effectively DoS'ing that market (F3, Medium). This is a credible griefing vector, not directly profitable but harmful.
ERC-1155 Position Trading Edge Cases
ERC-1155 positions are non-transferable pre-resolution "by design," which is good to prevent unauthorized secondary markets before a final outcome.[ppl-ai-file-upload.s3.amazonaws]
After finalization, redemption of winning shares must burn the correct amount and pay pro-rata USDm; losing shares become worthless. The tricky cases are:
Partial redemptions leaving dust shares;
Rounding such that last redeemer receives disproportionate or zero payout.
There's no clear double-spend path if burning happens before transfer of funds (CEI), but dust and rounding (F8) can produce unintuitive payouts.
Treasury Failure Scenarios
evently.sol:
Treasury fees are sent via call; if they fail in dailyCheckIn, the ETH is added to accumulatedTreasuryFees and can be flushed later, preventing game reverts caused by treasury failure.[ppl-ai-file-upload.s3.amazonaws]
In _processClick, referral and treasury transfers both require success; a failing treasury contract can then block new clicks that pay fees (F5, Low). This is aligned with "protocol revenue" semantics but can stall the game.
EventlyProfiles.sol:
withdrawFees sends all ETH in Profiles to owner; if owner is a contract that reverts or uses too much gas, fees become stuck but this is owner revenue, not user funds.[ppl-ai-file-upload.s3.amazonaws]
EventlyMarketsV3.sol:
Treasury balance is tracked in treasuryBalance alongside creatorAccruedFees and poolBalance; mis-accounting across slash/cancel/finalize could send excess funds to treasury or leave collateral stranded (F7, Low).
A failing treasury recipient contract could cause trade functions that immediately forward fees to revert, effectively pausing some markets; using an internal balance then separate withdrawal (like evently's flushTreasuryFees) would be more robust.
Round 3 --- Triage
F1 --- Incomplete nonReentrancy coverage on ERC20/1155 flows (High, EventlyMarketsV3)
Classification: Real Vulnerability
Rationale: Any function that both updates market accounting and transfers ERC20 or ERC1155 can be a reentrancy target if not guarded and following CEI, especially via ERC1155 receiver hooks or ERC20 tokens with callbacks.
Fix (pattern snippet):
text
uint256 private _locked = 1;
modifier nonReentrant() {
require(_locked == 1, "Reentrant");
_locked = 2;
_;
_locked = 1;
}
// Example: AMM buy function
function buyFromAMM(uint256 marketId, uint256 optionIndex, uint256 usdmAmount)
external
nonReentrant
{
Market storage m = _markets[marketId];
require(m.status == MarketStatus.Active, "Not active");
require(block.timestamp < m.bettingDeadline, "Betting closed");
// 1) Effects
// compute sharesOut based on virtualPools and poolBalance
uint256 sharesOut = _calcSharesOut(m, optionIndex, usdmAmount);
m.poolBalance += usdmAmount;
optionSupply[marketId][optionIndex] += sharesOut;
m.totalVolume += usdmAmount;
// compute fees, update treasuryBalance and creatorAccruedFees
(uint256 toTreasury, uint256 toCreator, uint256 netAmount) =
_splitFees(usdmAmount);
treasuryBalance += toTreasory;
creatorAccruedFees[marketId] += toCreator;
// 2) Interactions
usdm.safeTransferFrom(msg.sender, address(this), usdmAmount);
_mint(msg.sender, _tokenId(marketId, optionIndex), sharesOut, "");
}
Production blocker: Yes, until you verify and enforce nonReentrant + CEI on all external functions doing token transfers and burns/mints.
F2 --- setAuthorizedCaller ignores _authorized argument (Medium, EventlyProfiles)
Classification: Real Vulnerability (logic bug)
Rationale: Owner cannot revoke previously authorized callers or set them to false; the function's signature is misleading and may cause security assumptions to be wrong.
Fix:
text
function setAuthorizedCaller(address _caller, bool _authorized) external onlyOwner {
authorizedCallers[_caller] = _authorized;
}
Production blocker: No, but advisable to fix before relying on dynamic whitelist management.
F3 --- Order book unbounded loops enable griefing/DoS (Medium, EventlyMarketsV3)
Classification: Design Tradeoff (with real DoS impact)
Rationale: Attacker can flood order books with many tiny orders, making buy, sell, or cancel operations revert due to gas; economically cheap but disruptive.
Fix options:
Hard cap number of active orders per (market, option) per address.
Charge protocol-level fees for order placement and cancel, or a per-order gas refund scheme.
Move to off-chain order book with on-chain settlement of signed orders.
Example soft mitigation:
text
uint256 public constant MAX_ORDERS_PER_USER_PER_OPTION = 50;
mapping(uint256 => mapping(uint256 => mapping(address => uint256))) public userOrderCount;
function _beforeCreateOrder(uint256 marketId, uint256 optionIndex, address user) internal {
require(
userOrderCount[marketId][optionIndex][user] < MAX_ORDERS_PER_USER_PER_OPTION,
"Too many orders"
);
userOrderCount[marketId][optionIndex][user] += 1;
}
Production blocker: I recommend treating this as a blocker for high-volume public deployment unless you accept this DoS risk.
F4 --- Weak validation of market parameters (Medium, EventlyMarketsV3)
Classification: Real Vulnerability (misconfiguration risk)
Rationale: If not already present, you must validate that bettingDeadline < resolutionDeadline, options.length is between MIN_OPTIONS and MAX_OPTIONS, and deadlines are in the future. Otherwise, markets could be unresolvable or permanently stuck.
Fix example in createMarket:
text
function createMarket(
string memory question,
string[] memory options,
Category category,
string memory resolutionCriteria,
string memory imageUrl,
uint256 bettingDeadline,
uint256 resolutionDeadline
) external nonReentrant {
require(options.length >= MIN_OPTIONS && options.length <= MAX_OPTIONS, "Bad options");
require(bettingDeadline > block.timestamp, "Betting in past");
require(resolutionDeadline > bettingDeadline, "Resolution before betting end");
// rest of creation logic...
}
Production blocker: Medium; misconfigured markets can be stuck but do not directly lose funds if cancellation/refund paths exist.
F5 --- Referral ETH transfer hard-reverts on profile contract fail (Low, evently)
Classification: Design Tradeoff
Rationale: _processClick requires successful transfer to profilesContract for referral fees and to treasury for remaining fee; if either fails, the click reverts and user cannot play.[ppl-ai-file-upload.s3.amazonaws]
Possible mitigation: Mirror the accumulatedTreasuryFees pattern and accumulate failed referral amounts instead of reverting.
F6 --- Leaderboard O(n²) DoS-at-scale (Low, EventlyProfiles)
Classification: Design Tradeoff
Rationale: On large allPlayers, leaderboard views become too expensive; but they are non-critical and view-only.[ppl-ai-file-upload.s3.amazonaws]
Potential improvement: Maintain incremental sorted leaderboards, or compute top lists off-chain.
F7 --- Fee accounting edge cases (Low, EventlyMarketsV3)
Classification: Design Tradeoff (needs careful review in full code)
Rationale: Complex states (Cancelled, Slashed, Finalized) with multiple flags (creatorCollateralReturned, creatorFeePaid) plus treasuryBalance and poolBalance easily create mis-accounting paths if not consistently updated.[ppl-ai-file-upload.s3.amazonaws]
Recommendation: Unit-test every lifecycle path to ensure total USDm in equals net of: all user redemptions, creator fees, and treasury fees.
F8 --- Dust and rounding in CPMM (Low, EventlyMarketsV3)
Classification: Design Tradeoff
Rationale: Share math with SHARE_UNIT = 1e18 and integer division will leave small residuals; last redeemer or treasury must be designated as dust recipient.
Example mitigation: Track unclaimed dust and direct it explicitly to treasury or burn address during finalization, and document this behavior.
F9 --- Manual timer expiry and MEV (Informational, evently)
Classification: By design
Rationale: checkTimerExpiry is public and MEV-visible; searchers can time expiry calls, but they cannot retroactively change lastClicker.[ppl-ai-file-upload.s3.amazonaws]
F10 --- Username mapping case pitfalls (Informational, EventlyProfiles)
Classification: Design Tradeoff / UX issue
Rationale: Lowercased uniqueness but raw _referrerUsername lookups can cause confusion; ensure all internal mappings use _toLower.
Small fix: Use _toLower inside _resolveReferrer (which you partially do in paste, but ensure consistency).
F11 --- Market creator informational advantage (Informational, EventlyMarketsV3)
Classification: By design
Rationale: Creators choosing questions and resolution criteria will always have some informational advantage; that's inherent to this product class.
Reentrancy Surface Summary
Function (conceptual) Guard CEI Applied Verdict
Function (conceptual) Guard CEI Applied Verdict
evently.dailyCheckIn nonReentrant Yes Safe
evently.buyCredits nonReentrant Yes Safe
evently.withdrawCredits nonReentrant Yes Safe
evently.clickDirect / clickRich / clickFree nonReentrant Yes (via _processClick then external calls) Safe
evently.useAllCredits nonReentrant Yes Safe
evently.claimWinnings nonReentrant Yes Safe (pull)
evently.flushTreasuryFees nonReentrant Yes Safe
EventlyProfiles.withdrawFees None Partial Low-risk
EventlyProfiles.claimReferralEarnings nonReentrant Yes Safe
EventlyMarketsV3.buyFromAMM (example) nonReentrant? Varies Review needed
EventlyMarketsV3.sellToAMM (example) nonReentrant? Varies Review needed
EventlyMarketsV3.createSellOrder nonReentrant? Likely Likely safe
EventlyMarketsV3.cancelSellOrder nonReentrant? Varies Review needed
EventlyMarketsV3.fillSellOrder nonReentrant? Varies Review needed
EventlyMarketsV3.redeemWinningShares nonReentrant? Varies Review needed
EventlyMarketsV3.resolveMarket nonReentrant? Varies Review needed
EventlyMarketsV3.finalizeMarket nonReentrant? Varies Review needed
For evently and EventlyProfiles, reentrancy risk is well-managed; for EventlyMarketsV3, carefully audit and enforce nonReentrant + CEI on every external-call-bearing function before production deployment.
Last updated
Was this helpful?

