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 64474b7d Add Deployment helper to help deploy code
     new b2798bf1 Merge pull request #430 from atoulme/code_deployment
64474b7d is described below

commit 64474b7dc4e319e7940a7468cc76f7e9c8543709
Author: Antoine Toulme <anto...@lunar-ocean.com>
AuthorDate: Mon Aug 8 23:03:00 2022 -0700

    Add Deployment helper to help deploy code
---
 .../main/java/org/apache/tuweni/bytes/Bytes.java   | 31 ++++++++++++
 .../java/org/apache/tuweni/bytes/BytesTest.java    | 33 +++++++++++++
 .../main/kotlin/org/apache/tuweni/evmdsl/Code.kt   | 27 ++++++++++
 .../kotlin/org/apache/tuweni/evmdsl/Deployment.kt  | 57 ++++++++++++++++++++++
 .../org/apache/tuweni/evmdsl/Instructions.kt       | 15 ++++--
 .../kotlin/org/apache/tuweni/evmdsl/CodeTest.kt    | 42 ++++++++++++++++
 .../org/apache/tuweni/evmdsl/DeploymentTest.kt     | 30 ++++++++++++
 7 files changed, 231 insertions(+), 4 deletions(-)

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 6d71ded7..a9309529 100644
--- a/bytes/src/main/java/org/apache/tuweni/bytes/Bytes.java
+++ b/bytes/src/main/java/org/apache/tuweni/bytes/Bytes.java
@@ -617,6 +617,22 @@ public interface Bytes extends Comparable<Bytes> {
     return new ConstantBytesValue(b, size);
   }
 
