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/
|
nimcache/
|
||||||
runner
|
runner
|
||||||
|
*_test
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Nimbus
|
# Nimbus
|
||||||
|
|
||||||
An Ethereum 2.0 Sharding Client for Resource-Restricted Devices.
|
An Ethereum 2.0 Sharding Client for Resource-Restricted Devices.
|
||||||
|
|
||||||
https://docs.google.com/document/d/14u65XVNLOd83cq3t7wNC9UPweZ6kPWvmXwRTWWn0diQ/edit#
|
https://docs.google.com/document/d/14u65XVNLOd83cq3t7wNC9UPweZ6kPWvmXwRTWWn0diQ/edit#
|
||||||
|
|
|
@ -11,7 +11,13 @@ proc int256*(i: int): Int256 =
|
||||||
i.initBigInt
|
i.initBigInt
|
||||||
|
|
||||||
template i256*(i: int): Int256 =
|
template i256*(i: int): Int256 =
|
||||||
i.int256
|
i.initBigInt
|
||||||
|
|
||||||
|
template i256*(i: Int256): Int256 =
|
||||||
|
i
|
||||||
|
|
||||||
|
template getInt*(i: int): int =
|
||||||
|
i
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# We'll have a fast fixed i256, for now this works
|
# We'll have a fast fixed i256, for now this works
|
||||||
|
@ -77,7 +83,7 @@ mapOp(`xor`)
|
||||||
proc `abs`*(a: Int256): Int256 =
|
proc `abs`*(a: Int256): Int256 =
|
||||||
if a >= 0.i256: a else: -a
|
if a >= 0.i256: a else: -a
|
||||||
|
|
||||||
proc `getInt`*(a: Int256): int =
|
template `getInt`*(a: Int256): int =
|
||||||
a.limbs[0].int
|
a.limbs[0].int
|
||||||
|
|
||||||
let
|
let
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
import
|
import
|
||||||
strformat,
|
strformat,
|
||||||
errors, constants, bigints
|
errors, constants, bigints
|
||||||
|
@ -8,27 +6,37 @@ proc validateCanonicalAddress*(value: string, title: string = "Value") =
|
||||||
# TODO
|
# TODO
|
||||||
if false: #len(value) != 20:
|
if false: #len(value) != 20:
|
||||||
raise newException(ValidationError,
|
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") =
|
proc validateGte*(value: Int256 | int, minimum: int, title: string = "Value") =
|
||||||
if value <= minimum.int256:
|
if value.i256 < minimum.i256:
|
||||||
raise newException(ValidationError,
|
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") =
|
proc validateGt*(value: Int256 | int, minimum: int, title: string = "Value") =
|
||||||
if value <= minimum:
|
if value.i256 <= minimum.i256:
|
||||||
raise newException(ValidationError,
|
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") =
|
proc validateLength*[T](values: seq[T], size: int) =
|
||||||
if value < minimum.int256:
|
if values.len != size:
|
||||||
raise newException(ValidationError,
|
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") =
|
proc validateLte*(value: Int256 | int, maximum: int, title: string = "Value") =
|
||||||
if value < minimum:
|
if value.i256 > maximum.i256:
|
||||||
raise newException(ValidationError,
|
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")
|
result.logger = logging.getLogger("evm.vm.memory.Memory")
|
||||||
|
|
||||||
proc len*(memory: Memory): int =
|
proc len*(memory: Memory): int =
|
||||||
result = len(memory.bytes)
|
result = memory.bytes.len
|
||||||
|
|
||||||
proc extend*(memory: var Memory; startPosition: Int256; size: Int256) =
|
proc extend*(memory: var Memory; startPosition: Int256; size: Int256) =
|
||||||
if size == 0:
|
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]
|
result = memory.bytes[startPosition.getInt ..< (startPosition + size).getInt]
|
||||||
|
|
||||||
proc write*(memory: var Memory, startPosition: Int256, size: Int256, value: seq[byte]) =
|
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) =
|
template write*(memory: var Memory, startPosition: Int256, size: Int256, value: cstring) =
|
||||||
memory.write(startPosition, size, value.toBytes)
|
memory.write(startPosition, size, value.toBytes)
|
||||||
|
|
|
@ -19,24 +19,31 @@ proc len*(stack: Stack): int =
|
||||||
proc push*(stack: var Stack; value: Value) =
|
proc push*(stack: var Stack; value: Value) =
|
||||||
## Push an item onto the stack
|
## Push an item onto the stack
|
||||||
ensureStackLimit()
|
ensureStackLimit()
|
||||||
|
if value.kind == VInt:
|
||||||
|
validateGte(value.i, 0)
|
||||||
|
else:
|
||||||
|
validateStackItem(value.b)
|
||||||
|
|
||||||
stack.values.add(value)
|
stack.values.add(value)
|
||||||
|
|
||||||
proc push*(stack: var Stack; value: int) =
|
proc push*(stack: var Stack; value: int) =
|
||||||
## Push an integer onto the stack
|
## Push an integer onto the stack
|
||||||
ensureStackLimit()
|
ensureStackLimit()
|
||||||
|
validateGte(value, 0)
|
||||||
|
|
||||||
stack.values.add(Value(kind: VInt, i: value.int256))
|
stack.values.add(Value(kind: VInt, i: value.int256))
|
||||||
|
|
||||||
proc push*(stack: var Stack; value: Int256) =
|
proc push*(stack: var Stack; value: Int256) =
|
||||||
## Push an integer onto the stack
|
## Push an integer onto the stack
|
||||||
ensureStackLimit()
|
ensureStackLimit()
|
||||||
|
validateGte(value, 0)
|
||||||
|
|
||||||
stack.values.add(Value(kind: VInt, i: value))
|
stack.values.add(Value(kind: VInt, i: value))
|
||||||
|
|
||||||
proc push*(stack: var Stack; value: string) =
|
proc push*(stack: var Stack; value: string) =
|
||||||
## Push a binary onto the stack
|
## Push a binary onto the stack
|
||||||
ensureStackLimit()
|
ensureStackLimit()
|
||||||
|
validateStackItem(value)
|
||||||
|
|
||||||
stack.values.add(Value(kind: VBinary, b: value))
|
stack.values.add(Value(kind: VBinary, b: value))
|
||||||
|
|
||||||
|
@ -159,10 +166,10 @@ proc swap*(stack: var Stack; position: int) =
|
||||||
raise newException(InsufficientStack,
|
raise newException(InsufficientStack,
|
||||||
&"Insufficient stack items for SWAP{position}")
|
&"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
|
## Perform a DUP operation on the stack
|
||||||
if position < len(stack) + 1:
|
if (position != 0 and position.getInt < stack.len + 1) or (position == 0 and position.getInt < stack.len):
|
||||||
stack.push(stack.values[^position])
|
stack.push(stack.values[^position.getInt])
|
||||||
else:
|
else:
|
||||||
raise newException(InsufficientStack,
|
raise newException(InsufficientStack,
|
||||||
&"Insufficient stack items for DUP{position}")
|
&"Insufficient stack items for DUP{position}")
|
||||||
|
|
|
@ -28,3 +28,11 @@ proc vint*(i: Int256): Value =
|
||||||
proc vbinary*(b: string): Value =
|
proc vbinary*(b: string): Value =
|
||||||
Value(kind: VBinary, b: b)
|
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/code_stream_test.nim
|
||||||
nim c tests/gas_meter_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/code_stream_test
|
||||||
./tests/gas_meter_test
|
./tests/gas_meter_test
|
||||||
|
./tests/memory_test
|
||||||
|
./tests/stack_test
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue