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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]