This is an automated email from the ASF dual-hosted git repository.

toulmean pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-tuweni.git


The following commit(s) were added to refs/heads/main by this push:
     new d905c378 Add a new library for EVM dsl
     new dc592ce3 Merge pull request #403 from atoulme/evmdsl
d905c378 is described below

commit d905c3787dd009f34be9ebd7b8f91a1ee2d07b85
Author: Antoine Toulme <[email protected]>
AuthorDate: Thu May 26 23:42:20 2022 -0700

    Add a new library for EVM dsl
---
 .../main/java/org/apache/tuweni/bytes/Bytes.java   | 15 ++++++
 .../org/apache/tuweni/bytes/ConcatenatedBytes.java | 47 ++++++++++++++++++
 evm-dsl/build.gradle                               | 45 +++++++++++++++++
 .../main/kotlin/org/apache/tuweni/evmdsl/Code.kt   | 57 ++++++++++++++++++++++
 .../org/apache/tuweni/evmdsl/Instructions.kt       | 56 +++++++++++++++++++++
 .../kotlin/org/apache/tuweni/evmdsl/CodeTest.kt    | 41 ++++++++++++++++
 settings.gradle                                    |  1 +
 7 files changed, 262 insertions(+)

diff --git a/bytes/src/main/java/org/apache/tuweni/bytes/Bytes.java 
b/bytes/src/main/java/org/apache/tuweni/bytes/Bytes.java
index 9186da52..6d71ded7 100644
--- a/bytes/src/main/java/org/apache/tuweni/bytes/Bytes.java
+++ b/bytes/src/main/java/org/apache/tuweni/bytes/Bytes.java
@@ -133,6 +133,21 @@ public interface Bytes extends Comparable<Bytes> {
     return ConcatenatedBytes.wrap(values);
   }
 
