Checks-Effects-Interactions

Solidity security pattern ordering operations to validate inputs, update state, then make external calls preventing reentrancy vulnerabilities.

Checks-Effects-Interactions (CEI) is the fundamental Solidity security pattern that orders function operations to first validate all conditions (Checks), then update contract state (Effects), and finally make external calls (Interactions)—preventing reentrancy vulnerabilities by ensuring state is consistent before control transfers to potentially malicious external code. The article references this pattern implicitly through discussion of Uniswap V2's security design and explicitly addresses the reentrancy concern: "Uniswap V2 employs a lock modifier (mutex)" combined with careful state management to protect against exploitation.

The pattern emerged from early Ethereum smart contract exploits, most notably The DAO hack (2016) which drained $60 million through reentrancy. The vulnerable code made external calls before updating state, allowing recursive callbacks that withdrew funds multiple times against the same balance. CEI provides structural prevention: by updating state before external calls, recursive invocations see correct (already-updated) state, eliminating the attack vector. This pattern became foundational security practice—alongside reentrancy guards—for any contract making external calls.

Pattern Structure and Ordering

Checks phase validates all preconditions. At function start: verify caller authorization (access control), validate input parameters (ranges, types, sanity), check state requirements (sufficient balance, valid state), and ensure external conditions (oracle data freshness, time locks). Any violation: revert immediately before state changes or external calls. This ensures: only valid operations proceed, no partial execution with invalid inputs, and clear error conditions for callers.

Effects phase updates all contract state. After validation passes: modify storage variables (balances, mappings, counters), update internal accounting (totals, reserves, positions), emit events (audit trail, indexing), and finalize state changes. Critical rule: complete ALL state updates before ANY external interaction. This ensures: state is consistent when control might transfer externally, recursive calls see updated state, and no state changes depend on external call outcomes.

Interactions phase makes external calls last. Only after state is finalized: transfer tokens (ERC20.transfer, safeTransfer), call external contracts (callbacks, integrations), and interact with other protocols (swaps, loans). If external call fails: state already updated (may need careful design for reversibility), but reentrancy is prevented (state consistent). If external call reenters: sees updated state, cannot exploit stale values.

Reentrancy Prevention Mechanics

Why ordering prevents reentrancy fundamentally. Classic reentrancy attack: contract calls attacker, attacker calls back before state updates, second call sees original (stale) state, and attacker drains funds through repeated exploitation. With CEI: contract updates state first, then calls attacker, attacker calls back and sees updated state, and no stale state to exploit. The attack vector is structurally eliminated.

State consistency guarantee throughout execution. When external call occurs: all internal state reflects current reality, view functions return accurate data, and any reentrant call operates on consistent state. This consistency means: calculations based on state are accurate, balance checks reflect actual holdings, and invariants hold at interaction time.

Defense-in-depth with reentrancy guards provides additional protection. CEI prevents single-function reentrancy but cross-function reentrancy (callback calls different function) may still be possible. Combining CEI with reentrancy guard (mutex lock) provides: structural prevention (CEI pattern), runtime prevention (lock blocks reentry), and multiple layers protecting against developer errors.

CEI Pattern Examples

Withdrawal function demonstrating correct pattern:

1function withdraw(uint256 amount) external {
2 // CHECKS
3 require(amount > 0, "Zero amount");
4 require(balances[msg.sender] >= amount, "Insufficient balance");
5
6 // EFFECTS
7 balances[msg.sender] -= amount;
8 totalDeposits -= amount;
9 emit Withdrawal(msg.sender, amount);
10
11 // INTERACTIONS
12 (bool success, ) = msg.sender.call{value: amount}("");
13 require(success, "Transfer failed");
14}

State updates complete before external call. If msg.sender is malicious contract that reenters: balances[msg.sender] already reduced, second withdrawal attempt fails "Insufficient balance" check, and no double-withdrawal possible.

Vulnerable pattern (anti-pattern to avoid):

1function withdrawUnsafe(uint256 amount) external {
2 require(balances[msg.sender] >= amount);
3
4 // INTERACTION BEFORE EFFECTS - VULNERABLE!
5 (bool success, ) = msg.sender.call{value: amount}("");
6 require(success);
7
8 // Effects after interaction - too late!
9 balances[msg.sender] -= amount;
10}

Attacker's receive function calls withdrawUnsafe again. Second call: balances[msg.sender] still shows original amount (not yet decremented), check passes, and withdrawal succeeds again. Repeat until contract drained.

CEI in Complex Protocols

Multi-step operations require careful CEI application. When function involves: multiple state changes across variables, multiple external calls to different contracts, or conditional state updates—CEI still applies: complete ALL checks first (validate entire operation), complete ALL effects next (update all state), and complete ALL interactions last (make all external calls).

Uniswap V2 swap pattern demonstrates CEI in AMM context. The article shows the flow: check amounts valid ("require(amount0Out > 0 || amount1Out > 0)"), cache reserves (prepare for effects), make optimistic transfers (interactions—but before final state), execute callback (more interactions), verify invariant on actual balances (final check), and update reserves (effects). This slightly modifies CEI for optimistic transfer pattern but maintains state consistency guarantees through final invariant verification.

Flash loan patterns adapt CEI for atomic operations. Flash loans must: send tokens (interaction), allow usage (callback interaction), verify repayment (check), and update state (effect). The pattern adapts: some interactions occur early, but final verification ensures correctness, and atomic revert guarantees consistency. This demonstrates CEI as guideline rather than rigid rule—the goal (state consistency at trust boundaries) matters more than exact ordering when atomicity is guaranteed.

CEI Limitations and Edge Cases

Read-only reentrancy not prevented by CEI alone. The article discusses: "Read-Only Reentrancy remains a risk" even with proper CEI. During external call: state IS updated per CEI, but view functions may return values inconsistent with actual token balances (if reserves updated but balances not yet synchronized). External contracts reading this state may make incorrect decisions. CEI protects the calling contract but not integrating protocols reading stale-appearing data.

Cross-function reentrancy may bypass simple CEI. If contract has: function A (updates state X, calls external), function B (reads state Y, affected by X), and callback from A calls B—B may see incomplete state if X and Y not updated atomically. Solution: reentrancy guard covering all related functions, or ensuring CEI applies across the entire related state set.

Callback-dependent state complicates CEI. When state update depends on callback result: classic CEI cannot apply (effects need interaction result), callback-then-effects creates reentrancy risk, and complex patterns like try-catch or two-phase commits may be needed. These scenarios require: careful analysis, explicit reentrancy protection, and potentially alternative patterns (pull over push, commit-reveal).

CEI Best Practices

Group all checks at function start for clarity and safety. Don't interleave checks with effects: validate everything first, fail fast on any invalid condition, and make validation logic clear and auditable. This also improves gas efficiency—reverting early avoids wasted computation.

Complete all effects before any interaction without exception. Even if "just one small call" seems safe: external calls are never truly safe, patterns develop by being consistent, and auditors expect CEI compliance. Any deviation requires explicit justification and additional protections (reentrancy guards, invariant verification).

Use SafeERC20 for token interactions preventing transfer failures. ERC20 transfers may: return false instead of reverting (some tokens), revert unexpectedly (blacklisting tokens), or transfer less than requested (fee tokens). SafeERC20 wrapper: checks return values, handles non-reverting failures, and integrates cleanly with CEI pattern.

Document CEI compliance in code comments. Mark each phase explicitly: // === CHECKS ===, // === EFFECTS ===, // === INTERACTIONS ===. This helps: auditors verify pattern compliance, developers maintain pattern during modifications, and code review identify violations.

CEI in Modern Solidity

Solidity 0.8+ changes affect CEI implementation. Built-in overflow protection: eliminates some checks (arithmetic safety), but explicit validation still needed for business logic. Custom errors: improve check phase with gas-efficient, informative reverts. Try-catch: enables interaction error handling without breaking CEI (catch failures, maintain state consistency).

Transient storage (EIP-1153) enables new patterns. For within-transaction state: track operation flags transiently, verify at transaction end, and combine with CEI for enhanced protection. Flash accounting patterns in Uniswap V4 and Balancer V3 leverage this for efficient CEI-compliant implementations.

Reentrancy guard libraries complement CEI. OpenZeppelin's ReentrancyGuard: provides battle-tested mutex implementation, nonReentrant modifier for easy application, and combines with CEI for defense-in-depth. Standard practice: use both CEI pattern AND reentrancy guard for critical functions.

CEI Auditing Checklist

Verify check completeness first. All input parameters validated? All state preconditions verified? All authorization checks present? Access control properly enforced? Missing checks: potential for invalid operations, state corruption, or unauthorized actions.

Verify effect ordering second. All storage writes before external calls? All events emitted before interactions? State consistent before control transfers? Any effects after interactions require justification and additional protections.

Verify interaction safety third. All external calls use safe patterns (SafeERC20, etc.)? Return values checked? Failure cases handled? Reentrancy guards present for critical functions? Callback behavior analyzed?

Check cross-function consistency fourth. Related functions protected together? Shared state updated atomically? Cross-function reentrancy considered? The article's methodology—isolating components and analyzing boundaries—supports this analysis.

Understanding Checks-Effects-Interactions is fundamental for secure smart contract development. The pattern provides structural reentrancy prevention by ensuring state consistency before any external interaction—eliminating the attack vector that enabled early DeFi's most catastrophic exploits. The article's discussion of Uniswap V2 security demonstrates CEI principles in production: state protected via mutex, optimistic transfers verified through invariant checks, and careful ordering preventing exploitation. For auditors, CEI compliance is baseline expectation—violations are red flags requiring immediate attention. For developers, consistent CEI application (combined with reentrancy guards for defense-in-depth) prevents entire vulnerability classes. The pattern's simplicity—checks, then effects, then interactions—belies its importance: proper CEI compliance would have prevented hundreds of millions in historical DeFi losses.

Need expert guidance on Checks-Effects-Interactions?

Our team at Zealynx has deep expertise in blockchain security and DeFi protocols. Whether you need an audit or consultation, we're here to help.

Get a Quote

oog
zealynx

Subscribe to Our Newsletter

Stay updated with our latest security insights and blog posts

© 2024 Zealynx