Skip to content

Storage Layout

DISCLAIMER // NFA // DYOR

This analysis is based on observations of the contract behavior. We are not smart contract security experts. This document aims to explain what the contract appears to do based on the code. It should not be considered a comprehensive security audit or financial advice. Always verify critical information independently and consult with blockchain security professionals for important decisions.

⊙ generated by robots | curated by humans

METADATA
Contract Address 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D (etherscan)
Network Ethereum Mainnet
Analysis Date 2026-01-23

Storage Slots Overview

graph LR
    subgraph "Slot 0"
        S0[flexibleAllocation<br/>address 20 bytes]
    end

    subgraph "Slot 1"
        S1[hasClaimed mapping<br/>mapping address => bool<br/>Also: eligibilityStatus]
    end

    subgraph "Slot 2 Packed"
        S2A[owner<br/>address 20 bytes]
        S2B[paused<br/>bool 1 byte]
        S2C[claimingEnabled<br/>bool 1 byte]
    end

    S0 --> |Read by| F1[flexibleAllocation]
    S0 --> |Modified by| F2[setFlexibleAllocation]

    S1 --> |Read by| F3[hasClaimed]
    S1 --> |Read by| F4[getEligibilityStatus]
    S1 --> |Modified by| F5[claim]
    S1 --> |Modified by| F6[setEligibilityStatus]

    S2A --> |Read by| F7[owner]
    S2A --> |Modified by| F8[transferOwnership]

    S2B --> |Read by| F9[paused]
    S2B --> |Modified by| F10[pause/unpause]

    S2C --> |Read by| F11[claimingEnabled]
    S2C --> |Modified by| F12[setClaimingEnabled]

Slot 0: flexibleAllocation

PROPERTY VALUE
Type address
Size 20 bytes
Initial Value 0xe6c328360439b91727ec854bf2b6caeeaff5dc66
Access Pattern Read-heavy, rare writes

Purpose: Stores the address of the external FlexibleAllocation contract used to verify user allocations during claims.

Functions That Read:

  • flexibleAllocation() - Returns the address
  • claim() - Uses address to call getAllocation(address)
  • canClaim(address) - Uses address to call getAllocation(address)

Functions That Modify:

  • setFlexibleAllocation(address) - Owner-only function to change the allocation contract

Current Value:

$ cast storage 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D 0
0x000000000000000000000000e6c328360439b91727ec854bf2b6caeeaff5dc66

Security Considerations:

  • ☒ Owner can change this at any time with no timelock
  • △ Changing mid-operation affects active claimers
  • △ New contract must implement compatible getAllocation(address) interface

Slot 1: hasClaimed / eligibilityStatus

PROPERTY VALUE
Type mapping(address => bool)
Size Dynamic (keccak256 hash-based addressing)
Initial Value All addresses default to false
Access Pattern Write-once per address (claim), frequent reads

Purpose: Dual-purpose storage that tracks both:

  1. Whether an address has claimed (hasClaimed)
  2. Eligibility status for claiming (eligibilityStatus)

Functions That Read:

  • hasClaimed(address) - Returns claim status
  • getEligibilityStatus(address) - Returns eligibility status (same data!)
  • claim() - Checks if address has already claimed
  • canClaim(address) - Checks claim status before allocation check

Functions That Modify:

  • claim() - Sets to true permanently after successful claim
  • setEligibilityStatus(address, bool) - Owner sets eligibility
  • batchSetEligibilityStatus(address[], bool[]) - Owner sets eligibility for multiple addresses

Mapping Address Calculation:

// For address A, storage location is:
keccak256(abi.encodePacked(A, uint256(1)))

Example Values:

# Check if address has claimed
$ cast call 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D "hasClaimed(address)" <ADDRESS>

# Check eligibility status (reads same storage!)
$ cast call 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D "getEligibilityStatus(address)" <ADDRESS>

Security Considerations:

  • Critical: Same storage used for two purposes—cannot distinguish between "ineligible" and "already claimed"
  • △ Setting eligibility to false makes address appear as if it already claimed
  • △ Claims are permanent—even owner cannot reset
  • ☑ Write-once pattern for claims prevents double-claiming

Slot 2: Packed Storage (owner + paused + claimingEnabled)

PROPERTY VALUE
Type Packed: address (20 bytes) + bool (1 byte) + bool (1 byte)
Size 22 bytes total (10 bytes unused)
Layout Bytes 0-19: owner, Byte 20: paused, Byte 21: claimingEnabled
Access Pattern Frequent reads, occasional writes

Byte Layout

