Skip to content

Storage Layout

DISCLAIMER // NFA // DYOR

This analysis is based on decompiled bytecode and 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
Proxy Address 0x2a9848c39fff51eb184326d65f1238cc36764069 (etherscan)
Implementation Address 0xe528d428c188a80c4824aad89211d292f9a62d77 (etherscan)
Network Ethereum Mainnet
Analysis Date 2025-12-14

Storage Variables

SLOT OFFSET VARIABLE NAME TYPE PURPOSE
0 0 tokenAddress address External token contract address
0 160 initialized uint8 Initialization flag
1 - isRegistered mapping(address => uint8) Registration status
2 - userBalance mapping(address => uint256) User's tracked token balance
3 - lastUpdate mapping(address => uint256) Last balance update timestamp
4 - participants address[] Array of registered participants
5 - participantIndex mapping(address => uint256) Participant's index in array
6 - totalTrackedBalance uint256 Total tracked token balance
7 - participantCount uint256 Number of registered participants
8 - rewardPool uint256 Total ETH available for rewards
9 - rewardsPerShare uint256 Accumulated rewards per share
10 - rewardDebt mapping(address => uint256) User's reward debt
11 - totalClaimed mapping(address => uint256) Total claimed by user
12 - totalDistributed uint256 Cumulative ETH distributed
13 - adminAddress address Current admin address
14 - pendingAdminAddress address Pending admin (during transfer)
15 - operators mapping(address => uint8) Operator role flag
16 - operatorCount uint256 Number of operators
17 - blacklisted mapping(address => uint8) Blacklist flag
18 - userRewardsPaused mapping(address => uint8) User rewards paused flag
19 0 paused uint8 Global pause flag
19 8 registrationOpen uint8 Registration open flag
20 - minBalanceRequired uint256 Minimum token balance requirement
21 - snapshotInterval uint256 Snapshot interval (seconds)
22 - lastSnapshotTime uint256 Last global snapshot timestamp
23 - timelockQueue mapping(bytes32 => uint256) Timelock queue

Proxy-Specific Storage

SLOT VARIABLE PURPOSE
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc implementationAddress Implementation Contract address
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 proxyAdminAddress Proxy admin address

Storage Diagram

graph TB
    subgraph UserState["User State (per address)"]
        direction TB
        Registered[isRegistered]
        Balance[userBalance]
        Timestamp[lastUpdate]
        Index[participantIndex]
        RewardDebt[rewardDebt]
        Claimed[totalClaimed]
        Blacklisted[blacklisted]
        UserPaused[userRewardsPaused]
    end

    subgraph GlobalState["Global State"]
        direction TB
        TokenAddr[tokenAddress]
        TotalBalance[totalTrackedBalance]
        ParticipantArray[participants array]
        ParticipantCount[participantCount]
        RewardPool[rewardPool]
        RewardsPerShare[rewardsPerShare]
        TotalDistributed[totalDistributed]
    end

    subgraph Parameters["Parameters"]
        direction TB
        MinBalance[minBalanceRequired]
        SnapshotInterval[snapshotInterval]
        LastSnapshot[lastSnapshotTime]
    end

    subgraph AccessControl["Access Control"]
        direction TB
        Admin[adminAddress]
        PendingAdmin[pendingAdminAddress]
        Operators[operators mapping]
        OperatorCount[operatorCount]
        Timelocks[timelockQueue]
    end

    subgraph Flags["Flags"]
        direction TB
        GlobalPause[paused]
        RegOpen[registrationOpen]
        Initialized[initialized]
    end

    UserState --> GlobalState
    GlobalState --> Parameters
    Parameters --> AccessControl
    AccessControl --> Flags

    style TokenAddr fill:#e1ffe1
    style RewardPool fill:#e1ffe1
    style Admin fill:#ffe1e1
    style Operators fill:#fff4e1
    style TotalBalance fill:#ffe1f5
    style RewardsPerShare fill:#ffe1f5

Key Storage Variables Explained

tokenAddress (Token Contract)

ATTRIBUTE VALUE
Type address
Slot 0
Purpose External token contract whose balances determine reward distribution

Set once during initialization. All reward calculations depend on this external contract being accurate and available. Cannot be changed after initialization.


isRegistered (Registration Mapping)

ATTRIBUTE VALUE
Type mapping(address => uint8)
Slot 1
Purpose Tracks which addresses are registered participants

Set to 1 on registration, checked before most user operations, cleared on deregistration. Gates access to reward claiming.


userBalance (User Balance)

ATTRIBUTE VALUE
Type mapping(address => uint256)
Slot 2
Purpose Snapshot of user's token balance

Updated during registration, balance updates, and global snapshots. Determines user's proportional share of rewards.


participants (Participant Array)

ATTRIBUTE VALUE
Type address[]
Slot 4
Purpose Ordered list of all registered participants

