Merge pull request #543 from status-im/eip_2315

EIP 2315: implement jumpsub, beginsub, and returnsub opcode
This commit is contained in:
andri lim 2020-11-25 20:55:30 +07:00 committed by GitHub
commit 4d52d2fb92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 169 additions and 5 deletions

View File

@ -144,6 +144,7 @@ proc newComputation*(vmState: BaseVMState, message: Message, salt= 0.u256): Comp
result.msg = message
result.memory = Memory()
result.stack = newStack()
result.returnStack = @[]
result.gasMeter.init(message.gas)
result.touchedAccounts = initHashSet[EthAddress]()
result.suicides = initHashSet[EthAddress]()

View File

@ -535,6 +535,9 @@ template gasCosts(fork: Fork, prefix, ResultGasCostsName: untyped) =
Msize: fixed GasBase,
Gas: fixed GasBase,
JumpDest: fixed GasJumpDest,
BeginSub: fixed GasBase,
ReturnSub: fixed GasLow,
JumpSub: fixed GasHigh,
# 60s & 70s: Push Operations
Push1: fixed GasVeryLow,

View File

@ -95,6 +95,9 @@ fill_enum_holes:
Msize = 0x59, # Get the size of active memory in bytes.
Gas = 0x5a, # Get the amount of available gas, including the corresponding reduction for the cost of this instruction.
JumpDest = 0x5b, # Mark a valid destination for jumps. This operation has no effect on machine state during execution.
BeginSub = 0x5c, # Marks the entry point to a subroutine
ReturnSub = 0x5d, # Returns control to the caller of a subroutine.
JumpSub = 0x5e, # Transfers control to a subroutine.
# 60s & 70s: Push Operations.
Push1 = 0x60, # Place 1-byte item on stack.

View File

@ -542,6 +542,38 @@ op jumpDest, inline = true:
## 0x5b, Mark a valid destination for jumps. This operation has no effect on machine state during execution.
discard
op beginSub, inline = true:
## 0x5c, Marks the entry point to a subroutine
raise newException(OutOfGas, "Abort: Attempt to execute BeginSub opcode")
op returnSub, inline = true:
## 0x5d, Returns control to the caller of a subroutine.
if c.returnStack.len == 0:
raise newException(OutOfGas, "Abort: invalid returnStack during ReturnSub")
# Other than the check that the return stack is not empty, there is no
# need to validate the pc from 'returns', since we only ever push valid
# values onto it via jumpsub.
c.code.pc = c.returnStack.pop()
op jumpSub, inline = true, jumpTarget:
## 0x5e, Transfers control to a subroutine.
if jumpTarget >= c.code.len.u256:
raise newException(InvalidJumpDestination, "JumpSub destination exceeds code len")
let returnPC = c.code.pc
let jt = jumpTarget.truncate(int)
c.code.pc = jt
let nextOpcode = c.code.peek
if nextOpcode != BeginSub:
raise newException(InvalidJumpDestination, "Invalid JumpSub destination")
if c.returnStack.len == 1023:
raise newException(FullStack, "Out of returnStack")
c.returnStack.add returnPC
inc c.code.pc
# ##########################################
# 60s & 70s: Push Operations.
# 80s: Duplication Operations

View File

