Wake Arena: Multi-Agent AI Audit with Graph-Driven Reasoning

Benchmarked on audit competitions and production protocols

December 2025:

Josef Gattermayer, Ph.D.1,2
Michal Převrátil1
Martin Veselý1
Andrei Shchapaniak1
Josef Bazal1

1Ackee Blockchain
2Czech Technical University in Prague


Executive Summary

We present Wake Arena, a service for discovering vulnerabilities in Solidity smart contracts through multi-agent AI analysis with graph-driven reasoning.

Wake Arena discovered 43 of 94 high-severity vulnerabilities in historical audit competitions, outperforming both Zellic’s automated scanner (41/94) and plain GPT-5 (24/94). When experimentally integrated into Ackee’s manual audit workflow in November 2025 for Lido, Printr, and Everstake, Wake Arena identified 26 findings.

Specifically, Wake Arena discovered 5 critical vulnerabilities and 5 unique findings beyond those discovered by human auditors during the Printr audit.

Additionally, LUKSO served as a design partner, providing valuable feedback during development. In a purely AI-driven audit, Wake Arena identified 10 findings (2 High, 6 Medium, 1 Low, 1 Warning), with only two false positives. LUKSO’s responses and validation helped refine the service’s accuracy and user experience.

Metrics across benchmarks and production audits:

  • 33% of all reported findings and 50% of critical findings discovered
  • False positive rate below 50%
  • True positive rate above 50%

Unlike generic LLM wrappers, Wake Arena combines multi-agent AI reasoning, graph-driven analysis via Data Dependency and Control Flow graphs, and LLM-tailored static analysis from 200+ audits securing $180B+ in TVL.


Background and Motivation

Ackee Blockchain has conducted 200+ smart contract audits securing $180B+ in TVL (Lido, Aave, Axelar, Safe). As AI capabilities for code analysis have advanced, we built Wake Arena to leverage these capabilities while maintaining low false positive rates.

Wake Arena is a multi-agent AI system combining our private detector library (87 from billion-dollar audits) with deep AI-driven security reasoning that navigates Data Dependency Graphs like a senior auditor. LLM-tailored static analysis feeds deep code insights into a full AI-driven audit pipeline with graph-driven reasoning and contextual understanding from years of auditing expertise.

Problem statement

Teams need a consistent, powerful security tool that reliably finds important vulnerabilities before premium audits, helping protocols arrive with cleaner code and use audit time for deep protocol logic review. We want to help teams reduce reliance on surface-level tools while recognizing that high-quality manual audits remain essential for critical systems.


What Makes Wake Arena Different

1. Multi-agent AI system

A multi-step prompt pipeline combines:

  • Advanced reasoning and validation with multi-agent cross-checking
  • Deep contextual understanding from senior auditor expertise embedded in prompts
  • Full AI-driven audit pipeline from compilation to report generation

2. Graph-driven reasoning

LLM-tailored Static Analysis with Data Dependency and Control Flow graphs navigates the code to find:

  • Protocol-specific logical issues by tracing execution paths
  • Mathematical vulnerabilities through value flow analysis
  • Cross-function dependencies missed by pattern matching

Example: In the Lend protocol, Wake Arena traced how a deeply nested bug in LendStorage.borrowWithInterest affects CoreRouter and CrossChainRouter logic through getHypotheticalAccountLiquidityCollateral and other view functions, leading to denial-of-service reverts and incorrect accounting in the protocol. Tracing of the bug effects is what requires a deep graph-based reasoning.

3. Battle-tested static analysis integrated into AI workflow

LLM-tailored Static Analysis feeds deep insight about the analyzed code to the AI to:

  • Provide additional information not available in the code as text
  • Reference relevant code segments from different locations
  • Let AI perform analysis with the same contextual information and tooling that human auditors have

Evaluation Methodology

We evaluated Wake Arena performance through two distinct approaches:

  1. Audit competitions — comparing against industry-standard datasets
  2. Production audits — integration into production security audits

1. Audit competition performance

We accepted the Zellic benchmark dataset as an industry standard: 14 protocols from historical audit competitions (Code4rena and Sherlock) with publicly available codebases and verified findings reviewed by multiple security researchers.

