From 8203a9d69a2821e6330ee029de4a5a0634e87140 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 3 Apr 2020 16:26:26 +0200 Subject: [PATCH] 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 * Update EIPS/eip-2315.md Co-Authored-By: MrChico Co-authored-by: Andrei Maiboroda Co-authored-by: MrChico --- EIPS/eip-2315.md | 167 ++++++++++++++++++++++++++++++----------------- 1 file changed, 107 insertions(+), 60 deletions(-) diff --git a/EIPS/eip-2315.md b/EIPS/eip-2315.md index dffa2356f8e1dc..be52971ef6a51e 100644 --- a/EIPS/eip-2315.md +++ b/EIPS/eip-2315.md @@ -4,7 +4,7 @@ title: Simple Subroutines for the EVM status: Draft type: Standards Track category: Core -author: Greg Colvin (greg@colvin.org) +author: Greg Colvin (greg@colvin.org), 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/).** \ No newline at end of file +**Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).**