From 211d46e39ecb33fe84306f3b208ac58324061512 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 6 Feb 2018 21:20:06 +0200 Subject: [PATCH] Finish the first memory impl and tests, stack tests, fixes validation --- .gitignore | 1 + README.md | 1 + src/constants.nim | 10 ++++-- src/validation.nim | 38 ++++++++++++--------- src/vm/memory.nim | 16 +++++++-- src/vm/stack.nim | 15 ++++++--- src/vm/value.nim | 8 +++++ tests/memory_test.nim | 61 ++++++++++++++++++++++++++++++++++ tests/stack_test.nim | 77 +++++++++++++++++++++++++++++++++++++++++++ tests/test.sh | 5 +++ 10 files changed, 209 insertions(+), 23 deletions(-) create mode 100644 tests/memory_test.nim create mode 100644 tests/stack_test.nim diff --git a/.gitignore b/.gitignore index cdae7c60e..05b1eb201 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ nimcache/ runner +*_test diff --git a/README.md b/README.md index 46b9ee4cf..8895cc317 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Nimbus + An Ethereum 2.0 Sharding Client for Resource-Restricted Devices. https://docs.google.com/document/d/14u65XVNLOd83cq3t7wNC9UPweZ6kPWvmXwRTWWn0diQ/edit# diff --git a/src/constants.nim b/src/constants.nim index ab68f5359..d687d0be3 100644 --- a/src/constants.nim +++ b/src/constants.nim @@ -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 diff --git a/src/validation.nim b/src/validation.nim index 305966110..28bd15e30 100644 --- a/src/validation.nim +++ b/src/validation.nim @@ -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}") diff --git a/src/vm/memory.nim b/src/vm/memory.nim index 8c17721fe..7c31e69d8 100644 --- a/src/vm/memory.nim +++ b/src/vm/memory.nim @@ -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) diff --git a/src/vm/stack.nim b/src/vm/stack.nim index c8abfb388..2211b47ae 100644 --- a/src/vm/stack.nim +++ b/src/vm/stack.nim @@ -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}") diff --git a/src/vm/value.nim b/src/vm/value.nim index c6f1e8536..bc784fcb1 100644 --- a/src/vm/value.nim +++ b/src/vm/value.nim @@ -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 diff --git a/tests/memory_test.nim b/tests/memory_test.nim new file mode 100644 index 000000000..4534013d4 --- /dev/null +++ b/tests/memory_test.nim @@ -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]) diff --git a/tests/stack_test.nim b/tests/stack_test.nim new file mode 100644 index 000000000..642f67837 --- /dev/null +++ b/tests/stack_test.nim @@ -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) diff --git a/tests/test.sh b/tests/test.sh index bf073d2c1..bd620c6a5 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -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 +