Skip to content
Security

24 findings in code that had never been compiled

A security page that lists only resolved issues tells you nothing about whether anybody actually looked. Below is the complete output of our internal audit as of Sep 1, 2026 — all 24 findings, including two that would have caused unrecoverable loss of funds, each with the mechanism that made it possible and the test that now prevents it.

Findings fixed
24
Findings open
0
None outstanding
Contract tests
97/97
All passing
Third-party audit
Pending
Gate before mainnet

An internal review is not a third-party audit

Every finding below was found, reproduced, fixed and pinned by the people who wrote the code. That is genuinely useful, and it has a hard ceiling: an internal review reliably finds the bugs its authors were capable of imagining. No external security firm has examined this code, and that stays a gate before mainnet rather than something to arrange afterwards.

The two findings that would have caused unrecoverable loss of funds — a buyback contract that performed no swap while reporting that it had, and a yield vault that redeemed for zero — are described in full below. Both are closed.

Review scope

Reviewer
Internal review (no third-party audit completed)
Date
Sep 1, 2026
In scope
Whole protocol — token, vesting, buyback, airdrop distributor, treasury, the vaults, strategy executor, reputation registry, oracle, both deploy pipelines, the off-chain airdrop generator, and the front-end contract integration
Static analysis
Slither — 130 results, none at high or medium severity
Third-party audit
Not started
Mainnet deployment
None

What we will not claim

  • That an internal review substitutes for a third-party audit. It does not.
  • That a passing test suite proves the absence of bugs. It proves the presence of tests.
  • That closing 24 findings means there is no next one nobody has found.
  • That any of this predicts a price outcome for the token.
Nothing outstanding

No open findings

This section stays on the page. When a finding is open it appears here with the same detail as a resolved one — otherwise publishing findings at all would mean very little.

The last items outstanding were the five vault-layer findings, closed alongside two further issues uncovered during that work: a reputation flag a manager could mint for themselves, and rotation-vault receipts that did not actually commit to the basket they described. Both are below.

24 resolved

Every finding

Each was found, reproduced with a failing test where possible, fixed, and pinned with a regression test so it cannot come back quietly. Ordered by severity.

C-01criticaltokenfixed

The buyback contract never bought anything back

Impact

execute() checked its USDC balance, transferred whatever token balance it already held to a dead address, and emitted BuybackExecuted with usdcSpent set to its entire USDC balance. No swap was ever performed. Every dashboard reading that event would have reported large, entirely fictional buyback volume, and because rescueToken explicitly refused to move USDC and no other exit existed, all fee revenue reaching the contract was permanently unrecoverable.

Resolution

Rewritten to route through a swap adapter with a caller-supplied minimum output. Both legs are measured as balance deltas across the swap rather than read from the adapter return value, so a malicious router cannot over-report a burn. Tokens are burned via the token burn function, so totalSupply actually falls. A timelocked withdrawUsdc path guarantees fee revenue can never be stranded. Six regression tests, including one driven by a router that takes the USDC and delivers nothing.

C-02criticaltokenfixed

Deploy script left the entire supply in the deploy key

Impact

The pipeline minted all 1,000,000,000 tokens to the deploying EOA and never moved them. It also never deployed the vesting contract or the airdrop distributor at all, despite the app expecting both. Launching from that script would have put 100% of supply in a single hot key with no distribution, no vesting, and no airdrop.

Resolution

Replaced with a token-layer script that distributes the whole supply atomically in the deploy transaction and then asserts, as launch-blocking requires, that the deployer holds exactly zero, that the buckets sum to max supply, and that every privileged role has moved to governance.

V-01criticalvaultfixed

Yield vault valued shares against an adapter balance it never funded

Impact

The vault reported totalAssets as its yield adapter balance but never overrode the ERC-4626 deposit and withdraw hooks, so deposited funds stayed on the vault and the adapter balance stayed zero. Share price read as zero: a depositor burned every share and received nothing while their principal sat stranded in the contract. It also inflated share issuance for the next depositor, diluting the first.

Resolution

