Finish the first memory impl and tests, stack tests, fixes validation

This commit is contained in:
Alexander Ivanov 2018-02-06 21:20:06 +02:00
parent 04b1228fbe
commit 211d46e39e
10 changed files with 209 additions and 23 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
nimcache/
runner
*_test

View File

@ -1,4 +1,5 @@
# Nimbus
An Ethereum 2.0 Sharding Client for Resource-Restricted Devices.
https://docs.google.com/document/d/14u65XVNLOd83cq3t7wNC9UPweZ6kPWvmXwRTWWn0diQ/edit#

View File

@ -11,7 +11,13 @@ proc int256*(i: int): Int256 =
i.initBigInt
template i256*(i: int): Int256 =
i.int256
i.initBigInt
template i256*(i: Int256): Int256 =
i
template getInt*(i: int): int =
i
# TODO
# We'll have a fast fixed i256, for now this works
@ -77,7 +83,7 @@ mapOp(`xor`)
proc `abs`*(a: Int256): Int256 =
if a >= 0.i256: a else: -a
proc `getInt`*(a: Int256): int =
template `getInt`*(a: Int256): int =
a.limbs[0].int
let

View File

@ -1,5 +1,3 @@
import
strformat,
errors, constants, bigints
@ -8,27 +6,37 @@ proc validateCanonicalAddress*(value: string, title: string = "Value") =
# TODO
if false: #len(value) != 20:
raise newException(ValidationError,
fmt"{title} {value} is not a valid canonical address")
&"{title} {value} is not a valid canonical address")
proc validateGte*(value: Int256, minimum: int, title: string = "Value") =
if value <= minimum.int256:
proc validateGte*(value: Int256 | int, minimum: int, title: string = "Value") =
if value.i256 < minimum.i256:
raise newException(ValidationError,
fmt"{title} {value} is not greater than or equal to {minimum}")
&"{title} {value} is not greater than or equal to {minimum}")
proc validateGte*(value: int, minimum: int, title: string = "Value") =
if value <= minimum:
proc validateGt*(value: Int256 | int, minimum: int, title: string = "Value") =
if value.i256 <= minimum.i256:
raise newException(ValidationError,
fmt"{title} {value} is not greater than or equal to {minimum}")
&"{title} {value} is not greater than {minimum}")
proc validateGt*(value: Int256, minimum: int, title: string = "Value") =
if value < minimum.int256:
proc validateLength*[T](values: seq[T], size: int) =
if values.len != size:
raise newException(ValidationError,
fmt"{title} {value} is not greater than or equal to {minimum}")
&"seq expected {size} len, got {values.len}")
proc validateGt*(value: int, minimum: int, title: string = "Value") =
if value < minimum:
proc validateLte*(value: Int256 | int, maximum: int, title: string = "Value") =
if value.i256 > maximum.i256:
raise newException(ValidationError,
fmt"{title} {value} is not greater than or equal to {minimum}")
&"{title} {value} is not less or equal to {maximum}")
proc validateLt*(value: Int256 | int, maximum: int, title: string = "Value") =
if value.i256 >= maximum.i256:
raise newException(ValidationError,
&"{title} {value} is not less than {maximum}")
proc validateStackItem*(value: string) =
if value.len > 32:
raise newException(ValidationError,
&"Invalid stack item: expected 32 bytes, got {value.len}: value is {value}")

View File

@ -13,7 +13,7 @@ proc newMemory*: Memory =
result.logger = logging.getLogger("evm.vm.memory.Memory")
proc len*(memory: Memory): int =
result = len(memory.bytes)
result = memory.bytes.len
proc extend*(memory: var Memory; startPosition: Int256; size: Int256) =
if size == 0:
@ -32,7 +32,19 @@ proc read*(memory: var Memory, startPosition: Int256, size: Int256): seq[byte] =
result = memory.bytes[startPosition.getInt ..< (startPosition + size).getInt]
proc write*(memory: var Memory, startPosition: Int256, size: Int256, value: seq[byte]) =
echo value
if size == 0:
return
validateGte(startPosition, 0)
validateGte(size, 0)
validateLength(value, size.getInt)
validateLte(startPosition + size, memory.len)
let index = memory.len
if memory.len.i256 < startPosition + size:
memory.bytes = memory.bytes.concat(repeat(0.byte, memory.len - (startPosition + size).getInt)) # TODO: better logarithmic scaling?
for z, b in value:
memory.bytes[z + startPosition.getInt] = b
template write*(memory: var Memory, startPosition: Int256, size: Int256, value: cstring) =
memory.write(startPosition, size, value.toBytes)

