Solidity Cheatsheet
Modifiers
Use this Solidity reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Modifier Syntax
modifier <name>(<params>) { // pre-condition code _; // placeholder for the modified function body // post-condition code (optional) }
modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; // function body runs here } function withdraw() external onlyOwner { payable(owner).transfer(address(this).balance); }
Modifier with Parameters
modifier costs(uint256 price) { require(msg.value >= price, "Insufficient payment"); _; // refund excess if (msg.value > price) { payable(msg.sender).transfer(msg.value - price); } } function mint() external payable costs(0.05 ether) { _mintToken(msg.sender); }
Chaining Modifiers
Modifiers execute left to right, each wrapping the next.
modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } modifier whenNotPaused() { require(!paused, "Paused"); _; } modifier nonReentrant() { require(!_entered, "Reentrant"); _entered = true; _; _entered = false; } // Execution order: onlyOwner → whenNotPaused → nonReentrant → function body function criticalAction() external onlyOwner whenNotPaused nonReentrant { // ... }
_; Placement
// Pre-check only (most common) modifier onlyAdmin() { require(isAdmin[msg.sender]); _; } // Post-check (rare — runs after function body) modifier checkBalance() { _; require(address(this).balance >= reserve, "Below reserve"); } // Wrapping — both pre and post modifier timed() { uint start = block.timestamp; _; emit Elapsed(block.timestamp - start); } // Can place _; multiple times (function body runs each time) modifier twice() { _; _; }
Common Modifier Patterns
Access Control
address public owner; modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } function transferOwnership(address newOwner) external onlyOwner { require(newOwner != address(0), "Zero address"); owner = newOwner; }
Pausable
bool public paused; modifier whenNotPaused() { require(!paused, "Contract paused"); _; } modifier whenPaused() { require(paused, "Not paused"); _; } function pause() external onlyOwner whenNotPaused { paused = true; } function unpause() external onlyOwner whenPaused { paused = false; }
Reentrancy Guard
uint256 private _status = 1; // use 1/2 instead of false/true to avoid cold SSTORE modifier nonReentrant() { require(_status != 2, "ReentrancyGuard: reentrant call"); _status = 2; _; _status = 1; }
Since Solidity 0.8.24, you can use
transientstorage for reentrancy locks — cheaper (EIP-1153):
uint256 transient private _entered; modifier nonReentrant() { require(_entered == 0); _entered = 1; _; // auto-cleared at end of transaction }
Rate Limiting / Cooldown
mapping(address => uint256) public lastCall; modifier cooldown(uint256 period) { require(block.timestamp >= lastCall[msg.sender] + period, "Cooldown active"); lastCall[msg.sender] = block.timestamp; _; } function claim() external cooldown(1 days) { // ... }
Validation
modifier validAddress(address addr) { require(addr != address(0), "Zero address"); _; } modifier validAmount(uint256 amount) { require(amount > 0, "Amount must be > 0"); require(amount <= maxAmount, "Amount too large"); _; } function send(address to, uint256 amount) external validAddress(to) validAmount(amount) { _transfer(to, amount); }
Modifiers in Inheritance
contract Ownable { address public owner; modifier onlyOwner() virtual { require(msg.sender == owner, "Not owner"); _; } } contract MultiSig is Ownable { // Override to require multisig instead modifier onlyOwner() override { require(isApproved(msg.sig), "Not approved"); _; } }
Custom Errors vs require in Modifiers
// Using custom errors (cheaper — no string storage) error Unauthorized(address caller); error ContractPaused(); modifier onlyOwner() { if (msg.sender != owner) revert Unauthorized(msg.sender); _; } modifier whenNotPaused() { if (paused) revert ContractPaused(); _; }
Gotchas
- Modifiers cannot return values — they can only gate execution or emit events.
- The function's return values pass through the modifier stack unchanged.
- Avoid complex logic in modifiers — keep them readable; put logic in
internalfunctions called from the modifier. - Modifiers are inlined by the compiler — each use adds bytecode.
- A modifier with
_;after areturnin the pre-check: the function body still runs (thereturnin a modifier only exits the modifier's preamble block, not the function).
// WRONG mental model modifier earlyReturn() { if (someFlag) return; // exits modifier but function body still runs! _; } // CORRECT pattern modifier earlyReturn() { if (!someFlag) _; // only run function body when flag is false }