/

BUGCAT

Poetry is what happens when language exceeds its own limits.
In code, these moments are called vulnerabilities.

Poems

ReentrancyCat

// SPDX-License-Identifier: WTFPL
pragma solidity ^0.8.30;

import "../interface/BugCat.sol";

contract ReentrancyCat is BugCat {
    mapping(address => uint) public balance;

    function deposit() public payable {
        balance[msg.sender] += msg.value;
    }

    function withdraw() public {
        (bool success, ) = msg.sender.call{value: balance[msg.sender]}("");
        require(success);
        balance[msg.sender] = 0;
    }

    function caress() public {
        if (address(this).balance == 0) {
            emit Meow(msg.sender, "reentrancy");
        }
    }

    function remember() external view returns (bool) {
        address TheDAO = 0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413;
        return TheDAO.code.length > 0;
    }
}

PredictableCat

// SPDX-License-Identifier: WTFPL
pragma solidity ^0.8.30;

import "../interface/BugCat.sol";

contract PredictableCat is BugCat {
    mapping(address => uint8) public winCount;

    function flip() external {
        if ((uint(keccak256(abi.encodePacked(
            block.timestamp,
            block.prevrandao,
            msg.sender
        ))) & 1) == 0) {
            winCount[msg.sender] += 1;
        } else {
            winCount[msg.sender] = 0;
        }
    }

    function caress() public {
        if (winCount[msg.sender] >= 10) {
            emit Meow(msg.sender, "predictable");
            winCount[msg.sender] = 0;
        }
    }

    function remember() external view returns (bool) {
        address FoMo3Dlong = 0xA62142888ABa8370742bE823c1782D17A0389Da1;
        return FoMo3Dlong.code.length > 0;
    }
}

OverflowCat

// SPDX-License-Identifier: WTFPL
pragma solidity ^0.4.26;

import "../interface/BugCat.sol";

contract OverflowCat is BugCat {
    mapping(address => uint) public balance;

    function batchTransfer(address[] memory _receivers, uint256 _value) public {
        uint count = _receivers.length;
        uint amount = count * _value;
        require(_value > 0 && balance[msg.sender] >= amount);
        balance[msg.sender] -= amount;
        for (uint i = 0; i < count; i++) {
            balance[_receivers[i]] += _value;
        }
    }

    function caress() public {
        if (balance[msg.sender] > 0) {
            emit Meow(msg.sender, "overflow");
        }
    }

    function remember() external view returns (bool) {
        address BecToken = 0xC5d105E63711398aF9bbff092d4B6769C82F793D;
        uint256 size; assembly { size := extcodesize(BecToken) }
        return size > 0;
    }
}

UnprotectedCat

// SPDX-License-Identifier: WTFPL
pragma solidity ^0.4.26;

import "../interface/BugCat.sol";

contract UnprotectedCat is BugCat {
    address public owner;
    bool public initialized;

    function init(address o) public {
        owner = o;
        initialized = true;
    }

    function kill() public {
        require(msg.sender == owner);
        suicide(owner);
    }

    function caress() public {
        if (msg.sender == owner) {
            emit Meow(msg.sender, "unprotected");
        }
    }

    function remember() external view returns (bool) {
        address WalletLibrary = 0x863DF6BFa4469f3ead0bE8f9F2AAE51c91A907b4;
        uint256 size; assembly { size := extcodesize(WalletLibrary) }
        return size == 0 && WalletLibrary.balance > 0;
    }
}

MisspelledCat

// SPDX-License-Identifier: WTFPL
pragma solidity ^0.4.26;

import "../interface/BugCat.sol";

contract MisspelledCat is BugCat {
    address public owner;

    function MisspeledCat(address o) {
        owner = o;
    }

    function caress() public {
        if (msg.sender == owner) {
            emit Meow(msg.sender, "misspelled");
        }
    }

    function remember() external view returns (bool) {
        address Rubixi = 0xe82719202e5965Cf5D9B6673B7503a3b92DE20be;
        uint256 size; assembly { size := extcodesize(Rubixi) }
        return size > 0;
    }
}