+  /**
+   * Wrap a list of other values into a concatenated view.
+   *
+   * <p>
+   * Note that the values are not copied and thus any future update to the 
values will be reflected in the returned
+   * value. If copying the inputs is desired, use {@link 
#concatenate(Bytes...)}.
+   *
+   * @param values The values to wrap.
+   * @return A value representing a view over the concatenation of all {@code 
values}.
+   * @throws IllegalArgumentException if the result overflows an int.
+   */
+  static Bytes wrap(List<Bytes> values) {
+    return ConcatenatedBytes.wrap(values);
+  }
+
   /**
    * Create a value containing the concatenation of the values provided.
    *
diff --git a/bytes/src/main/java/org/apache/tuweni/bytes/ConcatenatedBytes.java 
b/bytes/src/main/java/org/apache/tuweni/bytes/ConcatenatedBytes.java
index d2dc2606..bc452a3f 100644
--- a/bytes/src/main/java/org/apache/tuweni/bytes/ConcatenatedBytes.java
+++ b/bytes/src/main/java/org/apache/tuweni/bytes/ConcatenatedBytes.java
@@ -17,6 +17,7 @@ import static 
com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkElementIndex;
 
 import java.security.MessageDigest;
+import java.util.List;
 
 final class ConcatenatedBytes extends AbstractBytes {
 
@@ -74,6 +75,52 @@ final class ConcatenatedBytes extends AbstractBytes {
     return new ConcatenatedBytes(concatenated, totalSize);
   }
 
+  static Bytes wrap(List<Bytes> values) {
+    if (values.size() == 0) {
+      return EMPTY;
+    }
+    if (values.size() == 1) {
+      return values.get(0);
+    }
+
+    int count = 0;
+    int totalSize = 0;
+
+    for (Bytes value : values) {
+      int size = value.size();
+      try {
+        totalSize = Math.addExact(totalSize, size);
+      } catch (ArithmeticException e) {
+        throw new IllegalArgumentException("Combined length of values is too 
long (> Integer.MAX_VALUE)");
+      }
+      if (value instanceof ConcatenatedBytes) {
+        count += ((ConcatenatedBytes) value).values.length;
+      } else if (size != 0) {
+        count += 1;
+      }
+    }
+
+    if (count == 0) {
+      return Bytes.EMPTY;
+    }
+    if (count == values.size()) {
+      return new ConcatenatedBytes(values.toArray(new Bytes[0]), totalSize);
+    }
+
+    Bytes[] concatenated = new Bytes[count];
+    int i = 0;
+    for (Bytes value : values) {
+      if (value instanceof ConcatenatedBytes) {
+        Bytes[] subvalues = ((ConcatenatedBytes) value).values;
+        System.arraycopy(subvalues, 0, concatenated, i, subvalues.length);
+        i += subvalues.length;
+      } else if (value.size() != 0) {
+        concatenated[i++] = value;
+      }
+    }
+    return new ConcatenatedBytes(concatenated, totalSize);
+  }
+
   @Override
   public int size() {
     return size;
diff --git a/evm-dsl/build.gradle b/evm-dsl/build.gradle
new file mode 100644
index 00000000..2d0c4542
--- /dev/null
+++ b/evm-dsl/build.gradle
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding 
copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+description = 'Ethereum Virtual Machine DSL'
+
+dependencies {
+  implementation project(':bytes')
+  implementation project(':concurrent')
+  implementation project(':crypto')
+  implementation project(':concurrent-coroutines')
+  implementation project(':eth')
+  implementation project(':evm')
+  implementation project(':units')
+  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core'
+  implementation 'org.apache.lucene:lucene-core'
+  implementation 'org.jetbrains.kotlin:kotlin-stdlib'
+  implementation 'org.jetbrains.kotlin:kotlin-reflect'
+  implementation 'org.slf4j:slf4j-api'
+  implementation 'io.vertx:vertx-core'
+
+  testImplementation project(':io')
+  testImplementation project(':merkle-trie')
+  testImplementation project(':junit')
+  testImplementation project(':kv')
+  testImplementation 'io.opentelemetry:opentelemetry-api'
+  testImplementation 'io.opentelemetry:opentelemetry-api-metrics'
+  testImplementation 'io.opentelemetry:opentelemetry-sdk-metrics'
+  testImplementation 'org.bouncycastle:bcprov-jdk15on'
+  testImplementation 'org.junit.jupiter:junit-jupiter-api'
+  testImplementation 'org.junit.jupiter:junit-jupiter-params'
+  testImplementation 'com.fasterxml.jackson.core:jackson-databind'
+  testImplementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml'
+
+  testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
+  testRuntimeOnly 'ch.qos.logback:logback-classic'
+}
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
new file mode 100644
index 00000000..447eae33
--- /dev/null
+++ b/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Code.kt
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tuweni.evmdsl
+
+import org.apache.tuweni.bytes.Bytes
+import kotlin.reflect.full.createInstance
+import kotlin.reflect.full.primaryConstructor
+
+/**
+ * EVM code represented as a set of domain-specific instructions.
+ *
+ * The code can be serialized into bytes to be deployed on a EVM-compatible 
chain.
+ *
+ * The code can also read bytecode and represent it in this DSL.
+ */
+class Code(val instructions: List<Instruction>) {
+
+  companion object {
+    fun read(codeBytes: Bytes): Code {
+      return Code(
+        buildList {
+          var index = 0
+
+          while (index < codeBytes.size()) {
+            val model = InstructionRegistry.opcodes.get(codeBytes.get(index))
+              ?: throw IllegalArgumentException("Unknown opcode " + 
codeBytes.get(index))
+            index++
+            if (model.additionalBytesToRead > 0) {
+              
this.add(model.instructionClass.primaryConstructor!!.call(codeBytes.slice(index,
 model.additionalBytesToRead)))
+              index += model.additionalBytesToRead
+            } else {
+              this.add(model.instructionClass.createInstance())
+            }
+          }
+        }
+      )
+    }
+  }
+
+  fun toBytes(): Bytes {
+    return Bytes.wrap(instructions.map { it.toBytes() })
+  }
+}
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
new file mode 100644
index 00000000..ab5d4b5f
--- /dev/null
+++ b/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Instructions.kt
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tuweni.evmdsl
+
+import org.apache.tuweni.bytes.Bytes
+import kotlin.reflect.KClass
+
+/**
+ * An EVM instruction. It is made of an opcode, optionally followed by bytes 
to be consumed by the opcode execution.
+ */
+interface Instruction {
+
+  fun toBytes(): Bytes
+}
+
+data class InstructionModel(val opcode: Byte, val additionalBytesToRead: Int = 
0, val instructionClass: KClass<out Instruction>)
+
+/**
+ * A registry of instructions that can be used to read code back into the DSL.
+ */
+object InstructionRegistry {
+  val opcodes: Map<Byte, InstructionModel> = buildMap {
+    for (i in 1..32) {
+      val b = (0x60 + i - 1).toByte()
+      this.put(b, InstructionModel(b, i, Push::class))
+    }
+  }
+}
+
+class Push(val bytesToPush: Bytes) : Instruction {
+
+  init {
+    if (bytesToPush.size() > 32) {
+      throw IllegalArgumentException("Push can push at most 32 bytes, 
${bytesToPush.size()} provided")
+    }
+    if (bytesToPush.isEmpty) {
+      throw IllegalArgumentException("Push requires at least one byte")
+    }
+  }
+
+  override fun toBytes(): Bytes = Bytes.wrap(Bytes.of((0x60 + 
bytesToPush.size() - 1).toByte()), bytesToPush)
+}
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
new file mode 100644
index 00000000..92b53204
--- /dev/null
+++ b/evm-dsl/src/test/kotlin/org/apache/tuweni/evmdsl/CodeTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tuweni.evmdsl
+
+import org.apache.tuweni.bytes.Bytes
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+class CodeTest {
+
+  @Test
+  fun testRoundtrip() {
+    val code = Code(
+      buildList {
+        this.add(Push(Bytes.fromHexString("0xdeadbeef")))
+        this.add(Push(Bytes.fromHexString("0xf00ba3")))
+        this.add(Push(Bytes.fromHexString("0xf000")))
+      }
+    )
+    assertEquals(Bytes.fromHexString("0x63deadbeef62f00ba361f000"), 
code.toBytes())
+    val codeRead = Code.read(code.toBytes())
+    assertEquals(3, codeRead.instructions.size)
+    assertEquals(Bytes.fromHexString("0xdeadbeef"), (codeRead.instructions[0] 
as Push).bytesToPush)
+    assertEquals(Bytes.fromHexString("0xf00ba3"), (codeRead.instructions[1] as 
Push).bytesToPush)
+    assertEquals(Bytes.fromHexString("0xf000"), (codeRead.instructions[2] as 
Push).bytesToPush)
+  }
+}
diff --git a/settings.gradle b/settings.gradle
index 3a0ac7f7..38ac4ad9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -35,6 +35,7 @@ include 'eth-reference-tests'
 include 'eth-repository'
 include 'ethstats'
 include 'evm'
+include 'evm-dsl'
 include 'genesis'
 include 'gossip'
 include 'hobbits'


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to