Skip to content

Methodology

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 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D (etherscan)
Network Ethereum Mainnet
Analysis Date 2026-02-13

Overview

This analysis of the Doubler contract (etherdoubler.com) followed a systematic process to understand a historically significant Ponzi Scheme Smart Contract from early 2016. The contract source code is verified on Etherscan, allowing direct code analysis rather than bytecode decompilation.

Analysis Approach:

  1. Source Code Review: Direct analysis of verified Solidity source code
  2. On-Chain Data Collection: Storage slot queries and transaction history review
  3. Function Decomposition: Systematic breakdown of each function's behavior
  4. Economic Modeling: Analysis of Ponzi mechanics and participant outcomes
  5. Risk Assessment: Identification of security vulnerabilities and design flaws
  6. Historical Context: Understanding as a 2016 artifact with deprecated practices

Key Challenges:

  • Old Solidity Version: Contract uses v0.2.0 with deprecated syntax and patterns
  • No Events: Lack of event emissions makes activity tracking difficult
  • Silent Failures: send() calls can fail without reverting, corrupting state
  • Dormant Contract: No recent activity requires historical reconstruction
  • Ponzi Economics: Understanding the mathematical certainty of failure

Thought Process

%%{init: {'theme': 'base'}}%%
mindmap
  root((Doubler Contract Analysis))
    Discovery
      Contract Type
        Standalone Ponzi
        No Proxy Pattern
        No Upgradability
      Deployment Info
        March 2016
        Block 1,143,960
        Solidity 0.2.0
      Current State
        321 Participants
        118 Paid Out
        203 Stuck Forever
    Code Analysis
      Function Catalog
        Constructor
        Fallback
        enter
        collectFees
        setOwner
      Access Control
        onlyowner Modifier
        Deprecated Syntax
        No Multisig
      Economic Model
        FIFO Queue
        10 Percent Fee
        180 Percent Payout
        First Participant Penalty
    On-Chain Data
      Storage Layout
        5 Fixed Slots
        Dynamic Array
        Participant Structs
      Balance Verification
        32.44 ETH Actual
        27.44 ETH Internal
        4.99 ETH Discrepancy
      Transaction History
        477 Total TXs
        Last Activity Years Ago
        Contract Dormant
    Risk Assessment
      Critical Risks
        Ponzi Collapse
        Insolvency
        Silent Send Failures
        Old Solidity Version
        Permanent Losses
      High Risks
        Balance Desync
        No Events
        Ownership Transfer
        Integer Overflow
      Medium Risks
        Unbounded Array
        First Participant
        No Emergency Controls
    Documentation
      Contract Analysis
        Architecture
        Economic Model
        Observations
      Function Details
        All 5 Functions
        Flow Diagrams
        Code Snippets
      Storage Layout
        Slot Mapping
        Verification Commands
        Current Values
      Potential Risks
        14 Total Risks
        Severity Ratings
        Mitigations
      Methodology
        Process Documentation
        Verification Guide
        Thought Process
      Artifacts
        Source Code
        Bytecode
        Storage Snapshot

Verification Guide

This section provides tools and commands to independently verify the analysis findings.

Tools Used

  • Foundry Cast: Command-line tool for Ethereum RPC calls and blockchain queries
  • Web Browser: Access to Etherscan.io for verified source code and transaction history
  • Storage Decoder: Manual slot calculation and value interpretation

External Resources


Commandline Tools

Tip

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

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

Verify Contract Deployment

Retrieve basic contract information including deployment block, creator, and bytecode.

# GET CONTRACT BYTECODE (VERIFY CONTRACT EXISTS)
cast code 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D

# GET CURRENT ETH BALANCE
cast balance 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D
# Expected: 32435530000000000000 (32.44 ETH)

# GET TRANSACTION COUNT (NUMBER OF TXS)
cast nonce 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D

Verify Storage State

Query all storage slots to verify current contract state matches analysis.

# SLOT 0: PARTICIPANTS ARRAY LENGTH
cast storage 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D 0
# Expected: 0x0000000000000000000000000000000000000000000000000000000000000141 (321)

# SLOT 1: PAYOUT INDEX
cast storage 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D 1
# Expected: 0x0000000000000000000000000000000000000000000000000000000000000076 (118)

