Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests & implementation of CallDataLoad and CodeCopy #67

Closed
mratsim opened this issue Jul 5, 2018 · 2 comments · Fixed by #1716
Closed

Tests & implementation of CallDataLoad and CodeCopy #67

mratsim opened this issue Jul 5, 2018 · 2 comments · Fixed by #1716

Comments

@mratsim
Copy link
Contributor

mratsim commented Jul 5, 2018

Extracting discussion from https://github.com/status-im/nimbus/pull/59/files#r200313762

CallDataLoad

The current master implementation for calldataload is (from #59):

proc callDataLoad*(computation: var BaseComputation) =
  # Load call data into memory
  let origDataPos = computation.stack.popInt
  if origDataPos >= computation.msg.data.len:
    computation.stack.push(0)
    return

  let
    dataPos = origDataPos.toInt
    dataEndPosition = dataPos + 31

  if dataEndPosition < computation.msg.data.len:
    computation.stack.push(computation.msg.data[dataPos .. dataEndPosition])
  else:
    var bytes: array[32, byte]
    var presentBytes = min(computation.msg.data.len - dataPos, 32)

    if presentBytes > 0:
      copyMem(addr bytes[0], addr computation.msg.data[dataPos], presentBytes)
    else:
      presentBytes = 0

    for i in presentBytes ..< 32: bytes[i] = 0
      computation.stack.push(bytes)

With a potential simpler solution (from #65)

op callDataLoad, inline = false, startPos:
  ## 0x35, Get input data of current environment
  let start = startPos.toInt

  # If the data does not take 32 bytes, pad with zeros
  let endRange = min(computation.msg.data.len - 1, start + 31)
  let padding = start + 31 - endRange
  var value: array[32, byte] # We rely on value being initialized with 0 by default
  value[padding ..< 32] = computation.msg.data.toOpenArray(start, endRange)

  push: value # TODO, with the new implementation we can delete push for seq[byte]

CodeCopy

The current master implementation for codecopy is (from #59):

proc writePaddedResult(mem: var Memory,
                       data: openarray[byte],
                       memPos, dataPos, len: Natural,
                       paddingValue = 0.byte) =
  mem.extend(memPos, len)

  let dataEndPosition = dataPos + len - 1
  if dataEndPosition < data.len:
    mem.write(memPos, data[dataPos .. dataEndPosition])
  else:
    var presentElements = data.len - dataPos
    if presentElements > 0:
      mem.write(memPos, data.toOpenArray(dataPos, data.len - 1))
    else:
      presentElements = 0

    mem.writePaddingBytes(memPos + presentElements,
                          len - presentElements,
                          paddingValue)

proc codeCopy*(computation: var BaseComputation) =
  let (memStartPosition,
       codeStartPosition,
       size) = computation.stack.popInt(3)

  computation.gasMeter.consumeGas(
    computation.gasCosts[CodeCopy].d_handler(size),
    reason="CODECOPY: word gas cost")

  let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt)

  computation.memory.writePaddedResult(computation.code.bytes, memPos, codePos, len)

With a simpler solution (from #65):

op codecopy, inline = false, memStartPos, copyStartPos, size:
  ## 0x39, Copy code running in current environment to memory.

  let (memPos, copyPos, len) = (memStartPos.toInt, copyStartPos.toInt, size.toInt)

  computation.gasMeter.consumeGas(
    computation.gasCosts[CodeCopy].m_handler(memPos, copyPos, len),
    reason="CodeCopy fee")

  computation.memory.extend(memPos, len)

  # If the data does not take 32 bytes, pad with zeros
  let lim = min(computation.code.bytes.len, copyPos + len)
  let padding = copyPos + len - lim
  # Note: when extending, extended memory is zero-ed, we only need to offset with padding value
  computation.memory.write(memPos):
    computation.code.bytes.toOpenArray(copyPos+padding, copyPos+lim)

Reference implementation

Here are the implementations in Py-EVM, Geth and Parity:

Py-EVM is doing something very complex, increasing a program counter in the "CodeStream" type. while Geth, Parity and the Yellow paper are just copying bytes?

Concerns

  1. The current implementation is complex, with 2 nested ifs, and the proc "writePaddingBytes" that is redundant with memory.extend (which pads with 0 already)

  2. The simpler codecopy has an off by one error (which see the simpler calldataload which does not suffer for this)

  3. codePos (callDataLoad) and copyPos (codeCopy) can be out of bounds. For copyPos this is handled in memory.write:

proc write*(memory: var Memory, startPos: Natural, value: openarray[byte]) =
  let size = value.len
  if size == 0:
    return
  #echo size
  #echo startPos
  #validateGte(startPos, 0)
  #validateGte(size, 0)
  validateLte(startPos + size, memory.len)
  let index = memory.len
  if memory.len < startPos + size:
    memory.bytes = memory.bytes.concat(repeat(0.byte, memory.len - (startPos + size))) # TODO: better logarithmic scaling?

  for z, b in value:
    memory.bytes[z + startPos] = b

but there is no handling for outofbounds read in codePos.

TODO

  • Add test cases to make sure the implementation chosen cover edge cases.
  • Simplify the current implementations

cc @zah

@mratsim
Copy link
Contributor Author

mratsim commented Jul 5, 2018

It didn't bd17353

mratsim added a commit that referenced this issue Jul 6, 2018
* move forks constants, rename errors

* Move vm/utils to vm/interpreter/utils

* initial opcodes refactoring

* Add refactored Comparison & Bitwise Logic Operations

* Add sha3 and address, simplify macro, support pop 0

* balance, origin, caller, callValue

* fix gas copy opcodes gas costs, add callDataLoad/Size/Copy, CodeSize/Copy and gas price opcode

* Update with 30s, 40s, 50s opcodes + impl of balance + stack improvement

* add push, dup, swap, log, create and call operations

* finish opcode implementation

* Add the new dispatching logic

* Pass the opcode test

* Make test_vm_json compile

* halt execution without exceptions for Return, Revert, selfdestruct (fix #62)

* Properly catch and recover from EVM exceptions (stack underflow ...)

* Fix byte op

* Fix jump regressions

* Update for latest devel, don't import old dispatch code as quasiBoolean macro is broken by latest devel

* Fix sha3 regression on empty memory slice and until end of range slice

* Fix padding / range error on expXY_success (gas computation left)

* update logging procs

* Add tracing - expXY_success is not a regression, sload stub was accidentally passing the test

* Reuse the same stub as OO implementation

* Delete previous opcode implementation

* Delete object oriented fork code

* Delete exceptions that were used as control flows

* delete base.nim 🔥, yet another OO remnants

* Delete opcode table

* Enable omputed gotos and compile-time gas fees

* Revert const gasCosts -> generates SIGSEGV

* inline push, swap and dup opcodes

* loggers are now template again, why does this pass new tests?

* Trigger CI rebuild after rocksdb fix status-im/nim-rocksdb#5

* Address review comment on "push" + VMTests in debug mode (not release)

* Address review comment: don't tag fork by default, make opcode impl grepable

* Static compilation fixes after rebasing

* fix the initialization of the VM database

* add a missing import

* Deactivate balance and sload test following #59

* Reactivate stack check (deactivated in #59, necessary to pass tests)

* Merge remaining opcodes implementation from #59

* Merge callDataLoad and codeCopy fixes, todo simplify see #67
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant