Skip to content

Commit

Permalink
Added list based stack, tests, and benchmarks
Browse files Browse the repository at this point in the history
  • Loading branch information
KonradStaniec committed Sep 22, 2017
1 parent 167e22f commit 498949c
Show file tree
Hide file tree
Showing 3 changed files with 471 additions and 0 deletions.
108 changes: 108 additions & 0 deletions src/main/scala/io/iohk/ethereum/vm/StackL.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package io.iohk.ethereum.vm

import io.iohk.ethereum.domain.UInt256

object StackL {
/**
* Stack max size as defined in the YP (9.1)
*/
val DefaultMaxSize = 1024

def empty(maxSize: Int = DefaultMaxSize): StackL =
new StackL(List(), maxSize)
}

//TODO: consider a List with head being top of the stack (DUP,SWAP go at most the depth of 16) [EC-251]
/**
* Stack for the EVM. Instruction pop their arguments from it and push their results to it.
* The Stack doesn't handle overflow and underflow errors. Any operations that trascend given stack bounds will
* return the stack unchanged. Pop will always return zeroes in such case.
*/
class StackL private(private val underlying: List[UInt256], val maxSize: Int) {

def pop: (UInt256, StackL) = underlying.headOption match {
case Some(word) =>
val updated = underlying.tail
(word, copy(updated))

case None =>
(UInt256.Zero, this)
}

/**
* Pop n elements from the stack. The first element in the resulting sequence will be the top-most element
* in the current stack
*/
def pop(n: Int): (Seq[UInt256], StackL) = {
val (popped, updated) = underlying.splitAt(n)
if (popped.length == n)
(popped, copy(updated))
else
(Seq.fill(n)(UInt256.Zero), this)
}

def push(word: UInt256): StackL = {
val updated = word :: underlying
if (updated.length <= maxSize)
copy(updated)
else
this
}

/**
* Push a sequence of elements to the stack. That last element of the sequence will be the top-most element
* in the resulting stack
*/
def push(words: Seq[UInt256]): StackL = {
val updated = underlying.reverse_:::(words.toList)
if (updated.length > maxSize)
this
else
copy(updated)
}

/**
* Duplicate i-th element of the stack, pushing it to the top. i=0 is the top-most element.
*/
def dup(i: Int): StackL = {
if (i < 0 || i >= underlying.length || underlying.length >= maxSize)
this
else
copy(underlying(i) :: underlying)
}

/**
* Swap i-th and the top-most elements of the stack. i=0 is the top-most element (and that would be a no-op)
*/
def swap(i: Int): StackL = {
if (i <= 0 || i >= underlying.length)
this
else {
val a = underlying.head
val b = underlying(i)
val updated = b :: underlying.updated(i, a).tail
copy(updated)
}
}

def size: Int = underlying.size

/**
* @return the elements of the stack as a sequence, with the top-most element of the stack
* as the first element in the sequence
*/
def toSeq: Seq[UInt256] = underlying

override def equals(that: Any): Boolean = that match {
case that: StackL => this.underlying == that.underlying
case _ => false
}

override def hashCode(): Int = underlying.hashCode

override def toString: String =
underlying.reverse.mkString("Stack(", ",", ")")

private def copy(updated: List[UInt256]): StackL =
new StackL(updated, maxSize)
}
93 changes: 93 additions & 0 deletions src/test/scala/io/iohk/ethereum/vm/StackLSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package io.iohk.ethereum.vm

import io.iohk.ethereum.domain.UInt256
import org.scalacheck.Gen
import org.scalatest.prop.PropertyChecks
import org.scalatest.{FunSuite, Matchers}

class StackLSpec extends FunSuite with Matchers with PropertyChecks {

val maxStackSize = 32
val stackGen = Generators.getStackLGen(maxSize = maxStackSize)
val intGen = Gen.choose(0, maxStackSize).filter(_ >= 0)
val uint256Gen = Generators.getUInt256Gen()
val uint256ListGen = Generators.getListGen(0, 16, uint256Gen)

test("pop single element") {
forAll(stackGen) { stack =>
val (v, stack1) = stack.pop
if (stack.size > 0) {
v shouldEqual stack.toSeq.head
stack1.toSeq shouldEqual stack.toSeq.tail
} else {
v shouldEqual 0
stack1 shouldEqual stack
}
}
}

test("pop multiple elements") {
forAll(stackGen, intGen) { (stack, i) =>
val (vs, stack1) = stack.pop(i)
if (stack.size >= i) {
vs shouldEqual stack.toSeq.take(i)
stack1.toSeq shouldEqual stack.toSeq.drop(i)
} else {
vs shouldEqual Seq.fill(i)(UInt256.Zero)
stack1 shouldEqual stack
}
}
}

test("push single element") {
forAll(stackGen, uint256Gen) { (stack, v) =>
val stack1 = stack.push(v)

if (stack.size < stack.maxSize) {
stack1.toSeq shouldEqual (v +: stack.toSeq)
} else {
stack1 shouldEqual stack
}
}
}

test("push multiple elements") {
forAll(stackGen, uint256ListGen) { (stack, vs) =>
val stack1 = stack.push(vs)

if (stack.size + vs.size <= stack.maxSize) {
stack1.toSeq shouldEqual (vs.reverse ++ stack.toSeq)
} else {
stack1 shouldEqual stack
}
}
}

test("duplicate element") {
forAll(stackGen, intGen) { (stack, i) =>
val stack1 = stack.dup(i)

if (i < stack.size && stack.size < stack.maxSize) {
val x = stack.toSeq(i)
stack1.toSeq shouldEqual (x +: stack.toSeq)
} else {
stack1 shouldEqual stack
}
}
}

test("swap elements") {
forAll(stackGen, intGen) { (stack, i) =>
val stack1 = stack.swap(i)

if (i < stack.size) {
val x = stack.toSeq.head
val y = stack.toSeq(i)
stack1.toSeq shouldEqual stack.toSeq.updated(0, y).updated(i, x)
} else {
stack1 shouldEqual stack
}
}
}

}
Loading

0 comments on commit 498949c

Please sign in to comment.