# SLOT 2: COLLECTED FEES
cast storage 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D 2
# Expected: 0x0000000000000000000000000000000000000000000000000000000000000000 (0)

# SLOT 3: INTERNAL BALANCE VARIABLE
cast storage 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D 3
# Expected: 0x00000000000000000000000000000000000000000000000182a8a4884a804000 (~27.44 ETH)

# SLOT 4: OWNER ADDRESS
cast storage 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D 4
# Expected: 0x00000000000000000000000045ab6108cc41c20f416a98615aa8c349f02a275b

Verify Public State Variables

Use auto-generated getter functions for public variables.

# GET PARTICIPANTS ARRAY LENGTH
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "participants(uint256)" 0
# Returns: (address, uint256) for participant at index 0

# GET CURRENT PAYOUT INDEX
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "payoutIdx()(uint256)"
# Expected: 118

# GET COLLECTED FEES
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "collectedFees()(uint256)"
# Expected: 0

# GET INTERNAL BALANCE
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "balance()(uint256)"
# Expected: 27444740000000000000 (27.44 ETH in Wei)

# GET OWNER ADDRESS
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "owner()(address)"
# Expected: 0x45ab6108cc41c20f416a98615aa8c349f02a275b

Verify Participant Data

Query individual participants in the queue.

# GET FIRST PARTICIPANT (INDEX 0)
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "participants(uint256)(address,uint256)" 0

# GET LAST PAID PARTICIPANT (INDEX 117)
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "participants(uint256)(address,uint256)" 117

# GET NEXT PARTICIPANT IN LINE (INDEX 118)
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "participants(uint256)(address,uint256)" 118

# GET LAST PARTICIPANT (INDEX 320)
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "participants(uint256)(address,uint256)" 320

# VERIFY ARRAY BOUNDS (INDEX 321 SHOULD FAIL)
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "participants(uint256)(address,uint256)" 321
# Expected: Revert (out of bounds)

Calculate Balance Discrepancy

Verify the accounting mismatch between internal balance and actual ETH balance.

# GET ACTUAL ETH BALANCE IN WEI
ACTUAL_BALANCE=$(cast balance 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D)
echo "Actual Balance: $ACTUAL_BALANCE Wei"

# GET INTERNAL BALANCE VARIABLE IN WEI
INTERNAL_BALANCE=$(cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "balance()(uint256)")
echo "Internal Balance: $INTERNAL_BALANCE Wei"

# CALCULATE DISCREPANCY
echo "Discrepancy: $(($ACTUAL_BALANCE - $INTERNAL_BALANCE)) Wei"
# Expected: ~4990000000000000000 (4.99 ETH)

# CONVERT TO ETH FOR READABILITY
cast to-unit $ACTUAL_BALANCE ether
cast to-unit $INTERNAL_BALANCE ether

Verify Participants Stuck in Queue

Calculate how many participants are waiting and will never be paid.

# GET TOTAL PARTICIPANTS
TOTAL=$(cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "participants(uint256)" 0 | wc -l)

# GET PAYOUT INDEX (NEXT TO BE PAID)
PAYOUT_IDX=$(cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "payoutIdx()(uint256)" | cast to-dec)

# CALCULATE STUCK PARTICIPANTS
# Note: participants.length is stored in slot 0, need to convert hex to decimal
TOTAL_PARTICIPANTS=$(cast to-dec $(cast storage 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D 0))
STUCK=$((TOTAL_PARTICIPANTS - PAYOUT_IDX))

echo "Total Participants: $TOTAL_PARTICIPANTS"
echo "Paid Out: $PAYOUT_IDX"
echo "Stuck in Queue: $STUCK"
# Expected: 321 total, 118 paid, 203 stuck

Verify Function Selectors

Confirm function selector hashes match the source code.

# CALCULATE ENTER() SELECTOR
cast sig "enter()"
# Expected: 0x13af4035

# CALCULATE COLLECTFEES() SELECTOR
cast sig "collectFees()"
# Expected: 0x35c1d349

# CALCULATE SETOWNER(ADDRESS) SELECTOR
cast sig "setOwner(address)"
# Expected: 0x13af40ba (note: may differ due to old Solidity version)

# VERIFY OWNER() GETTER SELECTOR
cast sig "owner()"
# Expected: 0x8da5cb5b

