Skip to content

Allocation logic redesign#42

Open
mgretzke wants to merge 11 commits intouse-settled-balancefrom
allocation-logic-redesign
Open

Allocation logic redesign#42
mgretzke wants to merge 11 commits intouse-settled-balancefrom
allocation-logic-redesign

Conversation

@mgretzke
Copy link
Collaborator

@mgretzke mgretzke commented Jan 21, 2026

Allocation Logic Redesign: Linked List Structure with Expiration Normalization

This PR redesigns the allocation storage mechanism in OnChainAllocator to prevent allocation bombing DoS attacks. The previous array-based storage allowed attackers to create unbounded allocations with unique expirations, causing gas exhaustion during traversal operations.

Problem

An attacker could create ~66,000 allocations on behalf of a victim (via allocateAndRegister), each with a unique expiration. Operations like attest(), allocate(), and authorizeClaim() would need to traverse all allocations, exceeding block gas limits and causing permanent DoS.

Solution

Linked List Storage Structure

  • Replaced mapping(bytes32 => Allocation[]) array with sorted linked list
  • _nextExpirationPointer[tokenHash] → head pointer to earliest expiration
  • _balancesByExpiration[tokenHash | expiration] → node with amount and next pointer
  • _allocatedClaims[claimHash] → maps claims to their normalized expiration

Expiration Normalization

  • All allocations have expirations rounded up to time-based buckets:
    • < 10 min: No rounding (600 max unique values)
    • < 1 hour 5 min: Round to 10 seconds (330 max)
    • < 1 day: Round to 1 minute (1,375 max)
    • < 1 week 1 hour: Round to 10 minutes (870 max)
    • > 1 week: Round to 1 hour (551 max)
  • Maximum 3,726 unique expirations regardless of attack volume
  • Amounts accumulate per bucket, not per allocation
  • User allocations also normalized to prevent hint invalidation attacks (user front-running to claim at hint position)
  • Note: Claim hash still uses original expiration for signature/registration verification

Hint System for O(1) Deletion

  • authorizeClaim accepts allocatorData parameter with position hints
  • Hints contain the expiration of any node between the lists head and target
  • Valid hints skip traversal from head to the hinted position
  • Ideal hint points directly to previous node for O(1), partial hints still reduce traversal
  • New getNormalizedExpirationForClaim() view function to retrieve hints

Technical Changes

Data Structures

  • tokenHash changed from bytes32 to bytes28 to fit expiration in same key
  • BalanceExpiration struct: {uint32 nextExpiration, uint224 amount}
  • Storage key: tokenHash | expiration combines both in bytes32

Gas-Optimized Assembly

  • _readAllocatedBalance: Traverses list, cleans expired entries, returns pointers
  • _storeAllocatedBalance: Inserts into sorted position with O(1) for same-expiration
  • _deleteAllocatedBalance: Removes node, updates pointers, supports hints

New Interface

  • getNormalizedExpirationForClaim(bytes32 claimHash) → returns normalized expiration
  • InvalidHint error for malformed allocatorData

Gas Impact

Operation Before (66k allocs) After (66k allocs)
attest() > 45M gas (DoS) ~266k gas
allocate() > 45M gas ~355k gas
authorizeClaim() up to > 45M gas ~275k gas
authorizeClaim() with hint N/A ~22k gas

Limitation

The previous uint224.max limit per allocated balance now expands to an expiration time.

Testing

Added comprehensive allocation bombing tests that:

  1. Create 66,000 allocations simulating worst-case attack
  2. Verify all critical operations complete under 10M gas
  3. Test hint system achieves O(1) deletion

@mgretzke mgretzke requested review from 0age and ccashwell January 21, 2026 12:53
@mgretzke mgretzke requested a review from a team as a code owner January 21, 2026 12:53
Copy link
Contributor

@0age 0age left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! My one concern here is that we may still be flying a little too close to the sun on the limits; theoretically there are going to be batch compacts that involve multiple allocations that a sponsor could still bomb, right?

I guess the pointer system helps (good idea) but I'm also a little nervous about that introducing discretionary behavior on what gets deleted; basically it'd be bad if you could point to a later allocated amount when an earlier allocated amount was already available and hadn't expired. Will have to think about it a little more.

Either way, this is solid progress!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants