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.
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:
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.
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:
unchecked/unsafe.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.
Two prior EIPs have proposed overflow detection at the EVM level:
ovf and sovf flags set by existing arithmetic opcodes, with OFV (0x0c) and SOVF (0x0d) opcodes to check and clear them.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.
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 and RFC 8174.
| Constant | Value |
|---|---|
FORK_BLOCK |
TBD |
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 |
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)| Stack | Value |
|---|---|
| top - 0 | a |
| top - 1 | b |
| Stack | Value |
|---|---|
| top - 0 | a + b |
result = a + b (mod 2^256)
if result < a:
revert(0, 0)
push result
Reverts if a + b > 2^256 - 1 (unsigned overflow).
SAFESUB (0x0d)| Stack | Value |
|---|---|
| top - 0 | a |
| top - 1 | b |
| 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.
if b > a:
revert(0, 0)
push a - b
Reverts if b > a (unsigned underflow).
SAFEMUL (0x0e)| Stack | Value |
|---|---|
| top - 0 | a |
| top - 1 | b |
| Stack | Value |
|---|---|
| top - 0 | a * b |
if a == 0:
push 0
else:
result = a * b (mod 2^256)
if result / a != b:
revert(0, 0)
push result
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)| Stack | Value |
|---|---|
| top - 0 | a |
| top - 1 | b |
| Stack | Value |
|---|---|
| top - 0 | a / b |
Stack ordering matches the existing DIV opcode: a is the dividend (on top), b is the divisor.
if b == 0:
revert(0, 0)
push a / b (unsigned integer division, truncating)
Reverts if b == 0 (division by zero). Unsigned integer division cannot overflow, so overflow is not checked.
Both EIP-1051 and EIP-6888 proposed flag-based approaches where existing arithmetic opcodes set implicit flags that must be checked by separate opcodes. This design has several drawbacks:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 |
The gas costs for these opcodes are conservatively set higher than their unchecked counterparts to avoid underpricing and prevent resource exhaustion attack vectors.
Once adopted, these opcodes allow developers to write more readable, safer code without the trade-off that checked arithmetic currently introduces.
Copyright and related rights waived via CC0.