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:
- Source Code Review: Direct analysis of verified Solidity source code
- On-Chain Data Collection: Storage slot queries and transaction history review
- Function Decomposition: Systematic breakdown of each function's behavior
- Economic Modeling: Analysis of Ponzi mechanics and participant outcomes
- Risk Assessment: Identification of security vulnerabilities and design flaws
- 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
- Etherscan Contract Page: Verified source code, transaction history, and contract state
- Ethereum Yellow Paper: EVM specification for understanding storage layout and opcodes
- Solidity 0.2.0 Docs: Documentation for legacy compiler version (archived)
- Foundry Documentation: Reference for
castcommand usage
Commandline Tools
Tip
Commands below use cast from the Foundry Toolkit. To run the commands below, you must set the RPC URL environment variable:
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