⚡ Thor Breaks Math - Overflow & Underflow Case Study
Danger (I have… broken math.)
Hulk’s rage wraps to 4.
Iron Man’s suit goes from 0 to 2²⁵⁶−1.
Thor wins without swinging Mjolnir once.
TL;DR
Vulnerability: Arithmetic overflow/underflow
Danger zone: Solidity <0.8.0 or any unchecked {} block in ≥0.8.0
Impact: Silent wraparound → logic bypass, infinite tokens, balance corruption
Severity: Critical (pre-0.8.0) → High (if unchecked misused)
Fix: Use Solidity ≥0.8.Sync 0.8.0+ with no unchecked, or validate before operations
🎬 Story Time - Thor: Ragnarok Edition
Grandmaster announces a new contest on Sakaar:
“Whoever breaks their opponent’s power meter first - wins!”
Round 1: Hulk vs Thor (Overflow)
Thor hands Hulk a rage token contract.
mapping(address => uint8) public rage;function getAngry(uint8 amount) public { unchecked { rage[msg.sender] += amount; } // Hulk angry = true}Hulk starts smashing → rage = 250
Thor: “One more smash, big guy.”
Hulk +10 rage → 260 → wraps to 4
Hulk suddenly calm.
Crowd loses mind.
Thor wins by making Hulk peaceful.
Round 2: Iron Man vs Thor (Underflow)
Tony Stark shows up in Mark LXXXV suit.
mapping(address => uint256) public energy;function drain(uint256 amount) public { unchecked { energy[msg.sender] -= amount; }}Tony’s suit at 0%
Thor: “Just one more repulsor blast.”
Tony fires → −100% → wraps to 2²⁵⁶ − 1
Suit now shows 115792089237316195423570985008687907853269984665640564039457584007913129639835% charged.
Arc reactor explodes from infinite energy.
Thor wins again.
Math: 2
Avengers: 0
Vulnerable Code - Pre-0.8.0 (The Dark Ages)
pragma solidity ^0.7.6;
contract HulkRageToken { mapping(address => uint8) public rage;
function getAngry(uint8 amount) public { rage[msg.sender] += amount; // Silent overflow - no SafeMath }}pragma solidity ^0.7.6;
contract IronManSuit { mapping(address => uint256) public energy;
function drain(uint256 amount) public { energy[msg.sender] -= amount; // Silent underflow → infinite energy }}Real-world victims: BeautyChain (BEC) tokens → trillions minted via batchOverflow (2018)
Vulnerable Code - Post-0.8.0 with unchecked
pragma solidity ^0.8.24;
contract HulkRageToken { mapping(address => uint8) public rage;
function getAngry(uint8 amount) public { unchecked { rage[msg.sender] += amount; // Explicitly dangerous } }}pragma solidity ^0.8.24;
contract IronManSuit { mapping(address => uint256) public energy;
function drain(uint256 amount) public { unchecked { energy[msg.sender] -= amount; // From 0 to 2^256-1 } }}Still seen in 2025 when devs “optimize gas” with unchecked without validation.
Exploit - ThorBreaker.sol
contract ThorBreaker { function breakHulk(IHulk hulk) external { hulk.getAngry(250); hulk.getAngry(10); // 260 → 4 }
function breakIronMan(IIronMan suit) external { suit.drain(1); // 0 - 1 → MAX_UINT256 }}One transaction. Two Avengers defeated.
Safe Code - Solidity ≥0.8.0 (Default Checked)
pragma solidity ^0.8.24;
contract HulkRageTokenSafe { mapping(address => uint8) public rage;
function getAngry(uint8 amount) public { rage[msg.sender] += amount; // Reverts on overflow }}contract IronManSuitSafe { mapping(address => uint256) public energy;
function drain(uint256 amount) public { require(energy[msg.sender] >= amount, "Not enough energy"); energy[msg.sender] -= amount; }}Or even better - use OpenZeppelin SafeCast if downcasting.
Foundry Test - Watch Math Explode
function testHulkOverflow() public { hulk.getAngry(250); hulk.getAngry(10); assertEq(hulk.rage(address(this)), 4);}
function testIronManUnderflow() public { ironMan.drain(1); assertEq(ironMan.energy(address(this)), type(uint256).max);}Gas used: almost nothing
Damage: total
Auditor’s Math Checklist (2025)
Warning
- Contract uses Solidity
<0.8.0→ Immediate Critical - Any
unchecked {}block with +, -, *, ++, —? - Subtraction without
require(a >= b)? - Multiplication without SafeMath/SafeCast (especially uint256 * uint256)?
- Tested with 0, 1, MAX_UINT, MAX_UINT-1?
- Downcasting uint256 → smaller uint without SafeCast?
Quiz - Are You Worthy of Mjolnir?
Exercise (1. What happens when uint8(255) += 1 in unchecked block?)
a) Reverts
b) Becomes 256
c) Wraps to 0
d) Becomes -1
Answer
c) Wraps to 0 (255 + 1 = 256 → 0)
Exercise (2. Safest way to prevent underflow in 0.8+?)
a) Use unchecked
b) Remove unchecked and let it revert
c) Use int256
d) Use SafeMath
Answer
b) Let default checked arithmetic revert - or add explicit require
Exercise (3. What balance does 0 - 1 become in unchecked uint256?)
a) 0
b) Reverts
c) 2^256 - 1
d) -1
Answer
c) 2²⁵⁶ − 1 → infinite money glitch
Tip
All code + SVG + tests:
github.com/thesandf/thesandf.xyz
Thor didn’t need Stormbreaker.
He just needed unchecked {}.
Don’t be Hulk.
Don’t be Tony Stark.
Be Solidity 0.8+ with no unchecked.
Or Thor will break your math too. ⚡