Solidity Cheatsheet
Error Handling
Use this Solidity reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Three Mechanisms
| Mechanism | Gas refund | ABI-encoded | Recommended use |
|---|---|---|---|
revert(string) | yes (unused gas) | string message | legacy |
require(cond, string) | yes | string message | input validation |
assert(cond) | no (pre-0.8) / yes (0.8+) | panic code | invariants |
revert CustomError(...) | yes | custom ABI | modern (cheapest) |
require
// Basic require(amount > 0, "Amount must be positive"); require(msg.sender == owner, "Not owner"); require(balances[msg.sender] >= amount, "Insufficient balance"); // Without message (saves gas — no string in bytecode) require(amount > 0); // With complex condition require( block.timestamp >= startTime && block.timestamp <= endTime, "Sale not active" );
- Refunds remaining gas to caller.
- Typically used for: input validation, access control, preconditions.
- Using
requirewithout a string saves ~200 gas (no string in bytecode).
revert
// With string (legacy) revert("Something went wrong"); // With custom error (preferred since 0.8.4) error Unauthorized(address caller, bytes32 role); error InsufficientBalance(uint256 requested, uint256 available); function withdraw(uint256 amount) external { if (msg.sender != owner) revert Unauthorized(msg.sender, ADMIN_ROLE); if (balances[msg.sender] < amount) revert InsufficientBalance(amount, balances[msg.sender]); balances[msg.sender] -= amount; payable(msg.sender).transfer(amount); }
Custom Errors (0.8.4+)
// Declared at file level or inside contract error ZeroAddress(); error InvalidAmount(uint256 got, uint256 min, uint256 max); error TransferFailed(address token, address from, address to); contract Vault { function deposit(uint256 amount) external { if (amount == 0) revert ZeroAddress(); // smallest: 4 bytes on wire // ... } }
Why custom errors:
- Encoded as 4-byte selector + ABI-encoded args — much cheaper than strings.
- Readable in ethers.js via error.errorName / decoded args.
- Can carry structured data for debugging.
// Decoding in ethers.js try { await contract.withdraw(amount); } catch (e) { if (e.errorName === "InsufficientBalance") { console.log(e.errorArgs.requested, e.errorArgs.available); } }
assert
// For internal invariants that should NEVER fail function transfer(address to, uint256 amount) external { uint256 totalBefore = totalSupply; balances[from] -= amount; balances[to] += amount; assert(totalSupply == totalBefore); // invariant check }
- In 0.8+:
assertusesREVERTopcode (likerequire) — refunds unused gas. Previously usedINVALID(consumed all gas). - Produces Panic error code, not a string.
- Failing
assertindicates a bug in your code, not a user error.
Panic Codes
| Code | Cause |
|---|---|
0x00 | Generic panic (assert in < 0.8) |
0x01 | assert(false) |
0x11 | Arithmetic overflow/underflow |
0x12 | Division or modulo by zero |
0x21 | Invalid enum value conversion |
0x22 | Incorrect storage byte array encoding |
0x31 | pop() on empty array |
0x32 | Index out of bounds |
0x41 | Too much memory allocated |
0x51 | Call to a zero-initialized function variable |
try / catch
Used for external function calls and contract creation. Cannot catch internal call failures.
interface IOracle { function getPrice() external view returns (uint256); } contract Consumer { function safeGetPrice(address oracle) external view returns (uint256 price, bool ok) { try IOracle(oracle).getPrice() returns (uint256 p) { return (p, true); } catch Error(string memory reason) { // revert("message") or require(false, "message") emit Failed(reason); return (0, false); } catch Panic(uint256 code) { // assert failure, overflow, etc. emit PanicCode(code); return (0, false); } catch (bytes memory lowLevelData) { // Custom error or unknown revert data emit Unknown(lowLevelData); return (0, false); } } }
try with Contract Deployment
contract Factory { event Deployed(address addr); event DeployFailed(string reason); function deploy(bytes32 salt) external { try new MyContract{salt: salt}() returns (MyContract c) { emit Deployed(address(c)); } catch Error(string memory reason) { emit DeployFailed(reason); } } }
try Gotchas
- Only works for external calls (including
this.func()). - Does not catch failures inside the
trysuccess branch — those propagate normally. - If the called contract returns successfully but
returnsdecode fails, it goes to the low-levelcatch.
Overflow and Underflow (0.8+)
uint256 x = type(uint256).max; x += 1; // REVERTS with Panic(0x11) in 0.8+ // Opt out for gas savings when you know it's safe unchecked { uint256 wrapped = x + 1; // 0, no revert for (uint i; i < 100; ++i) { } // unchecked loop counter saves ~50 gas/iter }
Use
uncheckedonly when you have mathematically proven no overflow is possible.
Safe Math Pattern (Pre-0.8 / Libraries)
// No longer needed in 0.8+ for basic arithmetic // OpenZeppelin SafeMath is deprecated for 0.8+ contracts // Only use unchecked when you need the old wrapping behavior
Graceful Degradation Pattern
// Instead of reverting, return a sentinel value function tryGetPrice(address feed) internal view returns (uint256) { try AggregatorV3Interface(feed).latestRoundData() returns (uint80, int256 price, uint256, uint256 updatedAt, uint80) { if (price <= 0) return 0; if (block.timestamp - updatedAt > 1 hours) return 0; // stale return uint256(price); } catch { return 0; } }
Custom Error Inheritance
// Errors can be defined in interfaces and inherited interface IErrors { error Unauthorized(address caller); error InvalidInput(string field); } contract MyContract is IErrors { function restricted() external { if (msg.sender != owner) revert Unauthorized(msg.sender); } }
Bubble Up vs Wrap Errors
// Bubble up — re-throw the original revert (bool ok, bytes memory data) = target.call(callData); if (!ok) { assembly { revert(add(data, 32), mload(data)) } } // Wrap — add context (bool ok2, ) = target.call(callData); require(ok2, "External call to target failed");