┌──────────────────────────────────────────┬──────────┬──────────────────┐
│  Bytes 0-19: owner (address)             │ Byte 20  │ Byte 21          │
│  0x2a4a36b59c47f9ed95b562ebeaefa8c19ef04902│ paused   │ claimingEnabled  │
└──────────────────────────────────────────┴──────────┴──────────────────┘

Current Value:

$ cast storage 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D 2
0x0000000000000000000001002a4a36b59c47f9ed95b562ebeaefa8c19ef04902

Parsing:

  • Bytes 0-19 (owner): 0x2a4a36b59c47f9ed95b562ebeaefa8c19ef04902
  • Byte 20 (paused): 0x00 = false (not paused)
  • Byte 21 (claimingEnabled): 0x01 = true (claiming enabled)

Subfield: owner (bytes 0-19)

Purpose: Stores the contract owner address with full administrative privileges.

Functions That Read:

  • owner() - Returns owner address
  • All admin functions - Check msg.sender == owner

Functions That Modify:

  • transferOwnership(address) - Changes owner

Security Considerations:

  • ☒ Single EOA owner—no multisig
  • ☒ No renounceOwnership function—cannot burn ownership
  • △ No timelock on ownership transfer

Subfield: paused (byte 20)

Purpose: Boolean flag indicating whether the contract is paused.

Functions That Read:

  • paused() - Returns pause state
  • claim() - Reverts if paused

Functions That Modify:

  • pause() - Sets to true
  • unpause() - Sets to false

Values:

  • 0x00 = Not paused (claims allowed)
  • 0x01 = Paused (claims blocked)

Security Considerations:

  • △ Owner can pause at will with no timelock
  • △ No automatic unpause mechanism
  • ☑ View functions remain accessible when paused

Subfield: claimingEnabled (byte 21)

Purpose: Boolean flag indicating whether claiming is globally enabled.

Functions That Read:

  • claimingEnabled() - Returns enabled state

Functions That Modify:

  • setClaimingEnabled(bool) - Owner sets state

Values:

  • 0x00 = Claiming disabled
  • 0x01 = Claiming enabled

Security Considerations:

  • Critical: This flag is NOT checked in claim() function logic based on bytecode analysis
  • △ Flag exists but appears to have no enforcement mechanism
  • △ May be vestigial from earlier contract version

Storage Packing Benefits

The contract uses efficient storage packing in Slot 2, combining three variables into one storage slot:

Gas Savings:

  • Single SLOAD (2100 gas) reads all three values
  • Single SSTORE (20000 gas first write, 5000 gas subsequent) updates packed data
  • Without packing: 3x SLOAD (6300 gas), 3x SSTORE (up to 60000 gas first writes)

Trade-offs:

  • More complex bit manipulation in bytecode
  • All three values are loaded even if only one is needed
  • Requires careful management to avoid overwriting adjacent values

Storage Verification Commands

# Read slot 0 (flexibleAllocation)
cast storage 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D 0

# Read slot 2 (owner + flags)
cast storage 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D 2

# Check if specific address has claimed (slot 1 mapping)
# First calculate storage location:
ADDRESS=<target_address>
SLOT=1
cast index address $ADDRESS $SLOT

# Then read from calculated location:
cast storage 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D <calculated_location>

# Or use the contract's view functions:
cast call 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D "owner()"
cast call 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D "paused()"
cast call 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D "claimingEnabled()"
cast call 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D "flexibleAllocation()"
cast call 0x33184cD3E5F9D27C6E102Da6BE33e779528A606D "hasClaimed(address)" $ADDRESS

Storage Diagram

classDiagram
    class Slot0 {
        +address flexibleAllocation
        getCurrentAllocation(address)
    }

    class Slot1 {
        +mapping hasClaimed
        +mapping eligibilityStatus
        <<Same Storage>>
    }

    class Slot2 {
        +address owner (bytes 0-19)
        +bool paused (byte 20)
        +bool claimingEnabled (byte 21)
        <<Packed Storage>>
    }

    note for Slot1 "Dual-purpose storage\nCannot distinguish\neligible vs claimed"

    note for Slot2 "Gas-optimized packing\n22 of 32 bytes used\n10 bytes unused"

Key Observations

OBSERVATION IMPACT
Slot 1 serves dual purpose Cannot distinguish between "ineligible" and "already claimed"
Efficient slot packing in Slot 2 Saves gas on admin operations
claimingEnabled flag not enforced Flag exists but has no apparent effect on claim() function
No storage for renounce ownership Owner cannot burn ownership permanently
External contract dependency Slot 0 address can be changed at any time by owner
Write-once claim pattern Claims are permanent and cannot be reset