This document enumerates realistic attack/griefing scenarios for the on-chain core and describes mitigations.
Goal:
Attackers cannot extract unbounded value or destabilize the protocol.
Attack
- No one calls
StakeEngine.updatePost(postId), so snapshots never run and the economic game stalls for that post.
Mitigations
- Backend keepers periodically update popular posts.
- UI-driven updates: frontends call
updatePost(or rely on stake/withdraw triggers, which call_maybeSnapshotinternally) before showing balances. - View functions (
getPostTotals,getUserStake,getUserLotInfo) project unrealized gains/losses without writing state, so reads are always current even if no snapshot has run. - Optional on-chain incentive (future): pay the caller a small fee.
Attack
- An attacker repeatedly calls
updatePostto waste gas and spam events.
Mitigation
_forceSnapshotearly-returns whencurrentEpoch <= lastSnapshotEpoch, so repeated calls within the same epoch are cheap (only a small read + early return). The attacker still pays gas for the call but cannot trigger work._maybeSnapshot(called from stake/withdraw paths) only triggers a full snapshot whensnapshotPeriodworth of epochs have elapsed.
Attack (intent)
- Stake 1 token 100 times instead of 100 once, hoping to occupy multiple early positions.
Mitigation (current implementation)
- The StakeEngine consolidates: each user holds at most one lot per (post, side). Repeated
stake()calls by the same user on the same side merge into the existing lot. The lot'sweightedPositionis updated as the stake-weighted average of the prior position and the new entry position. - This prevents the splitting attack at the contract level. The user's effective position cannot be better than a single weighted-average position. Adding stake later actually drags the average toward the back of the queue.
Attack
- A user spreads stake across N wallets to occupy N distinct early lots.
Mitigations
- Application-layer sybil resistance (optional KYC, web-of-trust badges) where appropriate.
- The economic incentive is bounded by the size of the per-side budget; N small lots collectively earn the same total as one consolidated stake of equal size on the same side, modulo the position-weight curve. The attack chiefly improves the position rather than the budget.
- Future hardening: per-staker aggregation across wallets (requires identity), or minimum lot size to raise the per-wallet cost.
Attack
- Create hundreds of intermediate claims linked into a target claim to amplify its effective VS.
Mitigations
- Stake-weighted contribution distribution (Section 4.4 of the whitepaper, "Conservation of Influence"): a parent's mass is divided across all its outgoing links, so creating more links from one parent dilutes each link's share rather than multiplying influence.
- Posting fees on every claim and link impose a non-trivial cost per node.
- Stake on the link itself is required to make it active; the activity threshold gates spam.
- Bounded fan-in: the ScoreEngine processes at most
maxIncomingEdgesper claim (default 64, governance-configurable). Edges beyond the cap contribute zero. This bounds gas regardless of how many links an attacker creates. - The credibility gate ensures discredited parents and challenged links contribute nothing.
- Effective VS is clamped to
[-1, +1].
Attack
- Build deep chains A→B→C→... to try to "boost" far downstream.
Mitigations
- Stake-weighted distribution attenuates each hop: a parent's mass is bounded by
parentVS × parentTotalStake, and the share that flows through any one outgoing link is at mostlinkStake / sumOutgoingLinkStake. - The credibility gate truncates chains at any node with non-positive effective VS.
- The ScoreEngine's
MAX_DEPTH = 32hard cap returns zero from any branch deeper than 32 hops. - The
maxOutgoingLinkscap (default 64) bounds the share-denominator computation per parent; combined withmaxIncomingEdges, total edge work pereffectiveVSRaycall is bounded.
Attack
- Create a cycle (A links to B, B links to A) and attempt to inflate one or both via mutual reinforcement.
Mitigations
- Cycle detection at compute time: when the ScoreEngine recurses, any post already on the recursion stack contributes zero for the cycled path. A claim cannot influence its own VS through any chain of links.
- Cycles are not enforced absent at the storage layer (
LinkGraphpermits them); safety is entirely at the ScoreEngine level. - The credibility gate further stabilizes cycles by silencing any leg whose VS is non-positive.
Attack
- Attempt to capture reward with minimal exposure.
Mitigation
- Per-snapshot delta is proportional to elapsed time and to
(amount × positionWeight). A new lot enters at the back of the queue (weightedPosition = sideTotalat entry), so itsposWeight = 1 - (sideTotal / sideTotal) = 0immediately after staking. Such a lot earns ~0 from its first snapshot. - Snapshots run at most once per
snapshotPeriod; very short windows yield no incremental rewards.
Attack
- A user on the losing side withdraws right before a snapshot to avoid the burn.
Mitigation
- The withdraw path calls
_maybeSnapshotfirst, which forces a snapshot if the snapshot period has elapsed. The user thus realizes their losses up to the most recent snapshot before being able to withdraw what remains. - Between snapshots, view-projected balances are smaller for losing positions, but the actual withdrawable amount is the stored lot amount; this is conservative for the protocol (the user can withdraw projected losses they have not yet realized, but they also forfeit any further potential growth).
Attack / failure mode
- Snapshots that should mint to winners or burn from losers will revert, freezing post evolution.
Mitigation
- Deployment scripts must set Authority roles before any user staking:
- StakeEngine is granted both minter and burner roles.
- PostRegistry is granted the burner role for posting fee burns.
- Integration tests should assert role assignment at deploy time.
Attack
- Spam tiny lots so snapshot loops over huge arrays.
Mitigations
- Consolidation (B.1): one lot per user per (post, side) caps the lot count at the number of distinct stakers per side.
- Sybil-distributed stakes still produce many lots, but each lot represents real economic exposure — the attacker pays gas to stake and risks the stake itself.
- Governance-controlled
compactLots(postId, side)removes burned-out (zero-amount) lots via swap-and-pop, reducing snapshot gas on long-lived posts. - Future: aggregation, partial settlement, or batching.
Attack
- Create many incoming evidence links to a claim to make every score read costly.
Mitigations
- Bounded fan-in:
maxIncomingEdges(default 64) andmaxOutgoingLinks(default 64) cap the work pereffectiveVSRaycall. Both are governance-configurable. - Edges beyond the cap are silently skipped in insertion order.
Attack
- An attacker stakes massively on a post to push sMax up, then withdraws, leaving smaller posts with a large sMax denominator and therefore low participation factors.
Mitigations
sMaxdecays at 0.5% per epoch when the leader's total is below the previoussMax(capped at 3650 epochs of decay per refresh). This bounds the duration of suppression after a pump.- The decay floor is the current leader's total, so
sMaxcannot drop below the largest active post. - The top-3 tracker promotes the next-largest post as leader if the previous leader withdraws, mitigating cliff effects.
- Governance can call
rescanSMaxto rebuild the tracker if state diverges from reality (e.g., after upgrades or extreme griefing).