Users added on registration, removed on deregistration (with array compaction). Iterated during global snapshots. Gas costs scale with array length.


totalTrackedBalance (Total Tracked Balance)

ATTRIBUTE VALUE
Type uint256
Slot 6
Purpose Sum of all registered users' token balances

Denominator for proportional calculations. Must stay in sync with individual balances. Updated when users register/deregister or balances change.


rewardPool (Available Rewards)

ATTRIBUTE VALUE
Type uint256
Slot 8
Purpose Total ETH available for distribution

Increased when operators deposit rewards, decreased when users claim, decreased by emergency withdrawals. Tracks solvency.


rewardsPerShare (Rewards Per Share)

ATTRIBUTE VALUE
Type uint256
Slot 9
Purpose Accumulated rewards per token unit, scaled by 10^18

Core accounting mechanism. Increased when rewards deposited (if participants exist). Enables efficient proportional distribution without iterating over users.


rewardDebt (Reward Debt)

ATTRIBUTE VALUE
Type mapping(address => uint256)
Slot 10
Purpose Tracks accumulated rewards already credited to user

Prevents double-claiming.

Formula: pendingRewards = (balance * rewardsPerShare / 10^18) - rewardDebt


timelockQueue (Timelock Queue)

ATTRIBUTE VALUE
Type nested mapping
Slot 23
Purpose Stores unlock timestamps for queued admin operations

Set when actions queued, checked before execution, cleared after execution or cancellation. Provides 24-hour warning period for critical changes.


Verification Commands

Tip

Commands below use cast from the Foundry Toolkit. To run the commands, you must set the RPC URL environment variable:

export ETH_RPC_URL=https://eth.llamarpc.com

Read Proxy Storage

# GET IMPLEMENTATION ADDRESS
# EIP-1967 implementation slot
cast storage 0x2a9848c39fff51eb184326d65f1238cc36764069 \
  0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc


# GET PROXY ADMIN ADDRESS
# EIP-1967 admin slot
cast storage 0x2a9848c39fff51eb184326d65f1238cc36764069 \
  0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103

Read Implementation Storage Values

# NOTE: Read through proxy address, not implementation directly

# CHECK IF INITIALIZED
cast call 0x2a9848c39fff51eb184326d65f1238cc36764069 "initialized()(bool)"


# GET ADMIN ADDRESS
cast call 0x2a9848c39fff51eb184326d65f1238cc36764069 "admin()(address)"


# CHECK IF PAUSED
cast call 0x2a9848c39fff51eb184326d65f1238cc36764069 "paused()(bool)"


# GET REWARD POOL BALANCE
cast call 0x2a9848c39fff51eb184326d65f1238cc36764069 "rewardPool()(uint256)"


# GET PARTICIPANT COUNT
cast call 0x2a9848c39fff51eb184326d65f1238cc36764069 "participantCount()(uint256)"


# GET TOTAL DISTRIBUTED
cast call 0x2a9848c39fff51eb184326d65f1238cc36764069 "totalDistributed()(uint256)"


# GET COMPREHENSIVE STATS
cast call 0x2a9848c39fff51eb184326d65f1238cc36764069 \
  "getStats()(uint256,uint256,uint256,uint256,bool,bool,uint256,uint256)"


# GET REWARDS PER SHARE (raw storage slot 9)
cast storage 0x2a9848c39fff51eb184326d65f1238cc36764069 9

Read User-Specific Storage

# Replace YOUR_ADDRESS with actual address

# CHECK IF REGISTERED
cast call 0x2a9848c39fff51eb184326d65f1238cc36764069 \
  "isRegistered(address)(bool)" \
  YOUR_ADDRESS


# CHECK IF BLACKLISTED
cast call 0x2a9848c39fff51eb184326d65f1238cc36764069 \
  "blacklisted(address)(bool)" \
  YOUR_ADDRESS


# GET TOTAL CLAIMED
cast call 0x2a9848c39fff51eb184326d65f1238cc36764069 \
  "totalClaimed(address)(uint256)" \
  YOUR_ADDRESS

Economic Invariants

Expected invariants (should hold true):

  1. Conservation: rewardPool <= address(this).balance
    - Reward pool should never exceed actual ETH balance

  2. Sum of pending rewards: Sum of all user pending rewards <= rewardPool
    - Total claimable should not exceed available pool

  3. Balance consistency: Sum of all userBalance[user] = totalTrackedBalance
    - Individual balances should sum to total tracked

  4. Registration consistency: participantCount = participants.length
    - Count should match array length

  5. Reward debt bounds: For any user: rewardDebt[user] <= (userBalance[user] * rewardsPerShare / 10^18)
    - Reward debt should not exceed accumulated rewards

Potential violations:

  • Manual balance updates by admin could break invariant #3
  • Emergency ETH withdrawals could break invariant #1
  • Rounding errors in reward calculations (unlikely with 10^18 precision)
  • External token balance queries could fail or return incorrect values