Reproducibility: All benchmark codebases and competition findings are public. Wake Arena reports with code references and exploit scenarios are linked in the table below.

Benchmark Protocols

The table displays the number of high-severity issues identified¹.

Protocol Wake Arena Zellic’s automated security scanner V12 Plain GPT-5 Total
Basin 2/2 2/2 2/2 2
Blackhole 2/2 2/2 1/2 2
Burve 2/9 2/9 2/9 9
Crestal 1/1 1/1 1/1 1
DODO 2/5 2/5 1/5 5
Lambo.win 2/4 2/4 2/4 4
Lend 13/28 10/28 4/28 28
Mellow 2/6 2/6 1/6 6
Munchables 4/5 4/5 2/5 5
Notional Exponent 2/11 2/11 0/11 11
Phi 4/7 6/7 3/7 7
Superfluid 1/2 1/2 1/2 2
TraitForge 2/6 1/6 2/6 6
Virtuals 4/6 4/6 2/6 6
Total 43/94 41/94 24/94 94

Notes:

¹ We used the same historical audit competition dataset as Zellic. Plain GPT-5 tested with prompt “perform extensive deep Solidity smart contract security analysis” from repository root, no special guidance. Wake Arena scans ran with standard configuration. Testing conducted November 2025.

Performance comparison

Wake Arena detected 43 out of 94 high-severity vulnerabilities, achieving the highest detection rate:

  • Wake Arena: 43/94 (45.7%)
  • Zellic’s automated security scanner V12: 41/94 (43.6%)
  • Plain GPT-5: 24/94 (25.5%)

2. Production audits

During November 2025, Wake Arena scans were integrated into Ackee Blockchain’s manual audit process for production protocols. Unlike isolated benchmark environments, production audits involve interconnected contracts, incomplete configurations, and the full severity spectrum from Critical to Informational.

Production audit results

Client Project Delivery Days Found by AI / All Found Critical High Medium Low Warning Info
Lido Stonks 2.0 Dec 2, 2025 15 4 / 17 0 / 0 0 / 0 1 / 1 1 / 2 1 / 5 1 / 9
Everstake ETH2 Batch Deposit Contract Nov 14, 2025 2 1 / 2 0 / 0 0 / 0 0 / 0 0 / 0 0 / 0 1 / 2
Printr Protocol Oct 1, 2025 32 21 / 60 5 / 10 0 / 4 1 / 5 4 / 10 4 / 15 7 / 16
Total 49 26 / 79 5 / 10 0 / 4 2 / 6 5 / 12 5 / 20 9 / 27

Key insight: Wake Arena identified 26/79 or 33% of all findings. Moreover, it discovered 5 critical and 5 unique vulnerabilities beyond those found by human auditors in the Printr audit.

Notes:

¹ Check Appendix C of the report for details on the Wake Arena findings.

Key Strengths

1. Cross-chain vulnerabilities

Wake Arena excels at finding complex cross-chain security issues through graph-driven reasoning:

  • Lend protocol: Cross-chain liquidation logic errors and multi-directional position handling
  • DODO: Cross-chain parameter validation and refund authorization issues

2. Access control and authorization

Deep contextual understanding identifies subtle permission bugs:

  • Crestal: Unauthenticated allowance drain
  • Virtuals: Permissionless validator registration
  • Mellow: Multisig threshold bypass via duplicate signers

3. Accounting and state management

Graph-driven analysis traces data dependencies through complex state updates:

  • Lend: Repeated reward claims due to missing state decrements
  • Notional: Incorrect netting logic causing accounting divergence
  • Mellow: Double-counting of staked vs. LP tokens

4. Protocol-specific logic errors

Multi-agent reasoning catches context-dependent vulnerabilities:

  • Munchables: Plot state management and dirty flag handling
  • TraitForge: Generation counter limits and airdrop attribution
  • Burve: Fee accrual checkpoint management across assets

Limitations

Wake Arena is a powerful tool for finding important vulnerabilities, but it has limitations:

What Wake Arena catches well:

  • Access control and authorization bugs
  • State management and accounting errors
  • Cross-chain and cross-contract logic issues
  • Reentrancy and callback vulnerabilities
  • Parameter validation and input handling

What Wake Arena may miss:

  • Novel cryptographic vulnerabilities requiring deep mathematical analysis
  • Protocol design flaws requiring extensive economic modeling
  • Extremely complex business logic spanning multiple contracts and off-chain systems
  • Zero-day attack vectors with no similar historical patterns

Ideal use case:

Use Wake Arena before your premium audit to:

  • Catch surface-level and mid-depth vulnerabilities early
  • Arrive at manual audit with cleaner code
  • Focus auditor time on deep protocol logic and design issues
  • Reduce overall security costs through early detection

Wake Arena complements, rather than replaces, high-quality manual audits of critical systems.


How to Use Wake Arena

Wake Arena is available now at ackee.xyz/wake/arena

Once you get access, scan your protocol in 3 steps:

  1. Upload codebase: Connect GitHub repository or upload files
  2. AI-driven analysis: A multi-agent system analyzes with graph-driven reasoning
  3. Receive report: Comprehensive PDF with findings, severity ratings, and remediation guidance

Pricing: Entry-level audit pricing with reports delivered in hours instead of months.

Foundation plans: Admin panels for grant programs to scan multiple projects under one subscription.


Appendix

Wake Arena Performance in Historical Audit Competitions

All findings below were discovered independently by Wake Arena with no special prompting or human assistance. Each protocol scan ran through the full AI-driven audit pipeline: compilation, graph-driven analysis, multi-agent reasoning, and report generation.

Basin (Code4rena, July 2024)

Wake Arena finds all 2 out of 2 high-severity issues reported by human researchers.

[H-01] Missing owner or role gating on upgrade endpoints enables permissionless upgrades

Functions upgradeTo and upgradeToAndCall rely solely on _authorizeUpgrade for gating, which enforces only environmental checks (delegatecall context, Aquifer mapping, and UUPS proxiableUUID) but does not restrict caller by owner or any role. Any external caller can invoke upgrade endpoints and change the implementation to any candidate that satisfies environment checks, bypassing governance.

[H-02] decodeWellData checks decimal0 twice, leaving decimal1 at 0 and mis-scaling token1 when 0 should default to 18

Function decodeWellData uses the same sentinel check twice for decimal0 and never checks decimal1 before defaulting to 18. When decimal1 is encoded as 0 to signal “default to 18”, it remains 0. Downstream scaling in getScaledReserves then multiplies token 1 by 10 ** (18 - 0) = 10 ** 18, mis-scaling reserves and corrupting pricing, reserve solves, and rate calculations.


Blackhole (May 2025)

Wake Arena finds all 2 out of 2 high-severity issues reported by human researchers.

[H-01] setRouter inverts the zero-address check, only allowing router = address(0)

Function setRouter inverts the zero-address guard, requiring the new router to be the zero address (require(_router == address(0), "ZA")). This prevents the owner from configuring any valid router. If router is ever zero, calls such as IGenesisPool(_genesisPool).launch(router, MATURITY_TIME) will use an invalid address, causing failures.

[H-02] createGauge permits untrusted _algebraEternalFarming, granting arbitrary ERC20 approval and enabling theft of factory-held reward tokens

Function createGauge is externally callable without modifiers and accepts caller-supplied farmingParam.algebraEternalFarming. It forwards this to the internal createEternalFarming, which unconditionally grants an ERC20 approval of 1e10 to the user-supplied _algebraEternalFarming before making an external call to it. Because _algebraEternalFarming is not validated against a trusted registry, any actor can point it to an arbitrary contract they control, allowing them to pull tokens from the factory via transferFrom using the granted allowance.


Burve (Sherlock, April 2025)

Wake Arena finds 2 out of 9 high-severity issues reported by human researchers.

[H-03] Incorrect netting in commit drops deposits when withdrawals exceed deposits

