-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
eip-2315: updated spec and examples (#2576)
* eip-2315: updated spec and examples * eip-2315: formatting nits * Update EIPS/eip-2315.md * Update EIPS/eip-2315.md Co-Authored-By: Andrei Maiboroda <[email protected]> * Update EIPS/eip-2315.md Co-Authored-By: MrChico <[email protected]> Co-authored-by: Andrei Maiboroda <[email protected]> Co-authored-by: MrChico <[email protected]>
- Loading branch information
1 parent
98d31ca
commit 8203a9d
Showing
1 changed file
with
107 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ title: Simple Subroutines for the EVM | |
status: Draft | ||
type: Standards Track | ||
category: Core | ||
author: Greg Colvin ([email protected]) | ||
author: Greg Colvin ([email protected]), Martin Holst Swende (@holiman) | ||
discussions-to: https://ethereum-magicians.org/t/eip-2315-simple-subroutines-for-the-evm/3941 | ||
created: 2019-10-17 | ||
--- | ||
|
@@ -23,16 +23,38 @@ In the Appendix we show example solc output for a simple program that uses over | |
|
||
## Specification | ||
|
||
We introduce one more stack into the EVM, called `return_stack`. The `return_stack` is limited to `1023` items. | ||
|
||
##### `BEGINSUB` | ||
|
||
Marks the entry point to a subroutine. | ||
|
||
pops: `0` | ||
pushes: `0` | ||
|
||
##### `JUMPSUB` | ||
Jumps to the destination address on top of the stack, which must be the offset of a `BEGINSUB`. | ||
|
||
1. Pops `1` value from the `stack`, hereafter referred to as `location`. | ||
- 1.1 If the opcode at `location` is not a `BEGINSUB`, abort with error. | ||
2. Pushes the current `pc+1` to the `return_stack`. | ||
- 2.1 If the `return_stack` already has `1023` items, abort with error. | ||
3. Sets the `pc` to `location`. | ||
|
||
pops: `1` | ||
pushes: `0` (`return_stack` pushes: `1`) | ||
|
||
|
||
##### `RETURNSUB` | ||
Returns to the most recently executed `JUMPSUB` and advances to the following instruction. | ||
|
||
A program may `JUMPSUB` at most 1023 times without an intervening `RETURNSUB`. A program which executes `RETURNSUB` without no prior `BEGINSUB` will `STOP`. | ||
1. Pops `1` value form the `return_stack`. | ||
1.1 If the `return_stack` is empty, abort with error | ||
2. Sets `pc` to the popped value | ||
|
||
pops: `0` (`return_stack` pops: `1`) | ||
pushes: `0` | ||
|
||
**Note:** Values popped from `return_stack` do not need to be validated, since they cannot be set arbitrarily from code, only implicitly by the evm. | ||
**Note2:** A value popped from `return_stack` _may_ be outside of the code length, if the last `JUMPSUB` was the last byte of the `code`. In this case the next opcode is implicitly a `STOP`, which is not an error. | ||
|
||
## Rationale | ||
|
||
|
@@ -42,69 +64,91 @@ This is the smallest possible change that provides native subroutines without br | |
|
||
These changes do not affect the semantics of existing EVM code. | ||
|
||
## Alternative variants | ||
|
||
One possible variant, would be to add an extra clause to the `BEGINSUB` opcode. | ||
|
||
- A `BEGINSUB` opcode may only be reached via a `JUMPSUB`. | ||
|
||
This would make `walking` into a subroutine an error. The rationale for this would be to possibly improve static analysis, being able | ||
to make stronger assertions about the code flow. | ||
|
||
This is not part of the current specification, since code-generators can trivially implement these guarantees by always prepending `STOP` opcode before | ||
any `BEGINSUB` operation. | ||
|
||
## Test Cases | ||
|
||
### Simple routine | ||
|
||
This should jump into a subroutine, back out and stop. | ||
|
||
Bytecode: `0x6004b300b5b7` | ||
|
||
|
||
| Pc | Op | Cost | Stack | RStack | | ||
|-------|-------------|------|-----------|-----------| | ||
| 0 | PUSH1 | 3 | [] | [] | | ||
| 2 | JUMPSUB | 8 | [4] | [] | | ||
| 4 | BEGINSUB | 1 | [] | [ 2] | | ||
| 5 | RETURNSUB | 2 | [] | [ 2] | | ||
| 3 | STOP | 0 | [] | [] | | ||
|
||
### Two levels of subroutines | ||
|
||
This should execute fine, going into one two depths of subroutines | ||
Bytecode: `0x6800000000000000000cb300b56011b3b7b5b7` | ||
|
||
| Pc | Op | Cost | Stack | RStack | | ||
|-------|-------------|------|-----------|-----------| | ||
| 0 | PUSH9 | 3 | [] | [] | | ||
| 10 | JUMPSUB | 8 | [12] | [] | | ||
| 12 | BEGINSUB | 1 | [] | [10] | | ||
| 13 | PUSH1 | 3 | [] | [10] | | ||
| 15 | JUMPSUB | 8 | [17] | [10] | | ||
| 17 | BEGINSUB | 1 | [] | [10,15] | | ||
| 18 | RETURNSUB | 2 | [] | [10,15] | | ||
| 16 | RETURNSUB | 2 | [] | [10] | | ||
| 11 | STOP | 0 | [] | [] | | ||
|
||
|
||
### Failure 1: invalid jump | ||
|
||
This should fail, since the given `location` is outside of the code-range. The code is the same as previous example, | ||
except that the pushed `location` is `0x01000000000000000c` instead of `0x0c`. | ||
|
||
Bytecode: `0x6801000000000000000cb300b56011b3b7b5b7` | ||
|
||
| Pc | Op | Cost | Stack | RStack | | ||
|-------|-------------|------|-----------|-----------| | ||
| 0 | PUSH9 | 3 | [] | [] | | ||
| 10 | JUMPSUB | 8 |[18446744073709551628] | [] | | ||
|
||
``` | ||
data return | ||
offset step opcode stack stack | ||
0 0 PUSH1 3 [] [] | ||
1 1 JUMPSUB [3] [1] | ||
2 4 STOP [] [1] | ||
3 2 BEGINSUB [] [1] | ||
4 3 RETURNSUB [] [] | ||
``` | ||
The above code should terminate after 4 steps with an empty stack. | ||
``` | ||
data return | ||
offset step opcode stack stack | ||
0 0 PUSH1 2 [] [] | ||
1 1 JUMPSUB [2] [1] | ||
2 2 BEGINSUB [] [1] | ||
3 3 RETURNSUB [] [] | ||
Error: at pc=10, op=JUMPSUB: evm: invalid jump destination | ||
``` | ||
The above code should terminate after 4 steps with an empty stack. | ||
|
||
### Failure 2: shallow `return_stack` | ||
|
||
This should fail at first opcode, due to shallow `return_stack` | ||
|
||
Bytecode: `0xb75858` (`RETURNSUB`, `PC`, `PC`) | ||
|
||
|
||
| Pc | Op | Cost | Stack | RStack | | ||
|-------|-------------|------|-----------|-----------| | ||
| 0 | RETURNSUB | 2 | [] | [] | | ||
|
||
``` | ||
data return | ||
offset step opcode stack stack | ||
0 0 PUSH1 3 [] [] | ||
1 1 JUMPSUB [3] [1] | ||
2 8 STOP [] [] | ||
3 2 BEGINSUB [] [1] | ||
4 3 PUSH1 7 [] [1] | ||
5 4 JUMPSUB [7] [1,5] | ||
6 7 RETURNSUB [] [1] | ||
7 5 BEGINSUB [] [1] | ||
8 6 RETURNSUB [] [1] | ||
Error: at pc=0, op=RETURNSUB: evm: invalid retsub | ||
``` | ||
The above code should terminate after 8 steps with an empty stack. The above code should terminate after 8 steps with an empty stack. | ||
|
||
## Implementations | ||
|
||
No clients have implemented this proposal as of yet, but there are Draft PRs on the [evmone](https://github.com/ethereum/evmone/pull/229) and [geth](https://github.com/ethereum/go-ethereum/pull/20619) interpreters. | ||
No clients have implemented this proposal as of yet, but there are Draft PRs for | ||
|
||
- [evmone](https://github.com/ethereum/evmone/pull/229), and | ||
- [geth](https://github.com/ethereum/go-ethereum/pull/20619) . | ||
|
||
The new operators proposed here are demonstrated by the following pseudocode, which adds a return stack and cases for `BEGINSUB`, `JUMPSUB` and `RETURNSUB` to a simple loop-and-switch interpreter. | ||
``` | ||
bytecode[code_size] | ||
data_stack[1024] = { } | ||
return_stack[1024] = { code_size } | ||
PC = 0 | ||
while PC < code_size { | ||
opcode = bytecode[PC] | ||
switch opcode { | ||
... | ||
case BEGINSUB: | ||
case JUMPSUB: | ||
push(return_stack, PC) | ||
PC = pop(data_stack) | ||
continue | ||
case RETURNSUB: | ||
PC = pop(return_stack) | ||
} | ||
++PC | ||
} | ||
``` | ||
Execution of EVM bytecode begins with one value on the return stack—the size of the bytecode. The implicit 0 bytes at and after this offset are EVM `STOP` opcods. So executing a `RETURNSUB` with no prior `JUMPSUB` jumps to the _code_size_ offset on the stack, then executes a `STOP` on the next cycle. A `STOP` or `RETURN` ends the execution of the program. | ||
|
||
### Costs and Codes | ||
|
||
|
@@ -118,7 +162,10 @@ We suggest that the cost of `BEGINSUB` be _base_, `JUMPSUB` be _low_, and `RETUR | |
``` | ||
## Security Considerations | ||
|
||
Program flow analysis frameworks will need to be updated to allow for a new type of branch - `JUMPSUB` - which will cause a jump to a destination which is a `BEGINSUB`, not a `JUMPDEST`. | ||
These changes do introduce new flow control instructions, so any software which does static/dynamic analysis of evm-code | ||
needs to be modified accordingly. The `JUMPSUB` semantics are similar to `JUMP` (but jumping to a `BEGINSUB`), whereas the `RETURNSUB` instruction | ||
is different, since it can 'land' on any opcode (but the possible destinations can be statically inferred). | ||
|
||
|
||
## Appendix: Comparative costs. | ||
|
||
|
@@ -190,4 +237,4 @@ MULTIPLY: | |
returnsub | ||
``` | ||
|
||
**Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).** | ||
**Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).** |