Secure Solidity patterns for gas optimisation with unchecked arithmetic. Features production-ready code with circuit breakers, access controls, and event monitoring. Ideal for developers balancing gas efficiency with security in DeFi and Web3 applications. #ethereum #solidity #gas-optimization
A secure Solidity smart contract demonstrating safe usage of unchecked
arithmetic operations with multiple security patterns.
- Overview
- Security Features
- Contract Structure
- Function Documentation
- Use Cases
- Security Considerations
- Gas Optimization
- Testing Recommendations
The EnhancedSafeMathTester
contract showcases how to securely implement Solidity's unchecked
blocks for gas optimization while maintaining robust security guarantees. Since Solidity 0.8.0, arithmetic overflow/underflow checks are performed by default, but using unchecked
blocks allows bypassing these checks when appropriate to save gas.
This contract implements multiple layers of protection:
- Manual Overflow Validation - Pre-checks to mathematically verify operations won't overflow
- Bounded Operations - Logic constraints that keep values within safe ranges
- Circuit Breaker Pattern - Emergency stop functionality to pause operations if issues are detected
- Comprehensive Event Logging - Full audit trail of all value changes
- Access Control - Owner-restricted critical functions
- Informational Functions - Helpers to monitor proximity to dangerous values
// State Variables
uint8 public bigNumber = 225; // Main value we're operating on (uint8 max is 255)
bool public emergencyStop = false; // Circuit breaker flag
address public owner; // For access control
// Events
event ValueChanged(uint8 oldValue, uint8 newValue, string operation);
event EmergencyToggled(bool stopped);
// Modifiers
modifier onlyOwner() {...}
modifier whenNotStopped() {...}
// Functions - multiple approaches to safe unchecked arithmetic
Demonstrates basic unchecked increment without safety features. For educational purposes only.
function unsafeAdd() public whenNotStopped {
uint8 oldValue = bigNumber;
unchecked {
bigNumber += 1;
}
emit ValueChanged(oldValue, bigNumber, "unsafeAdd");
}
Implements manual overflow validation before performing unchecked addition.
function safeAdd(uint8 x) public whenNotStopped {
// Manual check before performing unchecked operation
require(bigNumber <= 255 - x, "Addition would overflow");
uint8 oldValue = bigNumber;
unchecked {
bigNumber += x;
}
emit ValueChanged(oldValue, bigNumber, "safeAdd");
}
Uses conditional logic to ensure value stays within safe bounds.
function boundedIncrement() public whenNotStopped {
uint8 oldValue = bigNumber;
unchecked {
// Ensure value stays within bounds
if (bigNumber < 255) {
bigNumber += 1;
}
}
emit ValueChanged(oldValue, bigNumber, "boundedIncrement");
}
Shows mathematical validation for more complex operations.
function doubleIfSafe() public whenNotStopped {
// Ensure doubling won't overflow
require(bigNumber <= 127, "Doubling would overflow");
uint8 oldValue = bigNumber;
unchecked {
bigNumber *= 2;
}
emit ValueChanged(oldValue, bigNumber, "doubleIfSafe");
}
Circuit breaker pattern to pause all operations if issues are detected.
function toggleEmergencyStop() public onlyOwner {
emergencyStop = !emergencyStop;
emit EmergencyToggled(emergencyStop);
}
Direct value setting with proper access control.
function setBigNumber(uint8 newValue) public onlyOwner whenNotStopped {
uint8 oldValue = bigNumber;
bigNumber = newValue;
emit ValueChanged(oldValue, bigNumber, "setBigNumber");
}
Provides the current value and how close it is to overflowing.
function getBigNumberStats() public view returns (uint8, uint8) {
return (bigNumber, type(uint8).max - bigNumber);
}
Returns the maximum possible value for the data type.
function getMaxValue() public pure returns (uint8) {
return type(uint8).max; // Returns 255 for uint8
}
Standard checked arithmetic for comparison - no unchecked
block.
function addWithCheck() public whenNotStopped {
uint8 oldValue = bigNumber;
// This will revert if bigNumber = 255
bigNumber += 1;
emit ValueChanged(oldValue, bigNumber, "addWithCheck");
}
This contract pattern is valuable for:
- Gas-Intensive Applications - When performing many arithmetic operations where gas costs matter
- Counter Systems - Where you know values won't reach upper/lower bounds
- Educational Purposes - Demonstrating safe vs. unsafe unchecked arithmetic
- Mathematical Libraries - When operations are proven safe by design
- Rate-Limited Operations - Where bounded increments within validated ranges are needed
- Token Distribution Systems - When distributing tokens to many accounts
- Voting Systems - For counting large numbers of votes efficiently
- Gaming Smart Contracts - For game mechanics with frequent incrementing
- DeFi Protocols - Where gas optimisation is critical for user affordability
When using unchecked
blocks:
- Always Add Validation - Either before the operation or within the block
- Document Thoroughly - Explain why the unchecked operation is safe
- Consider Value Ranges - Know the maximum possible values your variables can reach
- Use For Simple Operations - More complex calculations increase risk
- Test Edge Cases - Especially values at or near boundaries
Using unchecked
blocks can save approximately:
- 15-30 gas per addition/subtraction operation
- 40-60 gas per multiplication operation
- More for complex operations
This can add up significantly for contracts with many operations or high usage.
When implementing this pattern:
- Create unit tests specifically for overflow/underflow scenarios
- Test emergency stop functionality
- Verify events are emitted correctly
- Test access control restrictions
- Create fuzz tests with random inputs to verify bounds
Note: This contract uses a uint8 for demonstration purposes. Real-world applications typically use uint256, but the principles remain the same.