# Integer Overflow और Underflow — SafeMath से पहले और बाद
Integer overflow और underflow vulnerabilities ने early smart contracts में havoc मचाया था। Solidity 0.8.0 (February 2021) ने built-in overflow checking introduce की, जो इन bugs को largely eliminate कर देती है — लेकिन legacy code और unchecked blocks अभी भी risks pose करते हैं।
इस guide में, हम explore करेंगे कि ये vulnerabilities कैसे काम करती थीं, SafeMath pattern, और modern Solidity में best practices।
Integer Overflow/Underflow क्या है?
Overflow तब होती है जब एक arithmetic operation का result उस type के maximum value से बड़ा होता है।
Underflow तब होती है जब result minimum value से छोटा होता है।
Example: uint8 (0 से 255)
uint8 x = 255;
x = x + 1; // Overflow: 255 + 1 = 0 (wraps around)
uint8 y = 0;
y = y - 1; // Underflow: 0 - 1 = 255 (wraps around)
Solidity 0.7 और पहले में, यह silently होता था — कोई error नहीं, बस incorrect values।
Pre-0.8 Vulnerability Example
// Solidity 0.7 (VULNERABLE)
pragma solidity ^0.7.0;
contract VulnerableToken {
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) external {
// No overflow check!
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount; // Could underflow
balances[to] += amount; // Could overflow
}
function batchTransfer(address[] calldata recipients, uint256 amount) external {
// CRITICAL VULNERABILITY
uint256 totalAmount = recipients.length * amount; // Overflow possible!
require(balances[msg.sender] >= totalAmount, "Insufficient balance");
for (uint i = 0; i < recipients.length; i++) {
balances[recipients[i]] += amount;
}
balances[msg.sender] -= totalAmount;
}
}
Attack Scenario
// Attacker calls:
address[] memory recipients = new address[](2);
recipients[0] = attacker1;
recipients[1] = attacker2;
// amount = 2^255 (very large number)
// recipients.length = 2
// totalAmount = 2 * 2^255 = 2^256 = 0 (OVERFLOW!)
token.batchTransfer(recipients, 2**255);
Result:
totalAmount = 0(overflow)
require(balances[msg.sender] >= 0)passes
- दोनों recipients को
2^255tokens मिलते हैं
- Attacker का balance unchanged (0 deducted)
- Unlimited tokens minted!
यह BeautyChain (BEC) token hack में exactly यही हुआ था (April 2018) — attacker ने unlimited tokens mint किए, token worthless हो गया।
SafeMath Library (Pre-0.8 Solution)
OpenZeppelin का SafeMath library हर operation पर overflow/underflow check करता था।
// Solidity 0.7
pragma solidity ^0.7.0;
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction underflow");
return a - b;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
return a / b;
}
}
Using SafeMath
pragma solidity ^0.7.0;
import "@openzeppelin/contracts/math/SafeMath.sol";
contract SafeToken {
using SafeMath for uint256;
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] = balances[msg.sender].sub(amount); // Safe
balances[to] = balances[to].add(amount); // Safe
}
function batchTransfer(address[] calldata recipients, uint256 amount) external {
uint256 totalAmount = recipients.length.mul(amount); // Reverts on overflow
require(balances[msg.sender] >= totalAmount);
for (uint i = 0; i < recipients.length; i++) {
balances[recipients[i]] = balances[recipients[i].add(amount);
}
balances[msg.sender] = balances[msg.sender].sub(totalAmount);
}
}
अब overflow/underflow होने पर transaction revert होगा।
Solidity 0.8+ Built-In Protection
Solidity 0.8.0 से, overflow/underflow checks automatically enforce होते हैं।
// Solidity 0.8+
pragma solidity ^0.8.0;
contract ModernToken {
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) external {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // Reverts on underflow
balances[to] += amount; // Reverts on overflow
}
function batchTransfer(address[] calldata recipients, uint256 amount) external {
uint256 totalAmount = recipients.length * amount; // Reverts on overflow
require(balances[msg.sender] >= totalAmount);
for (uint i = 0; i < recipients.length; i++) {
balances[recipients[i]] += amount;
}
balances[msg.sender] -= totalAmount;
}
}
No SafeMath needed! Compiler automatically checks insert करता है।
unchecked Block — Gas Optimization
कभी-कभी आप deliberately overflow checking disable करना चाहते हैं (gas save करने के लिए)।
pragma solidity ^0.8.0;
contract UncheckedExample {
function safeIncrement(uint256 x) external pure returns (uint256) {
// Overflow check included (costs more gas)
return x + 1;
}
function unsafeIncrement(uint256 x) external pure returns (uint256) {
// No overflow check (saves gas)
unchecked {
return x + 1;
}
}
function loopWithUnchecked() external pure {
uint256 sum = 0;
// Loop counters never overflow in practice
for (uint256 i = 0; i < 100; unchecked { i++ }) {
sum += i;
}
}
}
When to Use unchecked
✅ Safe use cases:
- Loop counters (जब आप जानते हैं कि वे overflow नहीं होंगे)
- Calculations जहां overflow mathematically impossible है
- Gas-critical code जहां आपने manually verify किया है
❌ Never use for:
- User inputs
- Balances/amounts
- कोई भी calculation जो overflow हो सकता है
Common Patterns और Pitfalls
Pattern 1: Timestamp Overflow (Less Common Now)
// Old code (Solidity 0.7)
uint256 deadline = block.timestamp + 30 days; // Could overflow (theoretically)
// Modern (Solidity 0.8+)
uint256 deadline = block.timestamp + 30 days; // Reverts on overflow
block.timestamp एक uint256 है, तो overflow लगभग impossible है (universe का end से पहले नहीं होगा), लेकिन 0.8+ में यह automatically protected है।
Pattern 2: Safe Subtraction Before Division
// VULNERABLE (0.7)
function calculateFee(uint256 amount) external pure returns (uint256) {
uint256 feeRate = 100; // 1%
return (amount * feeRate) / 10000; // Multiplication could overflow
}
// SAFE (0.8+)
function calculateFee(uint256 amount) external pure returns (uint256) {
uint256 feeRate = 100;
return (amount * feeRate) / 10000; // Automatically reverts on overflow
}
Pattern 3: Casting Downcasts
pragma solidity ^0.8.0;
function downcast(uint256 x) external pure returns (uint128) {
// This does NOT automatically revert on overflow!
return uint128(x); // Truncates higher bits
}
function safeDowncast(uint256 x) external pure returns (uint128) {
require(x <= type(uint128).max, "Downcast overflow");
return uint128(x);
}
⚠️ Important: Type casting does not trigger overflow checks — manually verify करें!
Testing for Overflow/Underflow
Foundry Test Example
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
contract OverflowTest is Test {
function testBuiltInOverflowProtection() public {
uint256 max = type(uint256).max;
// This should revert
vm.expectRevert();
uint256 overflow = max + 1;
}
function testBuiltInUnderflowProtection() public {
uint256 zero = 0;
// This should revert
vm.expectRevert();
uint256 underflow = zero - 1;
}
function testUncheckedOverflow() public {
uint256 max = type(uint256).max;
unchecked {
uint256 wrapped = max + 1;
assertEq(wrapped, 0); // Wraps to 0
}
}
}
Migration Guide: 0.7 → 0.8
// OLD (Solidity 0.7)
pragma solidity ^0.7.0;
import "@openzeppelin/contracts/math/SafeMath.sol";
contract OldToken {
using SafeMath for uint256;
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) external {
balances[msg.sender] = balances[msg.sender].sub(amount);
balances[to] = balances[to].add(amount);
}
}
// NEW (Solidity 0.8+)
pragma solidity ^0.8.0;
contract NewToken {
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) external {
balances[msg.sender] -= amount; // No SafeMath needed
balances[to] += amount;
}
}
Breaking Changes in 0.8
- Arithmetic operations revert on overflow/underflow
**(exponentiation) भी checked है
- Type conversions require explicit handling
Best Practices Checklist
- ✅ Solidity 0.8+ use करें built-in overflow protection के लिए
- ✅
uncheckedकेवल तभी use करें जब mathematically safe हो
- ✅ Type casting को manually validate करें
- ✅ Legacy code को 0.8+ में migrate करें
- ✅ हमेशा test करें edge cases (max values, zero, etc.)
- ✅ Fuzzing tools use करें (Echidna, Foundry) overflow scenarios discover करने के लिए
- ✅ Audit reports में overflow/underflow coverage verify करें
Real-World Examples
1. BeautyChain (BEC) Token — April 2018
batchTransferमें overflow vulnerability
- Attacker ने unlimited tokens mint किए
- Token value → 0
- Loss: Company destroyed
2. SmartMesh (SMT) Token — April 2018
- Similar
batchTransferoverflow
- Immediately after BEC incident
- Token trading suspended
3. PoWHC (Ponzi Scheme) — 2018
- Underflow bug में
sell()function
- Attacker ne 866 ETH drain किए
- Contract abandoned
Common thread: सभी Solidity 0.7 या earlier में थे, SafeMath use नहीं किया गया था।
Conclusion
Integer overflow/underflow vulnerabilities ने early DeFi को plague किया था, लेकिन Solidity 0.8 ने game change कर दिया।
Key takeaways:
- Solidity 0.8+ में automatic overflow/underflow protection है
- SafeMath अब redundant है (0.8+ में)
uncheckedblocks carefully use करें
- Type casting manually validate करें
- Legacy contracts को migrate करें या carefully audit करें
- Always test edge cases
Solingo पर, आप real historical vulnerabilities के साथ integer overflow bugs को identify और fix करना सीखेंगे। Solidity 0.7 और 0.8+ दोनों में practice करें।
अभी शुरू करें: solingo.io/security/integer-bugs