+  /**
+   * Splits a Bytes object into Bytes32 objects. If the last element is not 
exactly 32 bytes, it is right padded with
+   * zeros.
+   *
+   * @param bytes the bytes object to segment
+   * @return an array of Bytes32 objects
+   */
+  static Bytes32[] segment(Bytes bytes) {
+    int segments = (int) Math.ceil(bytes.size() / 32.0);
+    Bytes32[] result = new Bytes32[segments];
+    for (int i = 0; i < segments; i++) {
+      result[i] = Bytes32.rightPad(bytes.slice(i * 32, Math.min(32, 
bytes.size() - i * 32)));
+    }
+    return result;
+  }
+
   /**
    *
    * Provides the number of bytes this value represents.
@@ -1472,6 +1488,21 @@ public interface Bytes extends Comparable<Bytes> {
     return Bytes.EMPTY;
   }
 
+  /**
+   * Return a slice of representing the same value but without any trailing 
zero bytes.
+   *
+   * @return {@code value} if its right-most byte is non zero, or a slice that 
exclude any trailing zero bytes.
+   */
+  default Bytes trimTrailingZeros() {
+    int size = size();
+    for (int i = size - 1; i >= 0; i--) {
+      if (get(i) != 0) {
+        return slice(0, i + 1);
+      }
+    }
+    return Bytes.EMPTY;
+  }
+
   /**
    * Update the provided message digest with the bytes of this value.
    *
diff --git a/bytes/src/test/java/org/apache/tuweni/bytes/BytesTest.java 
b/bytes/src/test/java/org/apache/tuweni/bytes/BytesTest.java
index 482d3bef..35507ef9 100644
--- a/bytes/src/test/java/org/apache/tuweni/bytes/BytesTest.java
+++ b/bytes/src/test/java/org/apache/tuweni/bytes/BytesTest.java
@@ -664,4 +664,37 @@ class BytesTest extends CommonBytesTests {
     ByteBuf buffer = Unpooled.buffer(20).writeByte(3);
     assertEquals(1, Bytes.wrapByteBuf(buffer, 0, 
buffer.readableBytes()).size());
   }
+
+  @Test
+  void segmentBytes() {
+    Bytes b = Bytes
+        .wrap(
+            Bytes32.ZERO,
+            Bytes32.random(),
+            Bytes32.rightPad(Bytes.fromHexStringLenient("0x1")),
+            Bytes.fromHexString("0xf000"));
+    Bytes32[] result = Bytes.segment(b);
+    assertEquals(4, result.length);
+    assertEquals(Bytes32.rightPad(Bytes.fromHexString("0xf000")), result[3]);
+  }
+
+  @Test
+  void segments() {
+    Bytes value = 
Bytes.fromHexString("0x7b600035f660115760006000526001601ff35b60016000526001601ff3600052601c6000f3");
+    Bytes32[] result = Bytes.segment(value);
+    
assertEquals(Bytes.fromHexString("0x7b600035f660115760006000526001601ff35b60016000526001601ff3600052"),
 result[0]);
+    
assertEquals(Bytes.fromHexString("0x601c6000f3000000000000000000000000000000000000000000000000000000"),
 result[1]);
+  }
+
+  @Test
+  void testTrimLeadingZeros() {
+    Bytes b = Bytes.fromHexString("0x000000f300567800");
+    assertEquals(Bytes.fromHexString("0xf300567800"), b.trimLeadingZeros());
+  }
+
+  @Test
+  void testTrimTrailingZeros() {
+    Bytes b = Bytes.fromHexString("0x000000f300567800");
+    assertEquals(Bytes.fromHexString("0x000000f3005678"), 
b.trimTrailingZeros());
+  }
 }
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 1afd6655..c399c4a8 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
@@ -17,6 +17,7 @@
 package org.apache.tuweni.evmdsl
 
 import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.bytes.Bytes32
 
 /**
  * EVM code represented as a set of domain-specific instructions.
@@ -28,6 +29,9 @@ import org.apache.tuweni.bytes.Bytes
 class Code(val instructions: List<Instruction>) {
 
   companion object {
+    /**
+     * Reads the bytecode of code and interprets instructions into opcodes.
+     */
     fun read(codeBytes: Bytes): Code {
       return Code(
         buildList {
@@ -45,6 +49,29 @@ class Code(val instructions: List<Instruction>) {
         }
       )
     }
+
+    /**
+     * Generates a simple bytecode to be of exactly a given size
+     */
+    fun generate(size: Int): Code {
+      val words32 = size.floorDiv(34)
+      val remainder = size.rem(34)
+      val list = mutableListOf<Instruction>()
+      for (i in 0 until words32) {
+        list.add(Push(Bytes32.rightPad(Bytes.fromHexString("0x0b4dc0ff33"))))
+        list.add(Pop)
+      }
+      if (remainder > 2) {
+        list.add(Push(Bytes.wrap(Bytes.repeat(1.toByte(), remainder - 2))))
+      }
+      if (remainder == 2) {
+        list.add(Origin)
+      }
+      if (remainder > 0) {
+        list.add(Return)
+      }
+      return Code(list)
+    }
   }
 
   fun validate(): CodeValidationError? {
diff --git a/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Deployment.kt 
b/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Deployment.kt
new file mode 100644
index 00000000..b576eb97
--- /dev/null
+++ b/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Deployment.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 org.apache.tuweni.units.bigints.UInt256
+
+/**
+ * Creates just enough EVM opcodes to wrap a deployment of a smart contract.
+ *
+ * Pushes the contract bytecode into memory, and returns the whole bytecode as 
the result of the execution of the deployment execution.
+ *
+ */
+class Deployment(val code: Code) {
+
+  companion object {
+    /**
+     * Generates a bytecode of a deployment of code of an exact size.
+     * @param size the size of the contract deployed
+     */
+    fun generate(size: Int) = Deployment(Code.generate(size))
+  }
+
+  fun toBytes(): Bytes {
+    val codeBytes = code.toBytes()
+    val deployment = Code(
+      buildList {
+        var location = UInt256.ZERO
+        println(Bytes.segment(codeBytes))
+        for (segment in Bytes.segment(codeBytes)) {
+          this.add(Push(segment)) // push a segment of code to store
+          this.add(Push(location)) // set the location of the memory to store
+          this.add(Mstore)
+          location += UInt256.valueOf(32)
+        }
+        this.add(Push(Bytes.ofUnsignedInt(codeBytes.size().toLong()))) // 
length
+        this.add(Push(Bytes.fromHexString("0x00"))) // location
+        this.add(Return) // return the code
+      }
+    )
+    return deployment.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
index 3cc12ec5..0275ff76 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
@@ -169,15 +169,22 @@ object InstructionRegistry {
   }
 }
 
-class Push(val bytesToPush: Bytes) : Instruction {
+class Push(bytes: Bytes) : Instruction {
 
+  val bytesToPush: Bytes
   init {
-    if (bytesToPush.size() > 32) {
-      throw IllegalArgumentException("Push can push at most 32 bytes, 
${bytesToPush.size()} provided")
+    if (bytes.size() > 32) {
+      throw IllegalArgumentException("Push can push at most 32 bytes, 
${bytes.size()} provided")
     }
-    if (bytesToPush.isEmpty) {
+    if (bytes.isEmpty) {
       throw IllegalArgumentException("Push requires at least one byte")
     }
+    val trimmedBytes = bytes.trimLeadingZeros()
+    bytesToPush = if (trimmedBytes.isEmpty) {
+      Bytes.fromHexString("0x00")
+    } else {
+      trimmedBytes
+    }
   }
 
   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
index e1cc2c4b..9c126c5a 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
@@ -158,4 +158,46 @@ class CodeTest {
 
     println(deployment.toBytes().toHexString())
   }
+
+  @Test
+  fun testGenerateCode() {
+    val code = Code.generate(10)
+    assertEquals(10, code.toBytes().size())
+  }
+
+  @Test
+  fun testGenerateCode100() {
+    val code100 = Code.generate(100)
+    assertEquals(100, code100.toBytes().size())
+  }
+
+  @Test
+  fun testGenerateCode1000() {
+    val code1000 = Code.generate(1000)
+    assertEquals(1000, code1000.toBytes().size())
+  }
+
+  @Test
+  fun testCodeGenerate1() {
+    val code = Code.generate(1)
+    assertEquals(1, code.toBytes().size())
+  }
+
+  @Test
+  fun testCodeGenerateMultipleOf34() {
+    val code = Code.generate(34 * 5)
+    assertEquals(34 * 5, code.toBytes().size())
+  }
+
+  @Test
+  fun testCodeGenerateMultipleOf34Plus2() {
+    val code = Code.generate(34 * 5 + 2)
+    assertEquals(34 * 5 + 2, code.toBytes().size())
+  }
+
+  @Test
+  fun testCodeGenerateMultipleOf34Plus1() {
+    val code = Code.generate(34 * 5 + 1)
+    assertEquals(34 * 5 + 1, code.toBytes().size())
+  }
 }
diff --git a/evm-dsl/src/test/kotlin/org/apache/tuweni/evmdsl/DeploymentTest.kt 
b/evm-dsl/src/test/kotlin/org/apache/tuweni/evmdsl/DeploymentTest.kt
new file mode 100644
index 00000000..6839e66a
--- /dev/null
+++ b/evm-dsl/src/test/kotlin/org/apache/tuweni/evmdsl/DeploymentTest.kt
@@ -0,0 +1,30 @@
+/*
+ * 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 DeploymentTest {
+
+  @Test
+  fun testDeploymentCreation() {
+    val deployment = 
Deployment(Code.read(Bytes.fromHexString("0x600035f660115760006000526001601ff35b60016000526001601ff3")))
+    
assertEquals(Bytes.fromHexString("0x7f600035f660115760006000526001601ff35b60016000526001601ff300000000600052601c6000f3"),
 deployment.toBytes())
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@tuweni.apache.org
For additional commands, e-mail: commits-h...@tuweni.apache.org

Reply via email to