View File

@ -19,24 +19,31 @@ proc len*(stack: Stack): int =
proc push*(stack: var Stack; value: Value) =
## Push an item onto the stack
ensureStackLimit()
if value.kind == VInt:
validateGte(value.i, 0)
else:
validateStackItem(value.b)
stack.values.add(value)
proc push*(stack: var Stack; value: int) =
## Push an integer onto the stack
ensureStackLimit()
validateGte(value, 0)
stack.values.add(Value(kind: VInt, i: value.int256))
proc push*(stack: var Stack; value: Int256) =
## Push an integer onto the stack
ensureStackLimit()
validateGte(value, 0)
stack.values.add(Value(kind: VInt, i: value))
proc push*(stack: var Stack; value: string) =
## Push a binary onto the stack
ensureStackLimit()
validateStackItem(value)
stack.values.add(Value(kind: VBinary, b: value))
@ -159,10 +166,10 @@ proc swap*(stack: var Stack; position: int) =
raise newException(InsufficientStack,
&"Insufficient stack items for SWAP{position}")
proc dup*(stack: var Stack; position: int) =
proc dup*(stack: var Stack; position: int | Int256) =
## Perform a DUP operation on the stack
if position < len(stack) + 1:
stack.push(stack.values[^position])
if (position != 0 and position.getInt < stack.len + 1) or (position == 0 and position.getInt < stack.len):
stack.push(stack.values[^position.getInt])
else:
raise newException(InsufficientStack,
&"Insufficient stack items for DUP{position}")

View File

@ -28,3 +28,11 @@ proc vint*(i: Int256): Value =
proc vbinary*(b: string): Value =
Value(kind: VBinary, b: b)
proc `==`*(a: Value, b: Value): bool =
if a.kind != b.kind:
return false
case a.kind:
of VInt:
a.i == b.i
of VBinary:
a.b == b.b

61
tests/memory_test.nim Normal file
View File

@ -0,0 +1,61 @@
import unittest, macros, strformat, strutils, sequtils, constants, opcode_values, errors, vm / memory, bigints
proc memory32: Memory =
result = newMemory()
result.extend(0.i256, 32.i256)
proc memory128: Memory =
result = newMemory()
result.extend(0.i256, 128.i256)
suite "memory":
test "write":
var mem = memory32()
# Test that write creates 32byte string == value padded with zeros
mem.write(startPosition = 0.i256, size = 4.i256, value = @[1.byte, 0.byte, 1.byte, 0.byte])
check(mem.bytes == @[1.byte, 0.byte, 1.byte, 0.byte].concat(repeat(0.byte, 28)))
test "write rejects invalid position":
expect(ValidationError):
var mem = memory32()
mem.write(startPosition = -1.i256, size = 2.i256, value = @[1.byte, 0.byte])
expect(ValidationError):
var mem = memory32()
mem.write(startPosition = pow(2.i256, 256), size = 2.i256, value = @[1.byte, 0.byte])
test "write rejects invalid size":
expect(ValidationError):
var mem = memory32()
mem.write(startPosition = 0.i256, size = -1.i256, value = @[1.byte, 0.byte])
expect(ValidationError):
var mem = memory32()
mem.write(startPosition = 0.i256, size = pow(2.i256, 256), value = @[1.byte, 0.byte])
test "write rejects invalid value":
expect(ValidationError):
var mem = memory32()
mem.write(startPosition = 0.i256, size = 4.i256, value = @[1.byte, 0.byte])
test "write rejects valyes beyond memory size":
expect(ValidationError):
var mem = memory128()
mem.write(startPosition = 128.i256, size = 4.i256, value = @[1.byte, 0.byte, 1.byte, 0.byte])
test "extends appropriately extends memory":
var mem = newMemory()
# Test extends to 32 byte array: 0 < (start_position + size) <= 32
mem.extend(startPosition = 0.i256, size = 10.i256)
check(mem.bytes == repeat(0.byte, 32))
# Test will extend past length if params require: 32 < (start_position + size) <= 64
mem.extend(startPosition = 28.i256, size = 32.i256)
check(mem.bytes == repeat(0.byte, 64))
# Test won't extend past length unless params require: 32 < (start_position + size) <= 64
mem.extend(startPosition = 48.i256, size = 10.i256)
check(mem.bytes == repeat(0.byte, 64))
test "read returns correct bytes":
var mem = memory32()
mem.write(startPosition = 5.i256, size = 4.i256, value = @[1.byte, 0.byte, 1.byte, 0.byte])
check(mem.read(startPosition = 5.i256, size = 4.i256) == @[1.byte, 0.byte, 1.byte, 0.byte])
check(mem.read(startPosition = 6.i256, size = 4.i256) == @[0.byte, 1.byte, 0.byte, 0.byte])
check(mem.read(startPosition = 1.i256, size = 3.i256) == @[0.byte, 0.byte, 0.byte])

