Finish the first memory impl and tests, stack tests, fixes validation
This commit is contained in:
parent
04b1228fbe
commit
211d46e39e
|
@ -1,2 +1,3 @@
|
|||
nimcache/
|
||||
runner
|
||||
*_test
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Nimbus
|
||||
|
||||
An Ethereum 2.0 Sharding Client for Resource-Restricted Devices.
|
||||
|
||||
https://docs.google.com/document/d/14u65XVNLOd83cq3t7wNC9UPweZ6kPWvmXwRTWWn0diQ/edit#
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
|
@ -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)
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue