---
eip: 8219
title: Checked Arithmetic Opcodes
description: Adds opcodes for addition, subtraction, multiplication, and division that revert on overflow, underflow, or division by zero
author: Hubert Ritzdorf (@ritzdorf)
discussions-to: https://ethereum-magicians.org/t/eip-8219-checked-arithmetic-opcodes/27913
status: Draft
type: Standards Track
category: Core
created: 2026-04-08
---

## Abstract

This EIP introduces four new opcodes — `SAFEADD` (`0x0c`), `SAFESUB` (`0x0d`), `SAFEMUL` (`0x0e`), and `SAFEDIV` (`0x0f`) — that perform unsigned 256-bit arithmetic with built-in overflow, underflow, and division-by-zero checking. On error, these opcodes revert the current call frame with empty returndata, equivalent to `REVERT(0, 0)`. A single `SAFEADD` instruction replaces the 11-instruction compiler-generated overflow check pattern, reducing checked addition cost from ~79 gas to 5 gas and from 22 bytes to 1 byte of bytecode.

## Motivation

### The Checked Arithmetic Overhead

High-level EVM languages emit overflow checks by default: Solidity since v0.8.0, Vyper since inception. These checks are essential for correctness, but the EVM provides no native support for them, forcing compilers to synthesize multi-instruction check patterns around every arithmetic operation.

A typical checked addition compiles to something like:

1. (Jump to safe add)
2. Duplicate operand
3. Perform Addition
4. Check for overflow 
5. (Jump back)

This contains at least one conditional jump and potentially multiple more jumps. Benchmarks conducted with Solidity 0.8.33 (optimizer enabled, 200 runs) and Vyper 0.4.3 on equivalent ERC-20 contracts isolating a single checked addition yield the following overhead:

| Metric | Solidity 0.8.33 | Vyper 0.4.3 | With `SAFEADD` |
|---|---|---|---|
| Execution gas per checked add | ~79 (3 + 76) | ~41 (3 + 38) | 5 |
| Bytecode per checked add | 22 bytes | 26 bytes | 1 byte |
| Deployment gas per checked add | +4,704 | +5,208 | ~200 |
| Gas reduction | 93.7% | 87.8% | — |

The raw `ADD` opcode costs 3 gas. A compiler-checked addition costs approximately 79 gas — a **25x multiplier** purely for safety. This overhead grows across every arithmetic operation in every function in every contract on the network.

### The Dangerous Trade-off 

Because checked arithmetic is expensive, developers and auditors face constant pressure to bypass it. Solidity provides `unchecked {}` blocks; Vyper provides `unsafe_add()`, `unsafe_sub()`, and related builtins. While these are sometimes appropriate (e.g., loop counters that provably cannot overflow), their availability creates a dangerous trade-off: developers use unchecked arithmetic to save gas even when safety is not proven. 

This incentive has leads to:

- Less readable/auditable code, full of `unchecked`/`unsafe`.
- Real exploits where unchecked subtraction was exploited. 

Native checked arithmetic opcodes eliminate this trade-off. When a safe addition costs only 2 gas more than a raw addition (5 vs 3) and the bytecode size does not increase, there is no meaningful reason to opt out of overflow protection.

### Comparison with Prior EIPs 

Two prior EIPs have proposed overflow detection at the EVM level:

- **[EIP-1051](./eip-1051.md): Overflow Checking for the EVM** — Introduces `ovf` and `sovf` flags set by existing arithmetic opcodes, with `OFV` (`0x0c`) and `SOVF` (`0x0d`) opcodes to check and clear them.
- **[EIP-6888](./eip-6888.md): Arithmetic Verification at EVM Level** — Introduces `carry` and `overflow` flags with `JUMPC` and `JUMPO` opcodes for conditional jumps.

Both proposals share the same fundamental limitation: they still require multi-instruction patterns. After each arithmetic operation, the contract must execute a flag-read opcode and a conditional jump — at minimum 3 instructions instead of 1. Additionally, both introduce implicit EVM state (flags) that persists across instructions, complicating static analysis, formal verification, and compiler optimization. Flags also create subtle ordering dependencies: if a contract performs two additions and only checks the flag once, the first overflow is silently lost.

This EIP takes a different approach: each safe opcode is a self-contained, stateless instruction that either produces a correct result or reverts. No flags, no implicit state, no multi-instruction patterns. One opcode does the job of many.

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174).

### Parameters

| Constant | Value |
|---|---|
| `FORK_BLOCK` | TBD |

### Overview

Four new opcodes are introduced in the range `0x0c`–`0x0f`, immediately following the existing arithmetic opcode group (`ADD` through `SIGNEXTEND`, `0x01`–`0x0b`). These slots are currently unassigned.

| Opcode | Mnemonic | Gas | Description |
|---|---|---|---|
| `0x0c` | `SAFEADD` | 5 | Checked unsigned addition; reverts on overflow |
| `0x0d` | `SAFESUB` | 5 | Checked unsigned subtraction; reverts on underflow |
| `0x0e` | `SAFEMUL` | 7 | Checked unsigned multiplication; reverts on overflow |
| `0x0f` | `SAFEDIV` | 7 | Checked unsigned division; reverts on division by zero |

### Revert Behavior

When any safe arithmetic opcode detects an error condition (overflow, underflow, or division by zero), execution MUST revert the current call frame with empty returndata. This is semantically equivalent to executing `REVERT(0, 0)`.

### `SAFEADD` (`0x0c`)

#### Input stack

| Stack | Value |
|---|---|
| top - 0 | `a` |
| top - 1 | `b` |

#### Output stack

| Stack | Value |
|---|---|
| top - 0 | `a + b` |

#### Pseudocode

```
result = a + b  (mod 2^256)
if result < a:
    revert(0, 0)
push result
```

#### Error condition

Reverts if `a + b > 2^256 - 1` (unsigned overflow).

### `SAFESUB` (`0x0d`)

#### Input stack

| Stack | Value |
|---|---|
| top - 0 | `a` |
| top - 1 | `b` |

#### Output stack

| Stack | Value |
|---|---|
| top - 0 | `a - b` |

Stack ordering matches the existing `SUB` opcode: `a` is on top, `b` is below, and the result is `a - b`.

#### Pseudocode

```
if b > a:
    revert(0, 0)
push a - b
```

#### Error condition

Reverts if `b > a` (unsigned underflow).

### `SAFEMUL` (`0x0e`)

#### Input stack

| Stack | Value |
|---|---|
| top - 0 | `a` |
| top - 1 | `b` |

#### Output stack

| Stack | Value |
|---|---|
| top - 0 | `a * b` |

#### Pseudocode

```
if a == 0:
    push 0
else:
    result = a * b  (mod 2^256)
    if result / a != b:
        revert(0, 0)
    push result
```

#### Error condition

Reverts if `a != 0` and `a * b > 2^256 - 1` (unsigned overflow). When `a == 0`, the result is 0 regardless of `b`, with no revert.

### `SAFEDIV` (`0x0f`)

#### Input stack

| Stack | Value |
|---|---|
| top - 0 | `a` |
| top - 1 | `b` |

#### Output stack

| Stack | Value |
|---|---|
| top - 0 | `a / b` |

Stack ordering matches the existing `DIV` opcode: `a` is the dividend (on top), `b` is the divisor.

#### Pseudocode

```
if b == 0:
    revert(0, 0)
push a / b  (unsigned integer division, truncating)
```

#### Error condition

Reverts if `b == 0` (division by zero). Unsigned integer division cannot overflow, so overflow is not checked.

## Rationale

### Why Direct-Revert Over Flags

Both [EIP-1051](./eip-1051.md) and [EIP-6888](./eip-6888.md) proposed flag-based approaches where existing arithmetic opcodes set implicit flags that must be checked by separate opcodes. This design has several drawbacks:

1. **Multi-instruction overhead.** Checking a flag still requires at least two additional instructions (read flag + conditional jump), reducing but not eliminating the checked arithmetic overhead.
2. **Implicit state.** Flags introduce hidden state that persists between instructions. This complicates static analysis, formal verification, and optimization passes in compilers.
3. **Silent flag loss.** If multiple arithmetic operations execute between flag checks, earlier overflow conditions are silently overwritten. This creates a subtle class of bugs in both hand-written assembly and compiler output.

Direct-revert opcodes are stateless, self-contained, and cannot silently lose error conditions. They also provide the maximum possible gas savings, since no additional instructions are needed.

### Revert Instead of Exceptional Halt

Exceptional halts inside the EVM consume all remaining gas. These opcodes instead trigger a revert, which does not consume all gas. This distinction is important because checked arithmetic is commonly used for slippage checks and similar guards where the revert path is a normal, expected outcome. Consuming all gas in those cases would be unnecessarily punitive.

### Why Use Up Four Opcodes?

To achieve the goal of performing basic arithmetic operations in a single opcode, unfortunately, four new opcodes are required. While this is very significant, it is justified by the anticipated, heavy use of the four new opcodes by newly deployed contracts. Our analysis before Devcon VII (see Devcon Presentation: "EVM Charts 2024: What's hot? What's not? by Dominic Bruetsch | Devcon SEA") found that the existing four opcodes were among the Top-35 most-used opcodes, which together with the fact that checked arithmetic operations are now the default in Solidity and vyper provides an indication for this anticipated, heavy use.

### Opcode Placement

The opcodes `0x0c`–`0x0f` are placed immediately after the existing arithmetic group (`0x01` `ADD` through `0x0b` `SIGNEXTEND`). This contiguous placement reflects their semantic relationship to existing arithmetic opcodes and simplifies implementation in EVM interpreters that use jump tables or opcode-range checks. All four slots are currently unassigned.

### Gas Cost Justification

`SAFEADD` and `SAFESUB` are priced at 5 gas: the base cost of the underlying arithmetic operation (3 gas, the `Gverylow` group) plus 2 gas for the comparison and conditional revert check. The 2 gas premium conservatively accounts for the additional execution overhead.

`SAFEMUL` and `SAFEDIV` are priced at 7 gas: the base cost (5 gas, the `Glow` group) plus 2 gas for the check. `SAFEMUL` requires an internal division to verify the result; `SAFEDIV` requires a zero-comparison on the divisor. Both checks are computationally inexpensive relative to the underlying operation. Note that the existing `MUL` and `DIV` opcodes cost 5 gas, so the 2 gas premium is consistent with `SAFEADD`/`SAFESUB`.

### Why Unsigned 256-bit Only

This EIP defines only unsigned safe arithmetic opcodes. Signed overflow detection (two's complement) involves different boundary conditions and edge cases (e.g., `INT256_MIN / -1` overflows). Rather than overloading this proposal, signed variants (`SSAFEADD`, `SSAFESUB`, `SSAFEMUL`, `SSAFEDIV`) are deferred to a companion EIP. Unsigned arithmetic covers the vast majority of smart contract operations, since Solidity's `uint256` and Vyper's `uint256` are the dominant numeric types. This is also the reason that smaller data types are not considered.

### Why Include SAFEDIV

The existing `DIV` opcode silently returns 0 when the divisor is zero. While this is well-defined behavior, it is almost never the desired semantics — a division by zero virtually always indicates a logic error. Compilers today emit explicit zero-checks before `DIV` to revert on division by zero. `SAFEDIV` eliminates this pattern, providing the same gas and bytecode savings as the other safe opcodes.

### Revert with Empty Returndata

Safe arithmetic opcodes revert with empty returndata (zero-length return buffer) rather than encoding a specific error message. This design is compiler-neutral: the EVM specification does not mandate ABI encoding, and different languages use different error encoding schemes. Empty returndata is also consistent with how compilers currently implement overflow reverts. 

## Backwards Compatibility

The opcodes `0x0c`, `0x0d`, `0x0e`, and `0x0f` are currently unassigned and behave as `INVALID`, consuming all gas if executed.

All existing arithmetic opcodes (`ADD`, `SUB`, `MUL`, `DIV`, and others) are entirely unchanged. Contracts using `unchecked {}` (Solidity) or `unsafe_add()` (Vyper) continue to emit raw `ADD`, `SUB`, `MUL`, and `DIV` opcodes with their existing semantics.

Compilers opt in to the new opcodes by targeting the post-fork EVM version. Contracts compiled for earlier EVM versions continue to function identically.

## Test Cases

All test cases operate on unsigned 256-bit integers. `MAX` denotes `2^256 - 1` (`0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`). "Reverts" means the opcode triggers a revert with empty returndata and does not produce a stack output.

### `SAFEADD`

| # | `a` | `b` | Expected | Gas |
|---|---|---|---|---|
| 1 | `1` | `2` | `3` | 5 |
| 2 | `0` | `100` | `100` | 5 |
| 3 | `100` | `0` | `100` | 5 |
| 4 | `MAX - 1` | `1` | `MAX` | 5 |
| 5 | `MAX` | `1` | Reverts | 5 |
| 6 | `1` | `MAX` | Reverts | 5 |
| 7 | `MAX` | `MAX` | Reverts | 5 |
| 8 | `2^128` | `2^128 - 1` | `2^129 - 1` | 5 |
| 9 | `0` | `0` | `0` | 5 |

### `SAFESUB`

| # | `a` | `b` | Expected | Gas |
|---|---|---|---|---|
| 1 | `5` | `3` | `2` | 5 |
| 2 | `100` | `100` | `0` | 5 |
| 3 | `100` | `0` | `100` | 5 |
| 4 | `0` | `1` | Reverts | 5 |
| 5 | `0` | `MAX` | Reverts | 5 |
| 6 | `1` | `2` | Reverts | 5 |
| 7 | `MAX` | `MAX` | `0` | 5 |
| 8 | `MAX` | `0` | `MAX` | 5 |

### `SAFEMUL`

| # | `a` | `b` | Expected | Gas |
|---|---|---|---|---|
| 1 | `3` | `4` | `12` | 7 |
| 2 | `0` | `MAX` | `0` | 7 |
| 3 | `MAX` | `0` | `0` | 7 |
| 4 | `1` | `MAX` | `MAX` | 7 |
| 5 | `MAX` | `1` | `MAX` | 7 |
| 6 | `2^128` | `2^128` | Reverts | 7 |
| 7 | `2^128` | `2^128 - 1` | `2^256 - 2^128` | 7 |
| 8 | `MAX` | `2` | Reverts | 7 |
| 9 | `0` | `0` | `0` | 7 |

### `SAFEDIV`

| # | `a` | `b` | Expected | Gas |
|---|---|---|---|---|
| 1 | `10` | `3` | `3` | 7 |
| 2 | `10` | `10` | `1` | 7 |
| 3 | `10` | `0` | Reverts | 7 |
| 4 | `0` | `5` | `0` | 7 |
| 5 | `0` | `0` | Reverts | 7 |
| 6 | `MAX` | `1` | `MAX` | 7 |
| 7 | `MAX` | `MAX` | `1` | 7 |
| 8 | `1` | `MAX` | `0` | 7 |

## Security Considerations

### Gas Costs

The gas costs for these opcodes are conservatively set higher than their unchecked counterparts to avoid underpricing and prevent resource exhaustion attack vectors.

### More Readable, Safer Smart Contracts

Once adopted, these opcodes allow developers to write more readable, safer code without the trade-off that checked arithmetic currently introduces.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).

