BasketToken is a single-contract, non-upgradeable, in-kind mint/redeem vault.
The design goal is to make the contract's own attack surface as close to zero
as possible and to be explicit about the risks that remain.
| Property | Mechanism |
|---|---|
| Full backing | Mint pulls ceil(amount × units / 1e18) per constituent and verifies the actual balance delta; redeem pays floor(...). All rounding favors the vault. Invariant-tested (test/BasketToken.invariant.t.sol). |
| Redeem can never be blocked by the protocol | redeem has no pause, no cap, no guardian gate — verified by a dedicated test that redeems while minting is paused and the cap is below supply. |
| No admin control over funds | The guardian can only pause minting, move the supply cap (≤ immutable maxSupplyCap) and change the fee recipient. There is no withdraw, sweep, upgrade, delegatecall or selfdestruct path. |
| Reentrancy | nonReentrant on mint/redeem; attack mocks covering all four re-entry combinations. |
| Non-standard tokens | Balance-delta check rejects fee-on-transfer / lying constituents at mint time. |
- Issuer risk (dominant risk). Constituents are Robinhood Stock Tokens —
debt instruments of Robinhood Assets (Jersey) Ltd. The issuer can pause or
freeze the underlying tokens. If any constituent's transfers are frozen,
redeemof the whole basket reverts until the freeze is lifted, because a redeem is all-or-nothing by design (no partial-redeem escape hatch exists — deliberately, to keep the contract minimal). Supply caps exist to bound exposure to this risk. - Unaudited code. The contract is small (~160 nSLOC) and heavily tested, but has not undergone a professional audit. Supply caps are the mitigation.
- Weight drift. Units are fixed at deploy; weights drift with prices. This is documented product behavior, not a vulnerability.
The curation platform wraps the unchanged BasketToken core:
VimenToken— fixed 100M supply, minted once, no owner/mint/hooks.CuratorRegistry— staking pools (license + delegation), MultiRewards-style fee accounting, 7-day unstake cooldown. No admin; the splitter link is a one-shot deployer-only init, frozen afterwards.FeeSplitter—feeRecipientof every factory basket; 60/40 split are constants. Permissionlessdistribute.BasketFactory— permissionless creation gated on the stake license; wires every basket withfeeRecipient = splitterandguardian = protocol Safe, so curators can neither redirect fees nor touch caps.
Trust properties: after deployment wiring there is no privileged function in
the platform. Reward accounting rounds down; dust bound is
totalStake/1e18 wei per distribution plus 1 wei per staker settlement
(fuzz-tested). A curator unstaking below the threshold only loses the ability
to publish new baskets — existing baskets are immutable and unaffected.
Commission changes are timelocked (resolved former phase-1 limitation):
setCommission only announces a change (CommissionAnnounced with its
effectiveAt); it activates after COMMISSION_TIMELOCK (7 days, aligned
with UNSTAKE_COOLDOWN), so a delegator who exits at the announcement is out
before the new rate applies. commissionBps reflects a matured announcement
lazily — no keeper needed — and re-announcing before maturity restarts the
clock. The old grief (raise to the 50% cap and call the permissionless
distribute in the same transaction, capturing up to half of pending pool
fees with no notice) is closed: fees distributed inside the window still pay
the old rate.
Coverage: 100% lines/statements/branches/functions across all five contracts
(107 tests). Additional accepted Slither findings: timestamp comparison in
the cooldown (standard), benign == 0 early-return in distribute.
Note: via_ir = true is now enabled — the 9-argument BasketToken
constructor call in BasketFactory exceeds legacy-codegen stack depth
(NFR-1's "unless needed" clause). The full suite passes under via-IR.
- FR-2: constructor takes an extra final parameter
initialSupplyCap(validated0 < initialSupplyCap <= maxSupplyCap). The spec had the deploy script callsetSupplyCappost-deploy, but that call is guardian-gated and the guardian is a Safe, not the deploy key — the call would revert. Setting the initial cap in the constructor removes the extra transaction and the window where the cap is unset. Approved by the project owner on 2026-07-10.
Zero findings of any severity in src/BasketToken.sol other than:
calls-loop(Low):mint/redeem/isFullyBackedcall constituent ERC-20s in a loop. Accepted — inherent to an in-kind multi-token vault; the constituent set is bounded (2–20) and fixed at deploy.cyclomatic-complexity(Informational): constructor validation ladder. Accepted — it is a sequence of independent input checks.- The reported High (
incorrect-exp) and Medium (divide-before-multiply) findings are all inside OpenZeppelinMath.mulDiv(v5.6.1) — well-known false positives on the Remco Bloemen mulDiv implementation (the^is an intentional XOR in the Newton–Raphson inverse). Not our code; unmodified audited library.
2 High, both triaged as false positives / intentional:
- H-1 "state change after external call" (BasketFactory.createBasket,
CuratorRegistry.stake) — the external calls are to protocol-owned contracts
wired at deployment (registry/splitter) or are the deliberate balance-delta
read pattern that rejects fee-on-transfer VIMEN; every flagged function is
nonReentrant. Accepted. - H-2 unsafe int cast (
IUniswapV4.solBalanceDelta unpacking) — the truncation to the low 128 bits is the defined encoding of Uniswap v4'sBalanceDelta(two int128s packed in an int256); the cast is the decoder. Accepted.
Low findings (costly-op/require in loop, large literals 10_000 bps,
state-change-without-event on internal accounting, "could be immutable" on
guardian-settable fields, unchecked return on the quoter's revert-trick call,
unused error) — same rationales as the Slither triage above; all accepted.
Full report: contracts/aderyn-report.md.
No formal bug bounty yet. Please report privately via GitHub private vulnerability reporting before any public disclosure. Good-faith reports will be acknowledged within 48 hours, and rewards for critical findings will be negotiated case by case.