ReentrancyCat
vulnerability: 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.
// 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
vulnerability: weak randomness
FoMo3D. The future written in block hashes. Miners chose winners. 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.
// 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
vulnerability: 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.
// 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
vulnerability: uninitialized storage
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.
// 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
vulnerability: constructor typo
Rubixi. One missing letter. The constructor became a public function.
Anyone could be the owner.
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.
// 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;
}
}