EIP-8219: Checked Arithmetic Opcodes

Summary (ELI5):
This EIP introduces four new opcodes: SAFEADD, SAFESUB, SAFEMUL, and SAFEDIV that perform unsigned 256-bit arithmetic with built-in overflow, underflow, and division-by-zero checking. This makes smart contracts easier to read and write and cheaper to execute.

Champion: Hubert Ritzdorf, ritzdorf (Hubert Ritzdorf) · GitHub

Detailed Justification:

  • Currently, compilers add checked arithmetic, which creates significant overhead
  • Introducing checked arithmetic opcodes would reduce the actual gas cost of a checked addition by 88% and 93% for vyper and solidity respectively
  • This reduces execution cost and code size
  • Previous approaches didn’t offer such great gas cost reductions

Stakeholder Impact:

Positive:

  • Easier to implement EVM compilers
  • More readable smart contracts
  • Cheaper smart contract execution
  • Smaller smart contract size

Negative:

  • Four additional opcodes taken

Technical Readiness:

This proposal is ready. See Full EIP linked below. The required modifications in execution clients are relatively minor.

Security & Open Questions:

We welcome a discussion about the gas costs for the new opcodes.

Full EIP: EIPs/EIPS/eip-8219.md at master · ethereum/EIPs · GitHub

4 Likes

Why revert over assert? This should probably go in your Rationale section alongside the “revert with no message” paragraph.

1 Like

You might want to add a bit of text justifying using up four opcodes (vs. just one in the EIP-1051 approach).

Thanks a lot for your feedback @SamWilsn

Great point. I moved the paragraph.

Thanks. I added some justification. Essentially, we believe that it would be great to perform these safe arithmetic operations in a single opcode, which unfortunately requires the four new opcodes.

I’ve been thinking about this a tiny bit more, and have a few more alternatives to propose.

The first is a GETFLAGS and SETFLAGS(mask, value) pair of opcodes. We’d introduce a single flags: U256 variable here. Then we’d define a bit for safe math. When it’s enabled, all arithmetic is checked. Upsides are future extensibility and few new opcodes, but the downsides are that compilers have to manage the flags and that can be complex.

Next, same thing but with a PUSHFLAGS and POPFLAGS. Easier to reason about in compilers, but needs a whole new stack in the EVM.

Third, have a prefix-type instruction CHECKED that enables safe arithmetic just for the next instruction (and is a NOP otherwise). No complex management for compilers, but doubles the size of checked arithmetic code.

5 is too little for a conditional branching operation. Should be at least as much as JUMPI.

Thanks for the feedback.

So ADD execution becomes SAFEADD execution if the flag is set? And the flag stays active (within an EVM instance) until unset?

As you mentioned, it would have extensibility, so could even be used for 32/64/128-bit overflow checks. However, how would you deal with gas cost? Would ADD have a different cost depending on whether the flag is set?

Not sure how common this is but there are contracts with checked and unchecked arithmetic. Those would have to switch back and forth, which then creates significant overhead again as one SETFLAGS requires two previous PUSH operations.

Not sure I understand how this would work. PUSHFLAGS has a value following it like PUSH?

Makes sense to me:

  • just one opcode added
  • CHECKED can cost the extra gas
  • still a significant reduction of bytecode size and gas cost

If adding four bytecodes is no option, this would make sense to me.

I am not quite sure regarding the trade-offs here. Wouldn’t what you propose require to set an internal flag on CHECKED and unset it on any other operation?

Thanks for feedback.

Forgive my naive question: Doesn’t every EVM instruction already contain a conditional branch leading to termination (e.g. through out-of-gas)?

1 Like

Yes, exactly.

That’s a good point. I suppose yes, that is indeed how it would have to operate.

Do we know for sure we need to increase the gas cost? You give some reasoning in the EIP, and it makes sense, but have you benchmarked the performance of SAFEADD, for example, and confirmed that it takes two extra gas worth of resources?

I’m not sure that would qualify as “significant” though? The cost of switching is less than a single checked instruction is today.

Ah, no. PUSHFLAGS is identical to SETFLAGS except that it stores the previous flags on a new hidden stack. POPFLAGS pops a value from that stack and restores the behaviour.

I think you’d need to add an internal flag or change the interpreter loop to read a second opcode after encountering a CHECKED instruction, regardless of what the behaviour is when hitting a non-checked instruction after CHECKED.