The commit netting branch incorrectly zeros assetsToDeposit before subtracting it from assetsToWithdraw when assetsToWithdraw > assetsToDeposit. As a result, no netting occurs: the full withdrawal executes while the pending deposit is dropped. Because deposit and withdraw already mutate internal share accounting assuming netting will occur, this creates unbacked shares and persistent accounting mismatch.

[H-06] Uninitialized return variable used as tax base in removeValueSingle enables zero-fee withdrawals and weakens slippage checks

Function removeValueSingle computes realTax from the return variable removedBalance before that variable is assigned. Because return variables in Solidity are zero-initialized, removedBalance equals 0 at the time of multiplication, so realTax is always 0. Users withdraw the full gross amount without paying the configured tax, the protocol accrues no earnings, and the minReceive slippage guard is evaluated against the gross amount instead of net-of-tax.


Crestal (Sherlock, March 2025)

Wake Arena finds the only high-severity issue reported by human researchers.

[H-01] Unauthenticated allowance drain via public payWithERC20

Function payWithERC20 in contract Payment is public and accepts arbitrary fromAddress and toAddress. It invokes token.safeTransferFrom(fromAddress, toAddress, amount) without authenticating the caller, binding fromAddress to msg.sender, or validating any signed authorization. Any user can trigger spending of any allowance that fromAddress has granted to this contract and redirect funds to an arbitrary toAddress.


DODO Cross-Chain DEX (Sherlock, June 2025)

Wake Arena finds 2 out of 5 high-severity issues reported by human researchers.

[H-04] Unbound params.fromToken in withdrawToNativeChain enables arbitrary token draining via DODO mixSwap

Unbound params.fromToken in withdrawToNativeChain allows the contract to approve and spend arbitrary tokens it holds through DODOApprove when executing _doMixSwap. The approval amount is derived from caller-supplied amount, while the token to approve is taken from caller-supplied params.fromToken. Since params.fromToken is not enforced to equal the zrc20 that was transferred in, any contract-held balance can be drained.

[H-05] Non-EVM refund theft via authorization bypass in claimRefund

The authorization in function claimRefund is incorrectly bound to msg.sender for non-EVM refunds. For refunds where refundInfo.walletAddress.length != 20, the code sets receiver = msg.sender and then checks require(bots[msg.sender] || msg.sender == receiver). Because receiver equals msg.sender by construction, the require always passes for any caller, enabling any address to claim non-EVM refunds.


Lambo.win (Code4rena, December 2024)

Wake Arena finds 2 out of 4 high-severity issues reported by human researchers.

[H-01] ERC20-mode cashIn mints by msg.value, enabling unbacked minting and zero-credit deposits

Function cashIn mints virtual tokens based on msg.value even when the underlying asset is an ERC20 token. In the ERC20 branch, the function transfers amount ERC20 tokens from the caller but mints msg.value virtual tokens, creating two failure modes: users depositing ERC20 with msg.value == 0 receive 0 virtual tokens while their ERC20 is locked, and attackers can send ETH with amount == 0 to receive unbacked virtual tokens they can cashOut to drain others’ ERC20 deposits.

[H-02] Front-run DoS by pre-creating Uniswap pair (PAIR_EXISTS) due to predictable next clone address

Function createLaunchPad deploys a new quote token clone using Clones.clone (via CREATE), then immediately calls Uniswap V2 createPair. Because CREATE-based addresses are derived from the factory’s address and its nonce, an observer can predict the next clone address. An attacker can front-run and pre-create the pair for (virtualLiquidityToken, predictedQuoteToken), causing the victim’s createLaunchPad to revert with PAIR_EXISTS.


Lend (Sherlock, June 2025)

Wake Arena finds 13 out of 28 high-severity issues reported by human researchers.

[H-01] claimLend fails to decrement lendAccrued after grant, allowing repeated reward claims and LEND drain

Function claimLend transfers accrued LEND but never reduces the recorded accrual in storage. After a successful transfer, lendAccrued(account) remains unchanged, allowing the same accrual to be claimed repeatedly whenever the router holds enough LEND. Storage-side distribution functions only ever increase lendAccrued; no path decrements or clears it after grants.

[H-03] borrowWithInterest reverts for legitimate multi-direction positions (both arrays populated)

Function borrowWithInterest enforces that only one of crossChainBorrows or crossChainCollaterals is populated for a given user and underlying on a chain. This invariant does not hold for legitimate multi-direction positions involving the same underlying in opposite directions across different remote chains, causing denial-of-service in repay and accounting flows.

[H-04] Collateral seized before repayment; LiquidationSuccess uses foreign lToken, bricking cross-chain liquidation

Collateral is seized on the collateral chain before repayment is secured, and the follow-up LiquidationSuccess handler repays using a foreign lToken address. On the debt chain, liquidateCrossChain computes seize tokens and sends CrossChainLiquidationExecute without first escrowing or pulling repayment funds from the liquidator. On the collateral chain, _handleLiquidationExecute immediately updates accounting and reduces the borrower’s collateral before any guarantee that repayment will succeed, breaking the cross-chain liquidation flow and permanently desynchronizing borrower state across chains.

[H-07] Redeem underpays by using pre-accrual exchangeRateStored; surplus underlying stranded in router

Function redeem computes expectedUnderlying using exchangeRateStored read before calling the market’s redeem. The redeem call accrues interest and uses post-accrual rate, so the router receives more underlying than it forwards to the user. The difference remains stuck in the router and accumulates over time.

[H-08] borrowWithInterest excludes destination-chain collaterals, zeroing cross-chain debt and blocking liquidation

Function borrowWithInterest fails to include cross-chain debt on destination chains because the collaterals branch erroneously requires both destEid == currentEid and srcEid == currentEid. On genuine cross-chain borrows originating on Chain A with debt on Chain B, destEid == currentEid but srcEid != currentEid, so the condition is always false. This incorrect zero balance corrupts downstream logic including liquidity calculations and liquidation limits.

[H-09] Supply credits lTokens using stale exchangeRateStored, causing accounting drift and redeem DoS

Supply uses exchangeRateStored from before mint and then credits mintTokens using the stale rate. The subsequent mint call accrues interest and uses post-accrual exchange rate, so actual lTokens minted to the router are fewer than the credited amount. This creates persistent accounting surplus that eventually causes redemption reverts.

[H-10] Non-atomic cross-chain liquidation seizes collateral before collecting repayment, enabling unpaid seizures

The liquidation protocol seizes collateral on the collateral chain first, then attempts to collect repayment from the liquidator on the debt chain. There is no escrow of the liquidator’s tokens prior to seizure, so if the liquidator cannot or will not pay, repayment fails while collateral has already been redistributed.

[H-18] Cross-chain debt undercount and repay DoS on destination chain in borrowWithInterest

Cross-chain debt becomes invisible on the destination chain because the borrowWithInterest collaterals branch uses an unsatisfiable predicate requiring both destEid and srcEid to equal currentEid. On the destination chain, cross-chain collateral records have destEid == currentEid and srcEid set to the origin chain, so srcEid == currentEid is always false and the loop never adds any amount. This causes under-accounting of debt in helpers like getHypotheticalAccountLiquidityCollateral and repay path DoS where CoreRouter.repayBorrowInternal requires borrowedAmount > 0 but borrowWithInterest incorrectly returns 0.

[H-19] Liquidation repayment uses collateral seizeTokens instead of repayAmount

After seizing collateral, the LiquidationSuccess handler repays the borrower using payload.amount, but that field was set to seizeTokens (collateral lToken units) during _executeLiquidationCore. Repayment must be performed using the repayAmount in the borrowed asset’s underlying units, causing incorrect debt settlement.

[H-21] Stale source-chain collateral snapshot enables undercollateralized cross-chain borrowing

The destination-chain borrow handler trusts a collateral value snapshotted on the source chain at send time. There is no lock on source-chain collateral while the message is in flight. The borrower can withdraw source-chain collateral after initiating cross-chain borrow but before destination execution, allowing undercollateralized or uncollateralized borrowing.

[H-22] Liquidation validity check uses wrong units and wrong action model, enabling seizure of healthy accounts

