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:
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):
-
Conservation:
rewardPool <= address(this).balance
- Reward pool should never exceed actual ETH balance -
Sum of pending rewards: Sum of all user pending rewards <= rewardPool
- Total claimable should not exceed available pool -
Balance consistency: Sum of all
userBalance[user]=totalTrackedBalance
- Individual balances should sum to total tracked -
Registration consistency:
participantCount=participants.length
- Count should match array length -
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