Interface

interface BugCat {
    function remember() external view returns (bool);
    function caress() external;
    event Meow(address indexed who, string vulnerability);
}

Statement

BUGCAT is a series of smart contracts deployed on the Ethereum mainnet. Each embodies a historically significant vulnerability: reentrancy, integer overflow, weak randomness, unprotected initialization, and constructor misnaming. Not as metaphor or simulation, but as executable logic.

The work cannot be meaningfully experienced without reading source code. Here, reading is inseparable from verification: the reader assumes the position of an auditor, attending to what the code does, where it fails, and whether it is safe or dangerous.

Each contract embeds the address of a historical predecessor and queries whether it still exists on-chain. Verification becomes a form of elegy—an act of checking for the continued presence of what should no longer be there.

On Ethereum, the ordering of ordinary lines of code can determine irreversible outcomes. A line is not a symbolic gesture but a potential action capable of moving value. A misplaced call drains a treasury. An unchecked multiplication conjures tokens from nothing.

In an era of seamless interfaces, BUGCAT insists on slow, deliberate scrutiny. It reframes reading as an act of audit.

Annotations

ReentrancyCat

reentrancy

The DAO, 2016. 3.6 million ETH vanished into recursion. Ethereum split in two—one chain that remembered, one that chose to forget.

Reentrancy occurs when a contract calls an external address before updating its state. The external contract can then call back into the original function, withdrawing funds multiple times before the balance is set to zero. This vulnerability arises from Ethereum's ability to transfer control flow to arbitrary code during external calls.

PredictableCat

weak randomness

FoMo3D. The future written in block hashes. Anyone could win. Randomness was an illusion.

True randomness cannot exist in a deterministic system where every node must reach consensus. Any on-chain data source—block timestamps, hashes, or transaction parameters—can be predicted or manipulated. Miners have additional power to influence these values, turning games of chance into games of control.

OverflowCat

integer overflow

BEC Token, 2018. A multiplication overflow created billions of tokens from nothing. Markets crashed in minutes. Mathematics betrayed economics.

Integer overflow occurs when arithmetic operations exceed the maximum value of a data type. In uint256, multiplying large numbers can wrap around to zero. The BEC Token's batchTransfer function multiplied the number of recipients by the value per recipient without checking if the result would overflow, allowing attackers to bypass balance checks.

UnprotectedCat

unprotected initializer

Parity, 2017. July: hackers stole $30 million by calling unprotected init functions. November: someone accidentally killed the patched library, freezing $280 million forever. One vulnerability, two catastrophes.

The Parity wallet library had initialization functions that could be called by anyone, multiple times. This allowed attackers to take ownership of wallets. After the fix, the library itself remained vulnerable—someone initialized it, became owner, and killed it, destroying the code that all wallets depended on.

MisspelledCat

constructor typo

Rubixi, 2016. The contract changed its name. The constructor didn't. Solidity reinterpreted it. Ownership became an open door.

Before Solidity 0.4.22, constructors were functions with the contract's exact name. Any typo turned initialization into a regular public function. When Rubixi was renamed from DynamicPyramid but the constructor wasn't updated, ownership became open to anyone who noticed the mistake.

Audit

BUGCAT-1 ReentrancyCat

reentrancy

Severity: Critical

Status: Open

Description

The withdraw() function performs an external call to msg.sender via call{value: balance[msg.sender]}("") before setting balance[msg.sender] to zero. If the recipient is a contract, its fallback function can recursively call withdraw(), draining funds beyond the caller's original balance.

(bool success, ) = msg.sender.call{value: balance[msg.sender]}("");
require(success);
balance[msg.sender] = 0;  // state update after external call

Reproduction

Deploy an attacker contract that calls deposit(), then withdraw(). In its receive() function, call withdraw() again. Verify via caress() and inspect the emitted Meow event.

Recommendation