The liquidation validator _checkLiquidationValid models an additional borrow in the collateral market and passes payload.amount as the borrowAmount. In this flow, payload.amount is the number of lTokens to seize, not underlying-denominated borrow amount. This mixes units and incorrectly inflates the “borrowed” side of the solvency check, enabling liquidation of healthy accounts.

[H-25] LiquidationSuccess uses foreign lToken and wrong EIDs; repayment never executes after collateral seized

After seizing collateral on the collateral chain, the router sends a LiquidationSuccess message back to the debt chain. The receiving handler _handleLiquidationSuccess attempts to look up the borrow position and repay the debt using the collateral-chain destlToken and mismatched endpoint IDs. This causes the handler to fail to locate the record or revert when interacting with an unknown lToken, leaving repayment unexecuted while collateral has already been seized.

[H-27] First-time borrowers bypass per-user collateral checks in borrow due to zero borrowIndex path

In function borrow, the contract computes borrowed and collateral including the new amount, but then derives borrowAmount as zero when currentBorrow.borrowIndex == 0 (first-time borrowers). The per-user collateral check collateral >= borrowAmount becomes collateral >= 0, always passing and enabling new users to borrow against other users’ collateral held by the router.


Mellow (Sherlock, July 2025)

Wake Arena finds 2 out of 6 high-severity issues reported by human researchers.

[H-01] Threshold bypass: duplicate signer entries counted as distinct in checkSignatures

The Consensus multisig validator counts provided signatures against threshold but does not enforce that each signature is produced by a unique signer. The same signer can be repeated in the signatures array to satisfy any threshold, degrading a k-of-N policy to effectively 1-of-N as long as one registered signer is willing or compromised.

[H-04] Protocol fee double-accrual across non-base-asset reports because timestamp is only updated for base asset

A protocol-fee accrual checkpoint is stored per vault in timestamps. Function calculateFee unconditionally adds time-based protocol fees proportional to block.timestamp - timestamps[vault] for any asset, while function updateState advances the checkpoint only when asset == baseAsset[vault] and returns early otherwise. This allows the same elapsed period to be charged multiple times across different non-base-asset reports until a base-asset report finally updates the timestamp, systematically over-minting protocol fee shares.


Munchables (Code4rena, July 2024)

Wake Arena finds 4 out of 5 high-severity issues reported by human researchers.

[H-01] Missing plotId update on transfer causes stuck occupancy and event inconsistency

Function transferToUnoccupiedPlot updates occupancy bitmaps for the old and new plots but never updates the staked token’s toilerState.plotId field. As a result, the contract retains a stale plot id in storage while the plotOccupied mapping reflects the new plot. When the renter later calls unstakeMunchable, the function frees the old plot id (which is already empty), leaving the new plot permanently marked as occupied and preventing further rentals on that plot.

[H-02] Off-by-one in invalid-plot check allows farming on removed plots

The invalid plot detection in _farmPlots uses _getNumPlots(landlord) < _toiler.plotId instead of the correct >= comparison, missing the equality case. When a landlord reduces the number of plots, a toiler whose plotId equals the new plot count should be marked invalid, but the current code fails to detect it. As a result, staked tokens on the highest removed index continue farming as if the plot still existed, causing accounting errors for both renter and landlord.

[H-04] Time-delta underflow in _farmPlots when using landlord lastUpdated on invalid plot causes revert and blocks user actions

When a staked token’s plotId exceeds the landlord’s available plots, _farmPlots substitutes timestamp = plotMetadata[landlord].lastUpdated and then computes the farming delta as timestamp - _toiler.lastToilDate. If plotMetadata[landlord].lastUpdated is zero (never initialized) or earlier than _toiler.lastToilDate, the subtraction underflows under Solidity 0.8 and reverts the entire transaction. Because _farmPlots is executed by the forceFarmPlots modifier on key functions (stakeMunchable, unstakeMunchable, transferToUnoccupiedPlot), this revert prevents users from farming, moving to a valid plot, or even unstaking—effectively locking assets until metadata is updated.

[H-05] Dirty flag in _farmPlots is never cleared, permanently disabling farming for affected tokens

