Potential Risks
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 |
Risk Summary
| SEVERITY | COUNT | RISKS |
|---|---|---|
| ☒ Critical | 6 | Broken access control modifier, Ponzi mechanics, Insolvent contract, Failed send() handling, Solidity 0.2.0 vulnerabilities, Permanent fund loss |
| △ High | 4 | Balance desynchronization, No events, Ownership transfer, Integer overflow |
| ◇ Medium | 3 | Unbounded array, First participant disadvantage, No emergency controls |
| ☑ Low | 2 | Code simplicity, Gas optimization trade-offs |
Total Identified Risks: 15
Critical Risks ☒
1. Broken Access Control Modifier
Category: Smart Contract Security / Access Control
Description: The onlyowner Modifier uses deprecated Solidity 0.2.0 syntax that does NOT Revert unauthorized calls. Instead, it silently skips function execution while returning success.
Vulnerable Code:
modifier onlyowner { if (msg.sender == owner) _ }
function setOwner(address _owner) onlyowner {
owner = _owner;
}
function collectFees() onlyowner {
if (collectedFees == 0) return;
owner.send(collectedFees);
collectedFees = 0;
}
How The Vulnerability Works:
When a non-owner calls setOwner() or collectFees():
- Modifier checks:
if (msg.sender == owner)→ evaluates tofalse - Since condition is false, the
_(function body) is NOT executed - Transaction completes successfully with no revert
- No state changes occur, but transaction shows "Success" on Etherscan
Impact:
- User Confusion: Transactions succeed but do nothing (no error message)
- Wasted Gas: Users pay full gas fees for no-op transactions
- Historical Confusion: Appears ownership changed multiple times (it didn't)
- False Security: Accidentally protects state by failing silently
- No Access Control Enforcement: Anyone can call "owner-only" functions without revert
Real-World Evidence:
Multiple addresses have called setOwner() since 2019, all showing "Success":
| DATE | CALLER | FUNCTION | ACTUAL RESULT |
|---|---|---|---|
| 2019-10-27 | 0x4F691Fb1...bd6a69 | setOwner | ☒ Owner unchanged |
| 2020-05-30 | 0x7AF18FFE...7fe302 | setOwner | ☒ Owner unchanged |
| 2021-06-21 | 0x153685A0...487667 | setOwner | ☒ Owner unchanged |
| 2021-06-21 | 0x153685A0...487667 | collectFees | ☒ Fees not collected |
| 2026-01-12 | 0x6D87C072...e958d0 (onchainlooser.eth) | setOwner | ☒ Owner unchanged |
| 2026-01-12 | 0x6D87C072...e958d0 (onchainlooser.eth) | collectFees | ☒ Fees not collected |
| 2026-01-13 | 0xfd02F27F...a9142F | setOwner | ☒ Owner unchanged |
Verification:
# Owner before any of these transactions (2016-03-13)
0x45ab6108Cc41C20f416A98615aA8C349f02a275b (deployer)
# Owner after all of these transactions (2026-02-14)
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "owner()(address)"
# Returns: 0x45ab6108Cc41C20f416A98615aA8C349f02a275b (UNCHANGED!)
Exploitation Scenario:
While this vulnerability accidentally protects the contract state, it demonstrates a critical security flaw:
- Attacker calls
setOwner(attackerAddress) - Transaction succeeds (shows "Success" on Etherscan)
- Attacker assumes they are now owner
- Attacker calls
collectFees() - Transaction succeeds again (shows "Success")
- Attacker pays gas for both transactions
- Nothing actually happened - all fees remain uncollected
Why This Is Dangerous:
- Security Through Obscurity: Protection relies on broken code, not proper design
- No Error Feedback: Users cannot tell their transaction was rejected
- Misleading Success: "Success" status implies operation completed
- Griefing Vector: Spam failed calls to confuse blockchain explorers
- Poor UX: No revert message explains what went wrong
Modern Solidity Equivalent:
// Correct implementation (Solidity 0.8.x)
modifier onlyOwner() {
require(msg.sender == owner, "Caller is not the owner");
_;
}
// OR use OpenZeppelin
import "@openzeppelin/contracts/access/Ownable.sol";
Mitigation (Modern Contracts):
- Always use
require()with explicit error messages - Use OpenZeppelin's Ownable contract for battle-tested access control
- Write unit tests that verify unauthorized calls revert
- Use Solidity >=0.8.0 for better compiler safety
Severity: Critical (design flaw) but Low (actual risk due to silent failure protecting state)
Status: Unfixable - contract is immutable and will remain vulnerable forever
2. Ponzi Scheme Economics
Category: Economic Design Flaw
Description: The contract implements a Ponzi Scheme where earlier participants are paid from later participants' deposits. This model is mathematically guaranteed to collapse when new deposits stop.
Evidence:
// Payout comes from balance accumulated from later deposits
if (balance > participants[payoutIdx].amount * 2) {
uint transactionAmount = 2 * (participants[payoutIdx].amount - participants[payoutIdx].amount / 10);
participants[payoutIdx].etherAddress.send(transactionAmount);
balance -= participants[payoutIdx].amount * 2;
payoutIdx += 1;
}
Impact:
- Guaranteed Losses: Late participants have zero chance of payout when deposits stop
- Current State: 203 participants (indices 118-320) are stuck with no path to recovery
- Total Locked: ~32.44 ETH permanently locked with no payout mechanism
Exploitation Scenario:
- Contract relies on continuous new deposits to pay earlier participants
- When deposit rate slows or stops, the queue freezes
- All remaining participants lose 100% of their deposits
- No mechanism exists to refund or rescue stuck funds
Mitigation: None - the Ponzi model is intentional. The contract is designed to eventually fail.
3. Contract Insolvency
Category: Economic State
Description: The contract is currently insolvent with insufficient balance to pay even the next participant in queue, let alone all 203 remaining participants.
Evidence:
- Current contract balance: 32.44 ETH
- Participants in queue: 203 (indices 118-320)
- Estimated total owed: >>203 ETH (each participant deposited 1+ ETH and expects 1.8x back)
- Balance required for next payout:
participants[118].amount * 2(unknown but > 32.44 ETH)
Impact:
- Contract is mathematically unable to fulfill payout obligations
- 203 participants have lost their deposits permanently
- No new deposits since last activity means permanent insolvency
Verification:
# Check current balance
cast balance 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D
# Returns: 32435530000000000000 (32.44 ETH)
# Check queue position
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "payoutIdx()(uint256)"
# Returns: 118
# Check total participants
cast call 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D "participants(uint256)" 0 | wc -l
# Can query up to index 320
Mitigation: None possible. The contract design has no recovery mechanism.
4. Silent Send Failures
Category: Solidity Anti-Pattern
Description: The contract uses .send() for ETH transfers, which returns false on failure but doesn't revert the transaction. The contract ignores these return values, leading to silent failures.
Evidence:
// Refund for small deposits - failure ignored
msg.sender.send(msg.value);
return;
// Payout to participant - failure ignored
participants[payoutIdx].etherAddress.send(transactionAmount);
balance -= participants[payoutIdx].amount * 2; // ☒ Balance decremented even if send fails!
payoutIdx += 1;
// Fee collection - failure ignored
owner.send(collectedFees);
collectedFees = 0; // ☒ Fees lost forever if send fails!
Impact:
- Payout Failures: If
send()to participant fails (recipient is contract without payable fallback),balanceis still decremented andpayoutIdxstill increments - participant loses their payout permanently - Refund Failures: Deposits < 1 ETH that fail to refund result in participant losing funds
- Fee Losses: Owner's
collectFees()can fail, settingcollectedFees = 0without actual transfer - Balance Desync: The 4.99 ETH discrepancy between internal
balanceand actual balance likely caused by failed sends
Exploitation Scenario:
- Attacker registers with contract address as participant
- Contract address has no payable fallback function
- When attacker reaches front of queue,
send()fails - Contract decrements balance and increments payoutIdx anyway
- Attacker's payout is lost, and contract accounting is corrupted
Mitigation (Modern Solidity):
// Use call with explicit revert on failure
(bool success, ) = payable(recipient).call{value: amount}("");
require(success, "ETH transfer failed");
5. Solidity 0.2.0 Compiler Vulnerabilities
Category: Compiler Security
Description: This contract was compiled with Solidity v0.2.0-2016-01-15-cc4b4f5, which predates numerous critical security patches and language improvements.
Known Vulnerabilities in Early Solidity:
- No Integer Overflow Protection: Arithmetic operations can overflow/underflow silently
- Delegate Call Return Value:
delegatecallreturn value not checked in some cases - Constructor Naming: Uses old-style constructor (function with same name as contract)
- Modifier Execution: Modifier logic can be bypassed if not properly implemented
- Dynamic Array Deletion: Deleting dynamic array elements can leave gaps
Impact:
- Code may behave unexpectedly under edge conditions
- Integer overflow could corrupt accounting (though unlikely in this simple contract)
- Future Ethereum forks could break compatibility
Evidence:
// Old-style constructor (deprecated after 0.4.22)
function Doubler() {
owner = msg.sender;
}
// Modifier without explicit revert (deprecated)
modifier onlyowner { if (msg.sender == owner) _ }
Mitigation: None - contract is immutable. Modern contracts should use Solidity >=0.8.0 with built-in overflow protection.
6. Permanent Fund Loss for 203 Participants
Category: Economic Certainty
Description: Based on current state analysis, 203 participants have permanently lost their deposits with no technical or economic path to recovery.
Evidence:
- Participants Stuck: Indices 118-320 (203 total)
- Last Activity: Years ago (contract is dormant)
- No Recovery Mechanism: No admin function to refund, no emergency withdrawal, no upgrade path
- Insufficient Balance: Even if all future gas fees were eliminated, contract balance can't cover obligations
Impact:
- Approximately 32.44 ETH locked permanently
- 203 individuals or addresses lost funds
- No legal recourse (blockchain transactions are irreversible)
Verification:
# Verify no recent transactions
cast logs 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D --from-block latest --to-block latest
# Returns: empty (no recent activity)
Mitigation: None. This is the intended endgame of a Ponzi scheme.
High Risks △
1. Balance Accounting Desynchronization
Category: State Corruption
Description: The contract's internal balance State Variable (27.44 ETH) does not match the actual ETH balance (32.44 ETH), indicating accounting corruption.
Evidence:
# Internal balance variable
cast storage 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D 3
# Returns: 0x00000000000000000000000000000000000000000000000182a8a4884a804000 (27.44 ETH)
# Actual ETH balance
cast balance 0xfD2487cc0e5dCe97F08BE1Bc8eF1DCE8D5988B4D
# Returns: 32435530000000000000 (32.44 ETH)
# Discrepancy: 4.99 ETH
Impact:
- Payout logic uses internal
balancefor decisions:if (balance > participants[payoutIdx].amount * 2) - If actual balance is sufficient but internal balance is not, payouts won't trigger
- Conversely, if internal balance is higher than actual,
send()will fail but accounting will corrupt further - Breaks trustless assumptions about contract behavior
Possible Causes:
- Failed
send()calls that decrementedbalancewithout actual ETH transfer - Direct ETH transfers to contract (e.g., via
selfdestructfrom another contract) - Rounding errors in integer division accumulated over 321 participants (unlikely to be 4.99 ETH)
Mitigation (Modern Solidity):
// Use address(this).balance instead of internal variable
if (address(this).balance > participants[payoutIdx].amount * 2) {
// payout logic
}
2. No Event Emissions
Category: Transparency / Monitoring
Description: The contract emits zero Events for any state changes, making it difficult to track deposits, payouts, fee collections, or ownership transfers without direct storage queries.
Evidence:
Impact:
- User Experience: Participants can't easily track their queue position or payout status
- Transparency: External observers can't monitor contract activity without full archive node
- Integration: DApps and indexers can't efficiently track contract state changes
- Auditing: Difficult to reconstruct historical activity without replaying all transactions
Missing Events:
event Deposit(address indexed participant, uint256 amount, uint256 queuePosition);
event Payout(address indexed participant, uint256 amount, uint256 queuePosition);
event FeeCollection(address indexed owner, uint256 amount);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
Mitigation (Modern Solidity): Always emit events for significant state changes.
3. Unprotected Ownership Transfer
Category: Access Control
Description: The setOwner() function allows instant ownership transfer to any address without Timelock, multi-step confirmation, or zero-address protection.
Evidence:
function setOwner(address _owner) onlyowner {
owner = _owner; // No validation, no event, no delay
}
Impact:
- Owner could accidentally transfer to wrong address (permanent loss of control)
- Owner could transfer to Address(0) (ownership permanently burned)
- Owner could transfer to contract without owner function capability
- Malicious owner could transfer to attacker address and then collect all fees
Exploitation Scenario:
- Owner calls
setOwner(0x0000000000000000000000000000000000000000) - Ownership is now burned (no one can call
collectFees()orsetOwner()) - Any future accumulated fees are locked forever
Mitigation (Modern Solidity):
address public pendingOwner;
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "New owner is zero address");
pendingOwner = newOwner;
}
function acceptOwnership() external {
require(msg.sender == pendingOwner, "Not pending owner");
address oldOwner = owner;
owner = pendingOwner;
pendingOwner = address(0);
emit OwnershipTransferred(oldOwner, owner);
}
4. Integer Overflow/Underflow
Category: Arithmetic Safety
Description: Solidity 0.2.0 has no built-in overflow/underflow protection. Arithmetic operations can wrap around silently.
Vulnerable Operations:
// Potential overflow (though unlikely with realistic ETH amounts)
participants.length += 1;
collectedFees += msg.value / 10;
balance += msg.value;
// Potential underflow if accounting is corrupted
balance -= participants[payoutIdx].amount * 2;
// Division by zero protection missing
msg.value / 10; // Safe since msg.value is always defined
Impact:
- If
participants.lengthoverflowstype(uint256).max, length wraps to 0 (astronomically unlikely) - If
balanceunderflows, it wraps totype(uint256).max, breaking payout logic - If fees overflow, owner gets less than entitled amount
Current Risk: Low in practice due to reasonable ETH amounts, but poor security practice.
Mitigation (Modern Solidity): Use Solidity >=0.8.0 with automatic overflow checks, or use SafeMath library in older versions.
Medium Risks ◇
1. Unbounded Participant Array
Category: Storage / Gas
Description: The participants array has no maximum size limit and grows indefinitely with each deposit.
Evidence:
uint idx = participants.length;
participants.length += 1; // No cap, grows forever
participants[idx].etherAddress = msg.sender;
participants[idx].amount = msg.value;
Impact:
- Storage Costs: Each new participant costs gas to expand storage
- DoS Risk (Theoretical): If contract iterated over array, large size could cause out-of-gas errors (this contract doesn't iterate, so low risk)
- Practical Limit: EVM stack depth and gas limits effectively cap array size around 10,000-100,000 entries
Current State: 321 participants - well within safe limits.
Mitigation: For production contracts, consider capping array size or using more efficient data structures.
2. First Participant Disadvantage
Category: Economic Fairness
Description: The first participant (index 0) pays 100% of their deposit in fees and receives zero balance credit, while all other participants pay only 10% fees.
Evidence:
if (idx != 0) {
collectedFees += msg.value / 10; // 10% fee
balance += msg.value; // 90% to balance
} else {
collectedFees += msg.value; // 100% fee, 0% to balance
}
Impact:
- First participant is severely disadvantaged compared to all others
- First participant effectively donates their entire deposit to owner
- Disincentivizes being first (though owner could seed the contract themselves)
Rationale: Likely intended to prevent owner from needing to seed the contract, but creates unfair initial conditions.
Mitigation: Fair design would treat all participants equally or clearly disclose first-participant penalty.
3. No Emergency Controls
Category: Safety Mechanisms
Description: The contract has no pause, emergency withdrawal, or circuit breaker functionality. Once deployed, behavior cannot be altered.
Missing Functions:
pause()/unpause()- Stop deposits during emergencyemergencyWithdraw()- Recover funds if critical bug discoveredselfDestruct()- Terminate contract and return remaining funds
Impact:
- If critical bug discovered after deployment, no way to protect users
- No way to return funds to stuck participants (even if owner wanted to)
- Contract is truly immutable (a feature for decentralization, a bug for safety)
Current State: Contract has been dormant for years, so emergency functions are irrelevant now.
Mitigation: Modern contracts typically include pausable functionality for emergency response.
Low Risks ☑
1. Code Simplicity
Category: Complexity Management
Positive Risk: The contract is extremely simple with minimal logic, reducing attack surface.
Evidence:
- Only 5 functions total
- No external dependencies
- No complex math or cryptography
- Straightforward control flow
Impact: While the contract has severe economic and security flaws, the simplicity means the behavior is predictable and auditable.
2. Gas Optimization Trade-offs
Category: Performance
Description: The contract optimizes for gas costs at the expense of safety (e.g., using send() instead of call, not checking return values).
Evidence:
Impact:
- Lower gas costs for users
- Higher risk of failed transfers
- Modern best practice prioritizes safety over minor gas savings
Modern Approach: Use .call{value: amount}("") with explicit success checks.
Risk Mitigation Strategies (General Guidance)
For developers building similar contracts (please don't build Ponzi schemes), here are best practices:
- Use Modern Solidity: Minimum version 0.8.0 for overflow protection
- Check Transfer Results: Always verify return values from
send()orcall() - Emit Events: Log all significant state changes for transparency
- Implement Pausability: Include emergency stop mechanisms
- Two-Step Ownership: Use pending owner pattern for transfers
- Add Timelocks: Delay critical admin actions for community review
- Comprehensive Tests: Include edge cases and failure scenarios
- Professional Audit: Engage security firms before mainnet deployment
- Avoid Ponzi Mechanics: Build sustainable economic models
Responsible Disclosure
This contract is a historical artifact from 2016 and is no longer actively maintained. The risks identified are presented for educational purposes to help developers understand what to avoid in modern smart contract development.
No new interactions with this contract are recommended. The 203 stuck participants cannot be rescued due to the immutable nature of the code.