Verify Transaction History

Query recent transaction activity (should show contract is dormant).

# GET RECENT BLOCKS (LAST 1000 BLOCKS)
CURRENT_BLOCK=$(cast block-number)
FROM_BLOCK=$((CURRENT_BLOCK - 1000))

# SEARCH FOR RECENT TRANSACTIONS TO THE CONTRACT
cast logs --from-block $FROM_BLOCK --to-block latest --address 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D
# Expected: empty (no recent activity)

# GET DEPLOYMENT TRANSACTION
cast tx 0x61894a6bffbe5f3a9d58674a1241bf2ebe62e6b9c512eea505b17f4148c3df41

Verify Insolvency Calculation

Confirm the contract cannot pay remaining participants.

# GET CURRENT BALANCE
BALANCE=$(cast balance 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D)
echo "Current Balance: $(cast to-unit $BALANCE ether) ETH"

# GET NEXT PARTICIPANT'S DEPOSIT AMOUNT
NEXT_AMOUNT=$(cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "participants(uint256)(address,uint256)" 118 | tail -1)
echo "Next Participant Deposited: $(cast to-unit $NEXT_AMOUNT ether) ETH"

# CALCULATE REQUIRED PAYOUT (2X DEPOSIT)
REQUIRED=$((NEXT_AMOUNT * 2))
echo "Required for Next Payout: $(cast to-unit $REQUIRED ether) ETH"

# CHECK IF BALANCE IS SUFFICIENT
if [ $BALANCE -gt $REQUIRED ]; then
    echo "✓ Sufficient balance for payout"
else
    echo "✗ Insufficient balance - contract is stuck"
fi

Decode Participant Array Storage

Calculate storage slots for participant struct data.

# CALCULATE BASE SLOT FOR PARTICIPANTS ARRAY
BASE=$(cast keccak 0x0000000000000000000000000000000000000000000000000000000000000000)
echo "Participants Array Base Slot: $BASE"

# CALCULATE SLOT FOR PARTICIPANT 0 ETHER ADDRESS
# Slot = keccak256(0) + (index * 2) + 0
PARTICIPANT_0_ADDR_SLOT=$BASE
echo "Participant[0].etherAddress Slot: $PARTICIPANT_0_ADDR_SLOT"

# CALCULATE SLOT FOR PARTICIPANT 0 AMOUNT
# Slot = keccak256(0) + (index * 2) + 1
PARTICIPANT_0_AMOUNT_SLOT=$(printf "0x%x" $((0x$BASE + 1)))
echo "Participant[0].amount Slot: $PARTICIPANT_0_AMOUNT_SLOT"

# READ PARTICIPANT 0 DATA FROM STORAGE
cast storage 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D $PARTICIPANT_0_ADDR_SLOT
cast storage 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D $PARTICIPANT_0_AMOUNT_SLOT

Verify Compiler Version

Confirm the contract was compiled with Solidity 0.2.0.

# GET CONTRACT METADATA FROM ETHERSCAN
curl -s "https://etherscan.io/address/0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D#code" | grep -i "compiler"

# OR USE CAST TO GET BYTECODE AND CHECK FOR VERSION METADATA
cast code 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D | head -c 200

Token Cost Breakdown

PHASE DESCRIPTION TOKENS
Phase 0 Contract Retrieval & Setup 5 tok
Phase 1 Discovery & Architecture Analysis 10 tok
Phase 2 Function Analysis & Decomposition 15 tok
Phase 3 Risk Assessment & Security Analysis 18 tok
Phase 4 Documentation Synthesis 27 tok
TOTAL Complete Contract Analysis 75 tok

Note: Token costs are estimates based on typical conversation lengths and complexity. Actual consumption may vary by ±10-15% depending on API responses, iterative refinement, and verification steps.

Breakdown Details:

  • Phase 0: Fetching contract data, verifying source code, creating artifact directories
  • Phase 1: Identifying contract type, mapping architecture, cataloging functions, understanding Ponzi mechanics
  • Phase 2: Detailed function-by-function analysis with flow diagrams and code review
  • Phase 3: Comprehensive risk assessment across 14 identified risks with severity ratings
  • Phase 4: Generation of 6 documentation files (contract-analysis, functions, storage-layout, potential-risks, methodology, artifacts) plus TODO checklist