diff --git a/src/main/scala/org/scalacoin/script/constant/Constants.scala b/src/main/scala/org/scalacoin/script/constant/Constants.scala index dde78dd37d..cae2126039 100644 --- a/src/main/scala/org/scalacoin/script/constant/Constants.scala +++ b/src/main/scala/org/scalacoin/script/constant/Constants.scala @@ -11,6 +11,7 @@ trait ScriptToken { def hex : String def bytes = ScalacoinUtil.decodeHex(hex) def bytesSize = bytes.size + def toInt = Integer.parseInt(hex,16) } trait ScriptOperation extends ScriptToken { diff --git a/src/main/scala/org/scalacoin/script/interpreter/ScriptInterpreter.scala b/src/main/scala/org/scalacoin/script/interpreter/ScriptInterpreter.scala index f35d930435..5daf2dfed9 100644 --- a/src/main/scala/org/scalacoin/script/interpreter/ScriptInterpreter.scala +++ b/src/main/scala/org/scalacoin/script/interpreter/ScriptInterpreter.scala @@ -2,6 +2,7 @@ package org.scalacoin.script.interpreter import org.scalacoin.protocol.script.{ScriptSignature, ScriptPubKey} import org.scalacoin.protocol.transaction.Transaction +import org.scalacoin.script.splice.{SpliceInterpreter, OP_SIZE} import org.scalacoin.script.{ScriptProgramImpl, ScriptProgram} import org.scalacoin.script.arithmetic.{ArithmeticInterpreter, OP_ADD} import org.scalacoin.script.bitwise.{OP_EQUAL, BitwiseInterpreter, OP_EQUALVERIFY} @@ -18,7 +19,7 @@ import scala.annotation.tailrec * Created by chris on 1/6/16. */ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with ControlOperationsInterpreter - with BitwiseInterpreter with ConstantInterpreter with ArithmeticInterpreter { + with BitwiseInterpreter with ConstantInterpreter with ArithmeticInterpreter with SpliceInterpreter { private def logger = LoggerFactory.getLogger(this.getClass().toString) @@ -105,6 +106,8 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con //reserved operations case (nop : NOP) :: t => loop(ScriptProgramImpl(program.stack,t,program.transaction,program.altStack)) + //splice operations + case OP_SIZE :: t => loop(opSize(program)) //no more script operations to run, True is represented by any representation of non-zero case Nil => program.stack.head != ScriptFalse case h :: t => throw new RuntimeException(h + " was unmatched") diff --git a/src/main/scala/org/scalacoin/script/splice/SpliceInterpreter.scala b/src/main/scala/org/scalacoin/script/splice/SpliceInterpreter.scala new file mode 100644 index 0000000000..7af38f994b --- /dev/null +++ b/src/main/scala/org/scalacoin/script/splice/SpliceInterpreter.scala @@ -0,0 +1,27 @@ +package org.scalacoin.script.splice + +import org.scalacoin.script.{ScriptOperationFactory, ScriptProgramImpl, ScriptProgram} +import org.scalacoin.script.constant.ScriptConstantImpl +import org.scalacoin.util.ScalacoinUtil + +/** + * Created by chris on 2/4/16. + */ +trait SpliceInterpreter { + + /** + * Pushes the string length of the top element of the stack (without popping it). + * @param program + * @return + */ + def opSize(program : ScriptProgram) : ScriptProgram = { + require(program.script.headOption.isDefined && program.script.head == OP_SIZE, "Script top must be OP_SIZE") + require(program.stack.size > 0, "Must have at least 1 element on the stack for OP_SIZE") + val stringSize = program.stack.head.bytes.size + val hex = ScalacoinUtil.encodeHex(stringSize.toByte) + val scriptOperation = ScriptOperationFactory.fromHex(hex) + val size = if (scriptOperation.isDefined) scriptOperation.get else ScriptConstantImpl(hex) + ScriptProgramImpl(size :: program.stack, program.script.tail, program.transaction,program.altStack) + } + +} diff --git a/src/main/scala/org/scalacoin/util/BinaryTree.scala b/src/main/scala/org/scalacoin/util/BinaryTree.scala index 3d26580f9e..164dd8cb2e 100644 --- a/src/main/scala/org/scalacoin/util/BinaryTree.scala +++ b/src/main/scala/org/scalacoin/util/BinaryTree.scala @@ -141,12 +141,14 @@ trait BinaryTree[+T] { + def toSeq : Seq[T] = { //TODO: Optimize this into a tailrec function + //@tailrec def loop(tree : BinaryTree[T],accum : List[T]) : List[T] = tree match { - case Leaf(x) => x :: accum + case Leaf(x) => accum ++ List(x) case Empty => accum - case Node(v,l,r) => v :: loop(l,List()) ++ loop(r,List()) + case Node(v,l,r) => loop(r,loop(l,accum ++ List(v))) } loop(this,List()) } diff --git a/src/test/scala/org/scalacoin/script/interpreter/ScriptInterpreterTest.scala b/src/test/scala/org/scalacoin/script/interpreter/ScriptInterpreterTest.scala index 7824ba6f1c..4ba9ba7336 100644 --- a/src/test/scala/org/scalacoin/script/interpreter/ScriptInterpreterTest.scala +++ b/src/test/scala/org/scalacoin/script/interpreter/ScriptInterpreterTest.scala @@ -54,13 +54,13 @@ class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterp val source = scala.io.Source.fromFile("src/test/scala/org/scalacoin/script/interpreter/script_valid.json") //use this to represent a single test case from script_valid.json -/* val lines = + val lines = """ | - |[["10 0 11 TOALTSTACK DROP FROMALTSTACK", "ADD 21 EQUAL", "P2SH,STRICTENC"]] - """.stripMargin*/ + |[["128", "SIZE 2 EQUAL", "P2SH,STRICTENC"]] + """.stripMargin - val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close() + //val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close() val json = lines.parseJson val testCasesOpt : Seq[Option[CoreTestCase]] = json.convertTo[Seq[Option[CoreTestCase]]] val testCases : Seq[CoreTestCase] = testCasesOpt.flatten diff --git a/src/test/scala/org/scalacoin/script/splice/SpliceInterpreterTest.scala b/src/test/scala/org/scalacoin/script/splice/SpliceInterpreterTest.scala new file mode 100644 index 0000000000..9c0b6d97e8 --- /dev/null +++ b/src/test/scala/org/scalacoin/script/splice/SpliceInterpreterTest.scala @@ -0,0 +1,32 @@ +package org.scalacoin.script.splice + +import org.scalacoin.script.ScriptProgramImpl +import org.scalacoin.script.bitwise.OP_EQUAL +import org.scalacoin.script.constant.{OP_2, ScriptConstantImpl, OP_0} +import org.scalacoin.util.TestUtil +import org.scalatest.{MustMatchers, FlatSpec} + +/** + * Created by chris on 2/4/16. + */ +class SpliceInterpreterTest extends FlatSpec with MustMatchers with SpliceInterpreter { + + "SpliceInterpreter" must "evaluate an OP_SIZE correctly" in { + val stack = List(OP_0) + val script = List(OP_SIZE) + val program = ScriptProgramImpl(stack,script,TestUtil.transaction,List()) + val newProgram = opSize(program) + newProgram.stack must be (List(OP_0,OP_0)) + newProgram.script.isEmpty must be (true) + + } + + it must "evaluate an OP_SIZE correctly with something of size 2 bytes" in { + val stack = List(ScriptConstantImpl("80")) + val script = List(OP_SIZE) + val program = ScriptProgramImpl(stack,script,TestUtil.transaction,List()) + val newProgram = opSize(program) + newProgram.stack must be (List(OP_2),ScriptConstantImpl("80")) + newProgram.script.isEmpty must be (true) + } +} diff --git a/src/test/scala/org/scalacoin/util/BinaryTreeTest.scala b/src/test/scala/org/scalacoin/util/BinaryTreeTest.scala index 15bf02ff24..aa3110dbc7 100644 --- a/src/test/scala/org/scalacoin/util/BinaryTreeTest.scala +++ b/src/test/scala/org/scalacoin/util/BinaryTreeTest.scala @@ -17,16 +17,30 @@ class BinaryTreeTest extends FlatSpec with MustMatchers { } it must "convert a binary tree to to a list with node values" in { - //val script = List(OP_IF, OP_IF, OP_1, OP_ELSE, OP_0, OP_ENDIF, OP_ELSE, OP_IF, OP_0, OP_ELSE, OP_1, OP_ENDIF, OP_ENDIF) val bTree : BinaryTree[ScriptToken] = Node[ScriptToken](OP_IF,Node(OP_IF,Leaf(OP_1),Node(OP_ELSE,Leaf(OP_0),Leaf(OP_ENDIF))), Node(OP_ELSE,Node(OP_IF,Leaf(OP_0), Node(OP_ELSE,Leaf(OP_1),Leaf(OP_ENDIF))),Leaf(OP_ENDIF))) val script = List(OP_IF, OP_IF, OP_1, OP_ELSE, OP_0, OP_ENDIF, OP_ELSE, OP_IF, OP_0, OP_ELSE, OP_1, OP_ENDIF, OP_ENDIF) + bTree.toSeq.size must be (script.size) bTree.toSeq must be (script) } + it must "convert a simple binary tree to a sequence" in { + val bTree = Node(1,Leaf(2), Leaf(3)) + val seq = bTree.toSeq + seq must be (Seq(1,2,3)) + + val bTree1 = Node(1,Node(2,Empty,Empty), Node(3,Empty,Empty)) + val seq1 = bTree1.toSeq + seq1 must be (List(1,2,3)) + + val bTree2 = Node(OP_IF,Node(OP_1,Empty,Empty), Node(OP_ELSE,Node(OP_2,Empty,Empty),Leaf(OP_ENDIF))) + val seq2 = bTree2.toSeq + seq2 must be (Seq(OP_IF,OP_1,OP_ELSE, OP_2,OP_ENDIF)) + } + it must "find the first occurrence of a element in the tree" in { val tree = Node[Int](0,Node(1,Leaf(2),Leaf(3)), Node(1,Leaf(4), Leaf(5)))