When a staked token’s plotId is no longer valid (available plots shrank), _farmPlots sets toilerState[tokenId].dirty = true to denote a one-time adjustment. On subsequent calls, the check if (_toiler.dirty) continue; skips farming entirely for that token. There is no code path that clears the dirty flag after the adjustment, including in transferToUnoccupiedPlot, so the token will never accrue schnibbles again unless the owner fully unstakes and restakes it.


Notional Exponent (Sherlock, July 2025)

Wake Arena finds 2 out of 11 high-severity issues reported by human researchers.

[H-03] Off-by-one in batch range: includes previous batchId, enabling cross-request asset misattribution

Functions _finalizeWithdrawImpl and canFinalizeWithdrawRequest iterate from initialBatchId to finalBatchId inclusive. However, initialBatchId is captured before initiateRedemption, and finalBatchId after; if batchId increments during initiation, the upxETH for the current request lies in (initialBatchId + 1 .. finalBatchId). Including initialBatchId sweeps balances from the prior batch, causing the current request to redeem upxETH belonging to an earlier request.

[H-06] Withdrawal initiation DoS after ~65k requests due to 16-bit nonce overflow in s_batchNonce

The withdraw request identifier packs a 16-bit s_batchNonce in the high bits and uses ++s_batchNonce during initiation. In Solidity 0.8+, arithmetic on uint16 is checked; once the counter reaches 65535, the next increment reverts, permanently denying further withdrawal initiation through this manager.


Phi (Code4rena, October 2024)

Wake Arena finds 4 out of 7 high-severity issues reported by human researchers.

[H-01] Art creation signature lacks domain separation, enabling cross-chain replay

The createArt authorization flow verifies a personal-signature over (uint256 expiresIn, string uri, bytes credData) without binding to block.chainid or address(this). A valid art-creation signature for Chain A can be replayed on Chain B if the same phiSignerAddress is configured, creating unintended duplicate art across chains and bypassing per-chain rollout policies.

[H-02] createArt signatures do not bind CreateConfig, allowing parameter hijack and revenue redirection

The factory signature only covers (expiresIn, uri, credData) and does not bind CreateConfig fields or the caller. Any party with a valid signed payload can front-run and submit createArt with arbitrary artist, receiver, mintFee, and timing parameters. The attacker gains persistent control via onlyArtCreator modifier and redirects revenue. The first creation also fixes the per-cred ERC1155 contract address permanently.

[H-05] Public helper _addCredIdPerAddress allows arbitrary mutation of per-user position metadata (griefing/DoS)

Function _addCredIdPerAddress is declared public with no access control and accepts an arbitrary sender_ address. Any external caller can append arbitrary credId_ values into another account’s _credIdsPerAddress array and overwrite its stored index mapping. An attacker can bloat a victim’s positions array with duplicate or nonexistent entries, skew the index mapping, and degrade pagination and view results in getPositionsForCurator, while also setting up inconsistent state that increases the likelihood of removal failures.

[H-05] Public _removeCredIdPerAddress enables unauthorized deletion and sell-to-zero DoS

Function _removeCredIdPerAddress is declared public with no access control. Any external caller can remove any credId from any address’s positions array, desynchronizing _credIdsPerAddress with its index mappings. When the legitimate user later sells to zero, the contract reverts due to mismatched indices, permanently blocking sell-to-zero for that position.

[H-06] Reentrancy via refund in _handleTrade buy path allows sell-lock bypass

The single-trade flow updates lastTradeTimestamp[credId_][curator_] only after refunding excessPayment via external call. A malicious contract can reenter during the refund and immediately call sellShareCred before the timestamp is updated. The lock check uses the stale timestamp, allowing immediate sell after buy and bypassing the cooldown period. The function lacks nonReentrant guard.


Superfluid (Sherlock, June 2025)

Wake Arena finds 1 out of 2 high-severity issues reported by human researchers.

[H-01] provideLiquidity can spend staked tokens (no available-balance check), causing double counting and accounting breakage

Function provideLiquidity does not enforce that supAmount is bounded by the locker’s available (non-staked) balance. Staking only updates internal _stakedBalance while tokens remain in this contract’s address, so Uniswap’s position manager can pull staked tokens when minting an LP position. This violates the invariant FLUID.balanceOf(this) >= _stakedBalance, enables double counting (same tokens accrue staking and LP rewards), and can later break accounting and unlock flows.


TraitForge (Code4rena, July 2024)

Wake Arena finds 2 out of 6 high-severity issues reported by human researchers.

[H-01] Batch mint loop uses global _tokenIds, blocking mintWithBudget after generation-1 cap

The mintWithBudget while-condition compares global token counter _tokenIds against per-generation limit maxTokensPerGen. Because _tokenIds is monotonically increasing across all generations, after the first generation mints maxTokensPerGen tokens, the condition _tokenIds < maxTokensPerGen becomes false forever, preventing any further batch mints in subsequent generations.

[H-02] Burn before airdrop start lets current holder reduce the initial minter’s airdrop allocation

In burn(uint256 tokenId), while the airdrop has not started, the contract subtracts entropy from initialOwners[tokenId]. initialOwners is set at mint time and never updated on transfer. Any current holder or approved operator can burn the token before airdrop starts and force a deduction from the original minter’s airdrop allocation.


Virtuals (Code4rena, April 2025)

Wake Arena finds 4 out of 6 high-severity issues reported by human researchers.

[H-01] Permissionless validator registration enables sybil set inflation, base score manipulation, and gas-based DoS

Function addValidator is publicly callable and lacks access control, allowing any account to register an arbitrary validator for any virtualId. On call, addValidator unconditionally invokes _addValidator and _initValidatorScore, which appends to the per-virtual validator array and assigns a non-zero base score tied to the DAO’s proposal count. This enables sybil inflation of the validator set, upward manipulation of aggregated validator scoring via the non-zero base score, and unbounded growth of the _validators[virtualId] array—increasing gas costs in consumer loops like totalUptimeScore with realistic out-of-gas reverts in downstream flows.

[H-03] Unbound virtualId in updateImpact enables cross-persona impact and dataset score manipulation

Function updateImpact accepts caller-controlled virtualId and proposalId and computes the baseline service using _coreServices[virtualId][_cores[proposalId]], with no binding between the two parameters. The contract does not persist any mapping from proposalId to its originating persona, so any caller can pair a victim proposalId with an unrelated virtualId to compare maturity against the wrong persona’s last service or against zero. This directly changes _impacts[proposalId] and, when the proposal has an associated dataset, also rewrites _impacts[datasetId] and _maturities[datasetId], corrupting cross-persona scoring.

[H-04] Unchecked parentId enables cross-DAO lineage forgery and unbounded children growth

The mint function accepts an arbitrary parentId and only enforces parentId != proposalId, then writes the linkage and appends to the parent’s children without validating that the parent exists, belongs to the same virtual persona, or that the caller is authorized to modify the parent’s lineage. This allows any proposer to forge cross-DAO ancestry, attach children to nonexistent parents, and bloat _children[parentId] for griefing. An attacker can make a token from DAO A appear as a child of a token minted by DAO B, or spam a popular parent with thousands of children, breaking assumptions of downstream consumers and making getChildren(parentId) heavy for indexers.

[H-06] promptMulti can transfer to zero address or stale TBA because prevAgentId is never updated

The promptMulti loop initializes prevAgentId to 0 and agentTba to address(0) but never updates prevAgentId inside the loop. The refresh condition if (prevAgentId != agentId) therefore fails when agentId == 0, leaving agentTba unchanged. For the first element where agentId == 0, agentTba remains address(0) and token.safeTransferFrom(sender, agentTba, amounts[i]) attempts a transfer to the zero address, causing standard ERC20 tokens to revert. For any later element where agentId == 0 follows a non-zero agentId, the stale agentTba from the previous agent is reused, misdirecting funds to the wrong recipient.


Acknowledgments

We thank Lido, Printr, Everstake, and LUKSO for participating in the production validation of Wake Arena.


Access

Wake Arena: ackee.xyz/wake/arena

Request access


Ackee Blockchain Security