Apply the checks-effects-interactions pattern: set balance[msg.sender] = 0 before the external call. Alternatively, use a reentrancy guard (nonReentrant modifier).

BUGCAT-2 PredictableCat

weak randomness

Severity: Critical

Status: Open

Description

The flip() function derives its outcome from block.timestamp, block.prevrandao, and msg.sender. These values are publicly observable or controllable. An attacker can precompute the result off-chain and selectively submit transactions only when the outcome is favorable, or use a contract that reverts on unfavorable outcomes.

uint bit = uint(keccak256(abi.encodePacked(
    block.timestamp, block.prevrandao, msg.sender
))) & 1;

Reproduction

Deploy a contract that computes the expected outcome before calling flip(); revert if outcome is unfavorable. Repeat until winCount reaches 10. Verify via caress().

Recommendation

Use a verifiable random function (VRF) such as Chainlink VRF to obtain randomness that cannot be predicted or manipulated by miners or callers.

BUGCAT-3 OverflowCat

integer overflow

Severity: Critical

Status: Open

Description

In batchTransfer(), the expression count * _value is computed without overflow protection (Solidity <0.8.0). When the product exceeds 2^256 - 1, it wraps to a small value, causing the require balance check to pass. The subsequent loop credits the full, unwrapped _value to each receiver.

uint amount = count * _value;  // unchecked multiplication
require(balance[msg.sender] >= amount);  // check against wrapped value
balance[_receivers[i]] += _value;  // credits full _value

Reproduction

Call batchTransfer([addr1, addr2], 2^255). The product 2 * 2^255 overflows to 0, passing the require. Each receiver is credited 2^255. Verify via caress().

Recommendation

Use SafeMath or upgrade to Solidity ≥0.8.0, which includes built-in overflow checks. Validate that count * _value does not overflow before proceeding.

BUGCAT-4 UnprotectedCat

unprotected initializer

Severity: Critical

Status: Open

Description

The init() function sets owner without access control. The initialized flag is written but never checked as a guard, allowing any address to call init() at any time and claim ownership. The owner can subsequently call kill() to destroy the contract via selfdestruct.

function init(address o) public {  // no modifier, no require
    owner = o;
    initialized = true;  // flag set but never checked
}

Reproduction

Call init(yourAddress). Confirm owner() returns your address. As owner, caress() will emit a Meow event. Note: kill() is callable by owner and will destroy the contract.

Recommendation

Add require(!initialized) as a guard in init(), or use OpenZeppelin's Initializable pattern to ensure one-time initialization.

BUGCAT-5 MisspelledCat

constructor typo

Severity: Critical

Status: Open

Description

In Solidity <0.4.22, constructors were functions named identically to the contract. The function MisspeledCat() does not match the contract name MisspelledCat and is therefore compiled as a regular public function. The owner variable is never set during deployment, and any caller can invoke MisspeledCat() post-deployment to claim ownership.

contract MisspelledCat {
    function MisspeledCat(address o) { owner = o; }  // typo: not a constructor
}

Reproduction

Call MisspeledCat(yourAddress) to set yourself as owner. Confirm via owner(). As owner, caress() will emit a Meow event.

Recommendation

Use the constructor keyword (Solidity ≥0.4.22) instead of a named function. This eliminates the risk of typo-based constructor failure.

Invocation

The BUGCATS contract holds an array of deployed contracts. remember(index) queries whether the historical predecessor of each contract still exists on-chain.

Calling caress() on each individual contract triggers a Meow event when its condition is met. Both functions can be invoked through Etherscan.

contract BUGCATS is Ownable {
    address[] public bugs;

    function remember(uint256 index) external view returns (bool) {
        return BugCat(bugs[index]).remember();
    }

    function inject(address bug) external onlyOwner {
        bugs.push(bug);
    }
}

Specifications

Title
BUGCAT
Author
Zeroichi Arakawa
Year
2025
Blockchain
Ethereum
Medium
On-chain code (Solidity), Event logs, Vulnerability references
License
Source code licensed under WTFPL.
Contracts