Potential Risks
DISCLAIMER // NFA // DYOR
This analysis is based on decompiled bytecode — the contract source code is not verified on
Etherscan. Function names, parameter types, and internal logic are inferred from selector
matching, transaction input decoding, and event log analysis. We are not smart contract
security experts. This document should not be considered a comprehensive security audit or
financial advice. Always verify critical information independently.
⊙ generated by robots | curated by humans
| METADATA | |
|---|---|
| Contract Address | 0xD8706D2D...dC2C2c (etherscan) |
| Network | Ethereum Mainnet |
| Analysis Date | 2026-03-29 |
Risk Summary
| SEVERITY | COUNT |
|---|---|
| Critical | 0 |
| High | 2 |
| Medium | 3 |
| Low | 2 |
| Informational | 3 |
High Risks
Owner Can Drain All Funds Without Restriction
| FIELD | VALUE |
|---|---|
| Severity | High |
| Category | Centralization |
The emergencyWithdraw() function allows the owner to withdraw the entire contract ETH balance at any time with no timelock, no Multisig requirement, and no on-chain constraint. This function also zeroes the totalPending counter but does not zero individual user rewardBalance entries, creating a state where users appear to have claimable rewards but the contract has no ETH to pay them.
After an emergency withdrawal, any user calling claim() or claimAmount() would see their balance as non-zero but the ETH transfer would fail because the contract is empty. The getPoolStats() function would show a deficit.
The owner address (0x9fbcc72a...63ea03) is an EOA — a single private key controls this capability. There is no multisig, governance vote, or delay mechanism protecting user deposits.
What to ask the team: Is there a plan to transfer ownership to a multisig or implement a timelock before significant funds are deposited?
Off-Chain Reward Calculation With No Verifiable Formula
| FIELD | VALUE |
|---|---|
| Severity | High |
| Category | Trust Assumption |
Reward amounts are determined entirely off-chain. The distributor or owner calls depositToUser() or batchDeposit() with arbitrary amounts for arbitrary users. There is no on-chain formula, no proportional distribution based on token holdings, and no verifiable calculation linking user behavior to reward amounts.
Users must trust that:
- The distributor credits fair and accurate amounts
- The distributor does not selectively exclude registered users
- The amounts reflect some real economic activity (trading fees, yield, etc.)
- The same reward is not credited twice or not credited at all
This is in contrast to the previous XCL Rewards contract which, while also centralized, at least tracked per-share calculations on-chain via a rewardsPerShare accumulator.
Medium Risks
Registration Check Inconsistency Between Deposit Functions
| FIELD | VALUE |
|---|---|
| Severity | Medium |
| Category | Complexity |
batchDeposit() checks poolExists[user] for each recipient, ensuring only registered users receive credits. However, depositToUser() does not perform this check. This means the distributor or owner can credit rewards to unregistered addresses via depositToUser().
This inconsistency could lead to:
- Rewards credited to addresses that never registered (and may not know they have a balance)
- ETH locked in the contract if credited to an address that cannot call
claim() - Confusion about whether registration is actually required to receive rewards
Incomplete Claim Tracking
| FIELD | VALUE |
|---|---|
| Severity | Medium |
| Category | Complexity |
The totalClaimed mapping (slot 4) is only updated by claimAmount(), not by claim(). This means the cumulative claim tracking is incomplete — users who always use claim() will show totalClaimed = 0 despite having withdrawn ETH.
This could cause issues for any off-chain system that relies on the totalClaimed value to calculate lifetime rewards or validate distributions.
Ghost Balance After Emergency Withdrawal
| FIELD | VALUE |
|---|---|
| Severity | Medium |
| Category | Economic |
When emergencyWithdraw() is called, it zeroes totalPending but leaves individual rewardBalance entries intact. This creates a state where:
rewardBalance[user]returns a non-zero valueaddress(this).balanceis 0- Any
claim()orclaimAmount()call will revert on the ETH transfer
Users checking their balance via rewardBalance() or balanceOf() would see a number that suggests claimable funds, but attempting to claim would fail. The getPoolStats() function would reveal the deficit, but a typical user querying only their own balance would not see the discrepancy.
Low Risks
Single-Step Ownership Transfer
| FIELD | VALUE |
|---|---|
| Severity | Low |
| Category | Centralization |
The transferOwnership() function uses a single-step transfer pattern. If the owner accidentally transfers to an incorrect or inaccessible address, ownership is permanently lost and all owner-only functions become inaccessible. The more common two-step pattern (Ownable2Step) requires the new owner to accept, preventing accidental transfers.
Receive Function Accepts Unattributed ETH
| FIELD | VALUE |
|---|---|
| Severity | Low |
| Category | Economic |
The contract has a receive() fallback function that accepts plain ETH transfers. Any ETH sent to the contract without calling a specific function is accepted but not credited to any user's reward balance. This ETH can only be recovered by the owner via emergencyWithdraw().
Informational
Contract Not Verified on Etherscan
| FIELD | VALUE |
|---|---|
| Severity | Informational |
| Category | Trust Assumption |
The contract source code is not verified on Etherscan. This analysis is based on bytecode decompilation, which may not perfectly represent the original source code. Users and researchers cannot independently review the contract logic through standard tools without performing their own decompilation.
All Privileged Addresses Are EOAs
| FIELD | VALUE |
|---|---|
| Severity | Informational |
| Category | Centralization |
The owner (0x9fbcc72a...63ea03), distributor (0x867a2c98...db3157), and fee recipient (0xe1f3fbb1...264a84) are all Externally Owned Accounts (EOAs) rather than multisig wallets or governance contracts. This means each role is controlled by a single private key.
Same Deployer as XCL Rewards Proxy
| FIELD | VALUE |
|---|---|
| Severity | Informational |
| Category | Trust Assumption |
This contract was deployed by the same address (0x9fbcc72a...63ea03) that deployed the XCL Rewards proxy contract (0x2a9848c3...764069 (etherscan)). This establishes a direct operational link between the two contracts and the Xcellar ecosystem. The deployer appears to also be actively interacting with another unverified contract at 0x99c96efF...b2dCC (etherscan) which shares several administrative function selectors.