Both hooks now route capital through the adapter — deposits forward in, withdrawals recall first. Switching adapters migrates the position in the same transaction rather than stranding it, and a claimFees path was added so accrued fees are actually payable instead of permanently depressing NAV for nobody. Pinned by a fuzz test asserting a deposit-then-redeem round trip is whole to within one wei, plus an invariant that everything totalAssets counts is held where it is counted.

C-03hightokenfixed

Treasury and buyback were owned by the deploy key, contradicting the published role matrix

Impact

Both contracts were constructed with Ownable(msg.sender) and ownership was never transferred, so the deployer EOA retained them. The security documentation stated both were held by the governance Safe. The treasury rescue function can move any token balance to an arbitrary address, so the documented and actual trust models differed on a function that can drain all protocol fees.

Resolution

Both are now owned by the Timelock from the deploy transaction, and the script refuses to run if governance is unset or equal to the deployer. Ownership handover is asserted before the script reports success.

H-01hightokenfixed

A mint function that could never succeed, with tests asserting that it did

Impact

The token exposed mintForTestnet, but the constructor already minted the full cap and the ERC20Votes supply ceiling was pinned to that same cap, so any call reverted with ERC20ExceededSafeSupply. Three tests asserted the opposite. Those tests had never been executed — the suite did not compile — which meant the whole test suite was decorative. After any burn the function also became genuinely callable on the testnet chain id, letting burned supply be re-minted.

Resolution

The function is removed. The token now has no mint entrypoint whatsoever, pinned by a test that asserts both mintForTestnet and mint are absent from the ABI.

H-02hightoolingfixed

The repository did not compile

Impact

Production source was missing an IERC20Metadata import in the yield vault and a SafeERC20 using-directive in the swap adapter. Two test files used the wrong relative import depth, and a registry test read a public mapping getter as a struct. Nothing in the project had ever been built or tested, so no finding below this line could have been caught by CI.

Resolution

All five compilation errors fixed. The project now builds clean and the full suite executes.

H-03hightokenfixed

Vesting treated the cliff as additive with the vesting term

Impact

Vesting accrued linearly over cliff plus vestDuration rather than over vestDuration measured from the start. A schedule described as "12 month cliff, 48 month vest" actually ran 60 months and released 20% at the cliff instead of 25%. Every contributor and backer schedule would have silently paid out on the wrong curve and finished a year late.

Resolution

Accrual is now linear over vestDuration from startTime, with the cliff gating the first release. A regression test pins the exact cliff fraction, the midpoint, and the end date.

V-02highvaultfixed

The signed-rebalance path could not execute at all

Impact

The sliding rate-limit window computed its cutoff as block.timestamp minus one day, which underflows on any chain whose timestamp is below 86400 — precisely the state a fresh Foundry or Anvil instance starts in. Every rate-limited rebalance reverted with an arithmetic panic, so the EIP-712 mechanism the entire protocol is built around was never once exercised by a test.

Resolution

The cutoff is clamped at zero. The window is now compacted in place rather than deleted and re-pushed, which halves the storage writes, and the signature is verified before any storage is touched so a forged command cannot make the protocol pay for the compaction. All nine executor tests pass.

V-03highvaultfixed

Reputation registry reported disputes as unchallenged

Impact

A challenge wrote only to the history array, while the portal reads getLatest — which returned a separate duplicated copy that challenges never touched. The two diverged the moment anything was disputed, so an overturned commitment kept displaying as unchallenged.

Resolution

The duplicated latest mapping is removed entirely and getLatest now derives from history, so the two cannot disagree. Thirteen registry tests cover publish, dispute, arbitration and window expiry.

V-06highvaultfixed

A manager could mint their own "verified" badge

Impact

A challenge was resolved by comparing the stored commitment against a hash the challenger supplied, and a match set upheld to true. So a manager could publish any figures at all, immediately self-challenge quoting that same hash, and be recorded as upheld — while the already-challenged guard then permanently blocked a genuine challenge from anyone else. The portal renders that flag as a green Upheld badge, so the worst case was a fabricated track record displaying as independently verified.

Resolution

Only a mismatch is a dispute; a matching counter-commitment now reverts and leaves the window open. Upheld can only be set by a governance arbiter through a separate resolveChallenge call, because deciding which of two off-chain computations is correct is not something a hash comparison between the disputing parties can settle. The specific self-minting attack is pinned by a test.

M-01mediumfrontendfixed

Every contract address silently resolved to the zero address in the browser

Impact

The address registry read environment variables through a helper as process.env[name] with a variable key. Next.js only inlines statically analysable process.env member expressions, so nothing was substituted in the client bundle and every address fell back to 0x000…000. The token and airdrop pages would have read state from the zero address in production while working correctly in development, where a real process.env exists.

Resolution

Every lookup is now a literal process.env.NEXT_PUBLIC_* reference, addresses are format-validated, and the portal renders an explicit banner naming any contract that is unconfigured rather than showing a confident zero.

M-02mediumtokenfixed

Documentation and marketing denied a capability the token has

Impact

The token extends ERC20Votes and carries real checkpointed voting weight, while the security document stated "V1 has no voting token" and the token page listed "not a governance token" as a feature. Shipping a public site that understates what a token can do is a disclosure problem, not a copy problem.

Resolution

Voting is kept and described accurately: weight exists and is queryable today, no Governor is deployed, and the governance page states plainly that there is currently no on-chain venue for a proposal.

M-03mediumvaultfixed

The price oracle was deployed permanently unable to reach quorum

Impact

The median oracle was constructed requiring three fresh reports but only one updater key was ever added, so latestRoundData reverted with InsufficientFreshReports on every call. Any vault falling back to it was bricked from the moment it was deployed.

Resolution

Quorum is derived from the actual updater set, the script asserts it is satisfiable before finishing, and it prints an explicit instruction to seat a real multi-key updater set before accepting deposits.

M-04mediumtoolingfixed

The deploy script reverted in every production configuration

Impact

The vault factory grants its deploy role only to the constructor admin. The script passed governance as that admin and then called deploySpotVault, setSwapAdapter and grantRole from the deployer key. Whenever governance was a multisig — that is, every real deployment — the run failed partway through with an access-control revert, after the token had already been deployed.

Resolution

The factory and vaults are deployed with the deployer as temporary admin, wired, then handed to governance with the deployer renouncing. The script asserts no deployer authority survives.

M-05mediumtokenfixed

Voting checkpoints were keyed to block numbers on a chain with no block-time guarantee

Impact

ERC20Votes defaults to a block-number clock. Robinhood Chain does not guarantee a stable block interval, so any future governance period expressed in blocks would drift in wall-clock terms — a three day vote could quietly become five.

Resolution

The token overrides clock() and CLOCK_MODE() to report timestamps, per ERC-6372.

M-06mediumtokenfixed

Vesting accepted schedules that were already fully vested, and used quadratic gas

Impact

The funding function did not validate the start time, so a backdated schedule could be created already claimable in full. It also detected duplicate beneficiaries inside a batch with a nested loop, making gas grow quadratically with batch size.

Resolution

Start times are bounded by a maximum backdate, cliffs are required not to exceed the vesting term, and the duplicate check is now a single storage read per entry.

M-07mediumtokenfixed

The buyback had no slippage protection

Impact

Separately from the buyback performing no swap at all, the execute function took no minimum-output parameter and would have accepted whatever the venue returned. A permissionless, fully predictable market buy of the contract entire balance, with no bound on the price paid, is a standing invitation to sandwich the protocol treasury.

Resolution

The caller now supplies a minimum output, enforced against the measured balance delta rather than the adapter return value. Pinned by a test that moves the price against the buyback mid-flight.

V-04mediumvaultfixed

Spot vault circuit breaker and fee accrual were never actually tested

Impact

Both tests failed with an access-control revert before reaching a single assertion, because the suite never granted the risk-council or keeper roles. The circuit breaker and the fee logic were completely unverified while appearing to be covered.

Resolution

Roles are wired correctly, and the keeper role is deliberately withheld from the test contract so the negative authorisation test still means something. Ten spot vault tests pass.

V-05mediumvaultfixed

The invariant suite passed without exercising anything

Impact

The run reported both invariants holding across 128,000 calls, but all 64,010 rebalance calls reverted — the handler pranked as an address holding no keeper role. The invariants were satisfied vacuously while reporting green, which is worse than having no invariant test because it manufactures confidence. The two assertions were also tautologies: one checked that an unsigned integer was at least zero.

