integer overflow and underflow
Integer overflow and underflow are wraparound bugs in fixed-width arithmetic. Like a car odometer that rolls from 999999 back to 000000, an EVM unsigned integer has a maximum value and silently wraps around it. The EVM's native word is 256 bits, so a uint256 can hold values up to 2^256 minus 1; add one more and it wraps to zero. Subtract one from zero and it underflows to the maximum value, an astronomically large number. When a contract treats that wrapped result as a real balance, money is created or destroyed out of thin air.
Before Solidity 0.8, the language did none of these checks, so developers relied on the SafeMath library to wrap every add, subtract, and multiply with an explicit bounds check. Forgetting it was a classic exploit: in 2018 the batchOverflow bug in several ERC-20 tokens let an attacker pass arguments whose product overflowed, minting huge balances. Underflow was just as dangerous, for example a transfer that subtracts an amount larger than your balance, leaving you with a near-infinite balance instead of reverting.
Solidity 0.8 and later make checked arithmetic the default: any overflow or underflow on +, -, *, and friends automatically reverts. Developers can opt back out with an unchecked { ... } block to save gas where they have proven a value cannot overflow. The catch is that the checks only apply to operations at the declared type's width; an explicit downcast such as uint128(x) or uint8(x) still truncates silently, and unchecked blocks fully restore the old footgun.
Fixed-width arithmetic is modular: it wraps around its range.
Upgrading to Solidity 0.8 does not make a contract overflow-proof. Downcasts (uint256 to uint64) truncate without reverting, and any code inside an unchecked block, common in gas-optimized libraries, is back to pre-0.8 behavior. Audit those spots specifically.