@ -229,6 +229,14 @@ proc genIstanbulJumpTable(ops: array[Op, NimNode]): array[Op, NimNode] {.compile
let IstanbulOpDispatch {.compileTime.}: array[Op, NimNode] = genIstanbulJumpTable(PetersburgOpDispatch)
proc genBerlinJumpTable(ops: array[Op, NimNode]): array[Op, NimNode] {.compileTime.} =
result = ops
result[BeginSub] = newIdentNode "beginSub"
result[ReturnSub] = newIdentNode "returnSub"
result[JumpSub] = newIdentNode "jumpSub"
let BerlinOpDispatch {.compileTime.}: array[Op, NimNode] = genBerlinJumpTable(IstanbulOpDispatch)
proc opTableToCaseStmt(opTable: array[Op, NimNode], c: NimNode): NimNode =
let instr = quote do: `c`.instr
@ -308,6 +316,9 @@ macro genPetersburgDispatch(c: Computation): untyped =
macro genIstanbulDispatch(c: Computation): untyped =
result = opTableToCaseStmt(IstanbulOpDispatch, c)
macro genBerlinDispatch(c: Computation): untyped =
result = opTableToCaseStmt(BerlinOpDispatch, c)
proc frontierVM(c: Computation) =
genFrontierDispatch(c)
@ -332,6 +343,9 @@ proc petersburgVM(c: Computation) {.gcsafe.} =
proc istanbulVM(c: Computation) {.gcsafe.} =
genIstanbulDispatch(c)
proc berlinVM(c: Computation) {.gcsafe.} =
genBerlinDispatch(c)
proc selectVM(c: Computation, fork: Fork) {.gcsafe.} =
# TODO: Optimise getting fork and updating opCodeExec only when necessary
case fork
@ -349,8 +363,10 @@ proc selectVM(c: Computation, fork: Fork) {.gcsafe.} =
c.constantinopleVM()
of FkPetersburg:
c.petersburgVM()
else:
of FkIstanbul:
c.istanbulVM()
else:
c.berlinVM()
proc executeOpcodes(c: Computation) =
let fork = c.fork

View File

@ -68,6 +68,7 @@ type
msg*: Message
memory*: Memory
stack*: Stack
returnStack*: seq[int]
gasMeter*: GasMeter
code*: CodeStream
output*: seq[byte]

View File

@ -172,6 +172,10 @@ proc parseFork(fork: NimNode): Fork =
fork[0].expectKind({nnkIdent, nnkStrLit})
parseEnum[Fork](strip(fork[0].strVal))
proc parseGasUsed(gas: NimNode): GasInt =
gas[0].expectKind(nnkIntLit)
result = gas[0].intVal
proc generateVMProxy(boa: Assembler): NimNode =
let
vmProxy = genSym(nskProc, "vmProxy")
@ -262,10 +266,9 @@ proc initComputation(blockNumber: Uint256, chainDB: BaseChainDB, code, data: seq
proc runVM*(blockNumber: Uint256, chainDB: BaseChainDB, boa: Assembler): bool =
var computation = initComputation(blockNumber, chainDB, boa.code, boa.data, boa.fork)
# TODO: support gas comsumption validation
# let gas = computation.gasMeter.gasRemaining
let gas = computation.gasMeter.gasRemaining
execComputation(computation)
# let gasUsed = gas - computation.gasMeter.gasRemaining
let gasUsed = gas - computation.gasMeter.gasRemaining
if computation.isSuccess:
if boa.success == false:
@ -276,6 +279,11 @@ proc runVM*(blockNumber: Uint256, chainDB: BaseChainDB, boa: Assembler): bool =
error "different success value", expected=boa.success, actual=false
return false
if boa.gasUsed != -1:
if boa.gasUsed != gasUsed:
error "different gasUsed", expected=boa.gasUsed, actual=gasUsed
return false
if boa.stack.len != computation.stack.values.len:
error "different stack len", expected=boa.stack.len, actual=computation.stack.values.len
return false
@ -355,7 +363,7 @@ proc runVM*(blockNumber: Uint256, chainDB: BaseChainDB, boa: Assembler): bool =
result = true
macro assembler*(list: untyped): untyped =
var boa = Assembler(success: true, fork: FkFrontier)
var boa = Assembler(success: true, fork: FkFrontier, gasUsed: -1)
list.expectKind nnkStmtList
for callSection in list:
callSection.expectKind(nnkCall)
@ -375,6 +383,7 @@ macro assembler*(list: untyped): untyped =
of "data": boa.data = parseData(body)
of "output": boa.output = parseData(body)
of "fork": boa.fork = parseFork(body)
of "gasused": boa.gasUsed = parseGasUsed(body)
else: error("unknown section '" & label & "'", callSection[0])
result = boa.generateVMProxy()

View File

@ -175,3 +175,102 @@ proc opMiscMain*() =
memory:
"0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1B1"
"0x00"
assembler:
title: "Simple routine"
code:
PUSH1 "0x04"
JUMPSUB
STOP
BEGINSUB
RETURNSUB
gasUsed: 18
fork: Berlin
assembler:
title: "Two levels of subroutines"
code:
PUSH9 "0x00000000000000000C"
JUMPSUB
STOP
BEGINSUB
PUSH1 "0x11"
JUMPSUB
RETURNSUB
BEGINSUB
RETURNSUB
gasUsed: 36
fork: Berlin
assembler:
title: "Failure 1: invalid jump"
code:
PUSH9 "0x01000000000000000C"
JUMPSUB
STOP
BEGINSUB
PUSH1 "0x11"
JUMPSUB
RETURNSUB
BEGINSUB
RETURNSUB
success: false
fork: Berlin
assembler:
title: "Failure 2: shallow return stack"
code:
RETURNSUB
PC
PC
success: false
fork: Berlin
assembler:
title: "Subroutine at end of code"
code:
PUSH1 "0x05"
JUMP
BEGINSUB
RETURNSUB
JUMPDEST
PUSH1 "0x03"
JUMPSUB
gasUsed: 30
fork: Berlin
assembler:
title: "Error on 'walk-into-subroutine'"
code:
BEGINSUB
RETURNSUB
STOP
success: false
fork: Berlin
assembler:
title: "sol test"
code:
PUSH1 "0x02"
PUSH1 "0x03"
PUSH1 "0x08" # jumpdest
JUMPSUB
STOP
# 0x08
BEGINSUB
PUSH1 "0x0D" # jumpdest
JUMPSUB
RETURNSUB
# 0x0D
BEGINSUB
MUL
RETURNSUB
gasUsed: 47
fork: Berlin
stack:
"0x06"
when isMainModule:
opMiscMain()