Merge pull request #428 from atoulme/custom_opcode_support

Add support for new custom opcode in DSL
diff --git a/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Code.kt b/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Code.kt
index abe1db3..1afd665 100644
--- a/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Code.kt
+++ b/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Code.kt
@@ -51,6 +51,8 @@
     var stackSize = 0
     val visited = mutableSetOf<Int>()
     var index = 0
+    val jumpDests = mutableSetOf<Int>()
+    val jumpSrcs = mutableMapOf<Int, Int>()
     while (visited.add(index)) {
       val currentInstruction = instructions.getOrNull(index) ?: break
       if (currentInstruction.stackItemsNeeded() > stackSize) {
@@ -63,11 +65,15 @@
       if (currentInstruction is Invalid) {
         return CodeValidationError(currentInstruction, index, Error.HIT_INVALID_OPCODE)
       }
-      // TODO cannot follow jumps right now.
       if (currentInstruction == Jump || currentInstruction == Jumpi) {
-        break
+        jumpSrcs.put(index, stackSize)
       }
-
+      if (currentInstruction == JumpDest) {
+        jumpDests.add(index)
+      }
+      if (currentInstruction.end()) {
+        stackSize = 0
+      }
       index++
     }
     return null
diff --git a/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Instructions.kt b/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Instructions.kt
index 04be3e1..3cc12ec 100644
--- a/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Instructions.kt
+++ b/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Instructions.kt
@@ -31,6 +31,8 @@
   fun stackItemsConsumed(): Int
 
   fun stackItemsProduced(): Int
+
+  fun end(): Boolean = false
 }
 
 data class InstructionModel(val opcode: Byte, val additionalBytesToRead: Int = 0, val creator: (code: Bytes, index: Int) -> Instruction)
@@ -665,6 +667,7 @@
   override fun toString(): String = "RETURN"
   override fun stackItemsConsumed() = 2
   override fun stackItemsProduced() = 0
+  override fun end() = true
 }
 
 object DelegateCall : Instruction {
@@ -696,6 +699,7 @@
   override fun toString(): String = "REVERT"
   override fun stackItemsConsumed() = 2
   override fun stackItemsProduced() = 0
+  override fun end() = true
 }
 
 object SelfDestruct : Instruction {
@@ -728,3 +732,10 @@
   override fun stackItemsConsumed() = logIndex + 2
   override fun stackItemsProduced() = 0
 }
+
+data class Custom(val bytes: Bytes, val str: String, val consumed: Int, val produced: Int) : Instruction {
+  override fun toBytes() = bytes
+  override fun toString() = str
+  override fun stackItemsConsumed() = consumed
+  override fun stackItemsProduced() = produced
+}
diff --git a/evm-dsl/src/test/kotlin/org/apache/tuweni/evmdsl/CodeTest.kt b/evm-dsl/src/test/kotlin/org/apache/tuweni/evmdsl/CodeTest.kt
index dcdd648..e1cc2c4 100644
--- a/evm-dsl/src/test/kotlin/org/apache/tuweni/evmdsl/CodeTest.kt
+++ b/evm-dsl/src/test/kotlin/org/apache/tuweni/evmdsl/CodeTest.kt
@@ -17,6 +17,7 @@
 package org.apache.tuweni.evmdsl
 
 import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.bytes.Bytes32
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.jupiter.api.Assertions.assertNull
 import org.junit.jupiter.api.Test
@@ -114,4 +115,47 @@
     )
     assertNull(code.validate()?.error)
   }
+
+  @Test
+  fun testSomeWorkshopCode() {
+    val code = Code(
+      buildList {
+        this.add(Push(Bytes.fromHexString("0x00")))
+        this.add(CallDataLoad) // load from position 0
+        this.add(Custom(Bytes.fromHexString("0xf6"), "SHAREDSECRET", 1, 1))
+        this.add(Push(Bytes.fromHexString("0x11"))) // push jump destination
+        this.add(Jumpi) // if custom returns 0, keep going, else go to jumpdest at byte 0x11
+        this.add(Push(Bytes.fromHexString("0x00"))) // value
+        this.add(Push(Bytes.fromHexString("0x00"))) // location
+        this.add(Mstore) // store 0 in memory
+        this.add(Push(Bytes.fromHexString("0x01"))) // length
+        this.add(Push(Bytes.fromHexString("0x1f"))) // location
+        this.add(Return) // return 0
+        this.add(JumpDest)
+        this.add(Push(Bytes.fromHexString("0x01"))) // value
+        this.add(Push(Bytes.fromHexString("0x00"))) // location
+        this.add(Mstore) // store 1 in memory
+        this.add(Push(Bytes.fromHexString("0x01"))) // length
+        this.add(Push(Bytes.fromHexString("0x1f"))) // location
+        this.add(Return)
+      }
+    )
+    assertNull(code.validate()?.error)
+    println(code.toString())
+    println(code.toBytes().toHexString())
+
+    // surround with instructions to deploy.
+    val deployment = Code(
+      buildList {
+        this.add(Push(Bytes32.rightPad(code.toBytes()))) // pad the code with zeros to create a word
+        this.add(Push(Bytes.fromHexString("0x00"))) // set the location of the memory to store
+        this.add(Mstore)
+        this.add(Push(Bytes.ofUnsignedInt(code.toBytes().size().toLong()))) // length
+        this.add(Push(Bytes.fromHexString("0x00"))) // location
+        this.add(Return) // return the code
+      }
+    )
+
+    println(deployment.toBytes().toHexString())
+  }
 }