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]