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