77
tests/stack_test.nim Normal file
View File

@ -0,0 +1,77 @@
import unittest, macros, strformat, strutils, sequtils, constants, opcode_values, errors, vm / [stack, value], bigints
suite "stack":
test "push only valid":
for value in @[0.vint, (pow(2.i256, 256) - 1.i256).vint, "ves".vbinary]:
var stack = newStack()
stack.push(value)
check(stack.values == @[value])
for value in @[(-1).vint, (-2).vint, "yzyzyzyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz".vbinary]:
var stack = newStack()
expect(ValidationError):
stack.push(value)
test "push does not allow stack to exceed 1024":
var stack = newStack()
for z in 0 .. < 1024:
stack.push(z)
check(stack.len == 1024)
expect(FullStack):
stack.push(1025)
test "dup does not allow stack to exceed 1024":
var stack = newStack()
stack.push(1.i256)
for z in 0 ..< 1023:
stack.dup(1.i256)
check(stack.len == 1024)
expect(FullStack):
stack.dup(1.i256)
test "pop returns latest stack item":
var stack = newStack()
for element in @[1.vint, 2.vint, 3.vint]:
stack.push(element)
check(stack.popInt == 3)
stack = newStack()
for element in @["1".vbinary]:
stack.push(element)
check(stack.popBinary == "1")
test "swap correct":
var stack = newStack()
for z in 0 ..< 5:
stack.push(z)
check(stack.values == @[0.vint, 1.vint, 2.vint, 3.vint, 4.vint])
stack.swap(3)
check(stack.values == @[0.vint, 4.vint, 2.vint, 3.vint, 1.vint])
stack.swap(1)
check(stack.values == @[0.vint, 4.vint, 2.vint, 1.vint, 3.vint])
test "dup correct":
var stack = newStack()
for z in 0 ..< 5:
stack.push(z)
check(stack.values == @[0.vint, 1.vint, 2.vint, 3.vint, 4.vint])
stack.dup(1)
check(stack.values == @[0.vint, 1.vint, 2.vint, 3.vint, 4.vint, 4.vint])
stack.dup(5)
check(stack.values == @[0.vint, 1.vint, 2.vint, 3.vint, 4.vint, 4.vint, 1.vint])
test "pop raises InsufficientStack appropriately":
var stack = newStack()
expect(InsufficientStack):
discard stack.popInt()
test "swap raises InsufficientStack appropriately":
var stack = newStack()
expect(InsufficientStack):
stack.swap(0)
test "dup raises InsufficientStack appropriately":
var stack = newStack()
expect(InsufficientStack):
stack.dup(0)

View File

@ -2,5 +2,10 @@
nim c tests/code_stream_test.nim
nim c tests/gas_meter_test.nim
nim c tests/memory_test.nim
nim c tests/stack_test.nim
./tests/code_stream_test
./tests/gas_meter_test
./tests/memory_test
./tests/stack_test