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.
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.
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?
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.