Resolution

Rewritten with seven real invariants — fee solvency, full backing of outstanding shares, net never exceeding gross, receipt count matching executed rebalances, and no value created from nothing. The handler is wired so calls land, and an afterInvariant coverage floor fails the run outright if no deposit or rebalance ever succeeded. Revert rate went from 100% to under 10%.

V-07mediumvaultfixed

Rotation vault receipts did not commit to the basket

Impact

The rotation vault reused the single-asset commitment helper, passing rebalanceCount modulo 65536 as the target weight and the constant 10000 weight checksum as the cash leg. The resulting hash therefore bound neither the basket weights nor the per-token legs — exactly the fields a rotation receipt exists to attest. Two rebalances into completely different baskets could hash identically.

Resolution

A dedicated basketCommitment binds the full weight array and every token leg, using abi.encode rather than encodePacked so two dynamic arrays cannot collide. Tests assert that reordering either the weights or the legs changes the hash.

V-08mediumvaultfixed

The rotation vault charged none of its advertised performance fee

Impact

The vault stored a performance fee, a high-water mark and an accrual slot, and the deploy script configured 20% — but there was no evaluateFees and no claimFees anywhere in the contract. The accrual slot was never written, so the protocol earned nothing from this vault while the site advertised a 20% performance fee. Slither surfaced it by flagging the accrual as assignable-to-constant, which is what a never-written storage slot looks like from the outside.

Resolution

Both functions implemented, mirroring the spot vault: charged only on gains above the high-water mark, clamped so the accrual can never exceed what the vault holds, and payable in the base asset. Five tests cover no-accrual below the high-water mark, accrual on a new high, no double-billing at the same high, keeper gating, and the empty-claim revert.

L-01lowtokenfixed

Revoking a vesting schedule emitted no event

Impact

Revocation moved tokens and permanently altered a beneficiary schedule with no log, so indexers and observers could not detect it. For a protocol whose entire pitch is a verifiable record, an unlogged privileged action is a gap in the record.

Resolution

A Revoked event reports the vested amount kept and the unvested amount returned. Schedule creation is now logged too.

L-02lowfrontendfixed

An unconfigured backend crashed the build instead of degrading

Impact

The data module was marked client-only yet imported by server components, and it constructed a Supabase client at import time with empty credentials, which throws. A deployment without backend environment variables failed during prerender rather than rendering the marketing site.

Resolution

The client is created lazily, the module is server-safe, and every query degrades to an empty result with an explicit empty state.

L-03infotoolingfixed

via_ir silently invalidates time-based tests

Impact

foundry.toml enables via_ir, and the IR optimiser rematerialises the TIMESTAMP opcode at each use rather than caching it. A local variable holding block.timestamp therefore picks up the post-warp value, so a past-lookup assertion can pass or fail for reasons unrelated to the contract.

Resolution

Time-based tests use literal timepoints, with the reason documented inline so the pattern is not reintroduced.

Trust model

What a compromised key can do

The useful question is not whether keys can be compromised, but what the worst outcome is when one is.

A manager key

Can

Request rebalances within the vault’s preset weight bounds, slippage cap and daily rate limit.

Cannot

Withdraw funds, change the mandate, raise the fee, change the oracle, or touch any other vault.

The governance Safe

Can

Queue any privileged action, create and revoke vesting schedules, seat oracle updaters.

Cannot

Mint tokens, execute a queued action before the 48-hour delay elapses, or take vested tokens back from a beneficiary.

The Timelock

Can

Change the buyback swap route, withdraw undeployed fee revenue, pay out the insurance fund, sweep unclaimed airdrop after the deadline.

Cannot

Act instantly. Every action is visible on-chain for two days before it can execute.

The deploy key

Can

Nothing after deployment. It holds zero tokens and zero roles, and the deploy script asserts both before reporting success.

Cannot

Anything at all. This is the point of the assertions.

Found something we missed? Security reports are the one kind of message we always want. Contact details and the disclosure policy live in the protocol repository under docs/SECURITY.md. This page covers 24 findings in total.