From b8c4f5d08dae604f899fdfac4a1156c8fefb591e Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Fri, 21 Feb 2025 18:43:22 -0500 Subject: [PATCH 1/8] Create Event Signature --- abi-parser/pom.xml | 4 +++ .../smartcontract/abi/model/AbiEvent.java | 25 ++++++++++++++++++- .../smartcontract/abi/model/AbiParameter.java | 4 +++ .../abi/model/AbiParameterType.java | 18 +++++++++++++ .../smartcontract/abi/util/HexConverter.java | 23 +++++++++++++++++ .../smartcontract/abi/util/KeccakSupport.java | 25 +++++++++++++++++++ abi-parser/src/main/java/module-info.java | 2 ++ pom.xml | 6 +++++ 8 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java create mode 100644 abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakSupport.java diff --git a/abi-parser/pom.xml b/abi-parser/pom.xml index b75bef4..8f32d6a 100644 --- a/abi-parser/pom.xml +++ b/abi-parser/pom.xml @@ -23,6 +23,10 @@ jspecify compile + + com.github.aelstad + keccakj + diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java index a0d793a..48e0882 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java @@ -1,9 +1,11 @@ package com.openelements.hiero.smartcontract.abi.model; +import com.openelements.hiero.smartcontract.abi.util.HexConverter; +import com.openelements.hiero.smartcontract.abi.util.KeccakSupport; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; public record AbiEvent(@NonNull String name, @NonNull List inputs, boolean anonymous) implements AbiEntry{ @@ -16,4 +18,25 @@ public record AbiEvent(@NonNull String name, @NonNull List inputs, public AbiEntryType type() { return AbiEntryType.EVENT; } + + @NonNull + public String createEventSignature() { + if(!anonymous) { + throw new IllegalStateException("Cannot create anonymous topic for named event"); + } + final List canonicalParameterTypes = inputs.stream().map(AbiParameter::getCanonicalType).toList(); + return name + "(" + String.join(",", canonicalParameterTypes) + ")"; + } + + @NonNull + public byte[] createEventSignatureHash() { + final String eventSignature = createEventSignature(); + return KeccakSupport.keccak256(eventSignature.getBytes(StandardCharsets.UTF_8)); + } + + @NonNull + public String createEventSignatureHashAsHex() { + final byte[] eventSignatureHash = createEventSignatureHash(); + return HexConverter.bytesToHex(eventSignatureHash); + } } diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameter.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameter.java index 3323a21..4ff104c 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameter.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameter.java @@ -18,4 +18,8 @@ public record AbiParameter(@NonNull String name, @NonNull AbiParameterType type, } } + public String getCanonicalType() { + return type.getCanonicalType(); + } + } diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java index 8e2dbb1..4d0b39f 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java @@ -18,4 +18,22 @@ public static AbiParameterType of(final String name) { default -> throw new IllegalArgumentException("Unknown value type: " + name); }; } + + public String getCanonicalType() { + return switch (this) { + case ADDRESS -> "address"; + case STRING -> "string"; + case BYTE32 -> "bytes32"; + case BOOL -> "bool"; + case UINT256 -> "uint256"; + case TUPLE -> "tuple"; + }; + } + + public boolean isDynamic() { + return switch (this) { + case STRING, BYTE32 -> true; + default -> false; + }; + } } diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java new file mode 100644 index 0000000..c867d85 --- /dev/null +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java @@ -0,0 +1,23 @@ +package com.openelements.hiero.smartcontract.abi.util; + +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import org.jspecify.annotations.NonNull; + +public class HexConverter { + + private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); + + @NonNull + public static String bytesToHex(@NonNull final byte[] bytes) { + //see https://stackoverflow.com/questions/9655181/java-convert-a-byte-array-to-a-hex-string + Objects.requireNonNull(bytes, "bytes"); + final byte[] hexChars = new byte[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars, StandardCharsets.UTF_8); + } +} diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakSupport.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakSupport.java new file mode 100644 index 0000000..7c03526 --- /dev/null +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakSupport.java @@ -0,0 +1,25 @@ +package com.openelements.hiero.smartcontract.abi.util; + +import com.github.aelstad.keccakj.provider.Constants; +import com.github.aelstad.keccakj.provider.KeccakjProvider; +import java.security.MessageDigest; +import java.security.Security; + +public class KeccakSupport { + + public synchronized static MessageDigest getMessageDigest() { + if(Security.getProvider(Constants.PROVIDER) == null) { + Security.addProvider(new KeccakjProvider()); + } + try { + return MessageDigest.getInstance(Constants.SHA3_256, Constants.PROVIDER); + } catch (Exception e) { + throw new RuntimeException("Error in Keccak provisioning", e); + } + } + + public static byte[] keccak256(byte[] data) { + MessageDigest md = getMessageDigest(); + return md.digest(data); + } +} diff --git a/abi-parser/src/main/java/module-info.java b/abi-parser/src/main/java/module-info.java index 84eab7f..8232006 100644 --- a/abi-parser/src/main/java/module-info.java +++ b/abi-parser/src/main/java/module-info.java @@ -1,5 +1,7 @@ module com.openelements.hiero.smartcontract.abi { exports com.openelements.hiero.smartcontract.abi; exports com.openelements.hiero.smartcontract.abi.model; + exports com.openelements.hiero.smartcontract.abi.util; requires org.jspecify; + requires keccakj; } \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3f53737..f137bec 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,7 @@ 2.8.0 1.9.0 2.17.1 + 1.1.0 @@ -191,6 +192,11 @@ gson ${gson.version} + + com.github.aelstad + keccakj + ${keccakj.version} + From e74b2e8bcac6c0d389b914673b9f5d053b84130b Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Fri, 21 Feb 2025 19:48:58 -0500 Subject: [PATCH 2/8] Create Event Signature --- abi-parser/pom.xml | 9 +++- .../smartcontract/abi/model/AbiEvent.java | 11 ++-- .../abi/model/AbiParameterType.java | 3 ++ .../smartcontract/abi/util/HexConverter.java | 2 +- .../smartcontract/abi/util/KeccakSupport.java | 25 --------- abi-parser/src/main/java/module-info.java | 2 +- .../smartcontract/abi/test/AbiEventTests.java | 52 +++++++++++++++++++ abi-parser/src/test/java/module-info.java | 5 ++ pom.xml | 6 +++ 9 files changed, 80 insertions(+), 35 deletions(-) delete mode 100644 abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakSupport.java create mode 100644 abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/AbiEventTests.java create mode 100644 abi-parser/src/test/java/module-info.java diff --git a/abi-parser/pom.xml b/abi-parser/pom.xml index 8f32d6a..6ed2c3b 100644 --- a/abi-parser/pom.xml +++ b/abi-parser/pom.xml @@ -24,8 +24,13 @@ compile - com.github.aelstad - keccakj + org.bouncycastle + bcprov-jdk15to18 + + + org.junit.jupiter + junit-jupiter + test diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java index 48e0882..46ea1e2 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java @@ -1,10 +1,10 @@ package com.openelements.hiero.smartcontract.abi.model; import com.openelements.hiero.smartcontract.abi.util.HexConverter; -import com.openelements.hiero.smartcontract.abi.util.KeccakSupport; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; +import org.bouncycastle.jcajce.provider.digest.Keccak; import org.jspecify.annotations.NonNull; public record AbiEvent(@NonNull String name, @NonNull List inputs, boolean anonymous) implements AbiEntry{ @@ -21,9 +21,6 @@ public AbiEntryType type() { @NonNull public String createEventSignature() { - if(!anonymous) { - throw new IllegalStateException("Cannot create anonymous topic for named event"); - } final List canonicalParameterTypes = inputs.stream().map(AbiParameter::getCanonicalType).toList(); return name + "(" + String.join(",", canonicalParameterTypes) + ")"; } @@ -31,12 +28,14 @@ public String createEventSignature() { @NonNull public byte[] createEventSignatureHash() { final String eventSignature = createEventSignature(); - return KeccakSupport.keccak256(eventSignature.getBytes(StandardCharsets.UTF_8)); + Keccak.DigestKeccak kecc = new Keccak.Digest256(); + kecc.update(eventSignature.getBytes(StandardCharsets.UTF_8)); + return kecc.digest(); } @NonNull public String createEventSignatureHashAsHex() { final byte[] eventSignatureHash = createEventSignatureHash(); - return HexConverter.bytesToHex(eventSignatureHash); + return "0x" + HexConverter.bytesToHex(eventSignatureHash); } } diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java index 4d0b39f..84016d4 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java @@ -6,6 +6,7 @@ public enum AbiParameterType { BYTE32, BOOL, UINT256, + UINT, TUPLE; public static AbiParameterType of(final String name) { @@ -15,6 +16,7 @@ public static AbiParameterType of(final String name) { case "bytes32" -> BYTE32; case "bool" -> BOOL; case "uint256" -> UINT256; + case "uint" -> UINT; default -> throw new IllegalArgumentException("Unknown value type: " + name); }; } @@ -27,6 +29,7 @@ public String getCanonicalType() { case BOOL -> "bool"; case UINT256 -> "uint256"; case TUPLE -> "tuple"; + case UINT -> "uint256"; }; } diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java index c867d85..09b549e 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java @@ -18,6 +18,6 @@ public static String bytesToHex(@NonNull final byte[] bytes) { hexChars[j * 2] = HEX_ARRAY[v >>> 4]; hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; } - return new String(hexChars, StandardCharsets.UTF_8); + return new String(hexChars, StandardCharsets.UTF_8).toLowerCase(); } } diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakSupport.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakSupport.java deleted file mode 100644 index 7c03526..0000000 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakSupport.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.openelements.hiero.smartcontract.abi.util; - -import com.github.aelstad.keccakj.provider.Constants; -import com.github.aelstad.keccakj.provider.KeccakjProvider; -import java.security.MessageDigest; -import java.security.Security; - -public class KeccakSupport { - - public synchronized static MessageDigest getMessageDigest() { - if(Security.getProvider(Constants.PROVIDER) == null) { - Security.addProvider(new KeccakjProvider()); - } - try { - return MessageDigest.getInstance(Constants.SHA3_256, Constants.PROVIDER); - } catch (Exception e) { - throw new RuntimeException("Error in Keccak provisioning", e); - } - } - - public static byte[] keccak256(byte[] data) { - MessageDigest md = getMessageDigest(); - return md.digest(data); - } -} diff --git a/abi-parser/src/main/java/module-info.java b/abi-parser/src/main/java/module-info.java index 8232006..9116606 100644 --- a/abi-parser/src/main/java/module-info.java +++ b/abi-parser/src/main/java/module-info.java @@ -3,5 +3,5 @@ exports com.openelements.hiero.smartcontract.abi.model; exports com.openelements.hiero.smartcontract.abi.util; requires org.jspecify; - requires keccakj; + requires org.bouncycastle.provider; } \ No newline at end of file diff --git a/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/AbiEventTests.java b/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/AbiEventTests.java new file mode 100644 index 0000000..959ea79 --- /dev/null +++ b/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/AbiEventTests.java @@ -0,0 +1,52 @@ +package com.openelements.hiero.smartcontract.abi.test; + +import com.openelements.hiero.smartcontract.abi.model.AbiEvent; +import com.openelements.hiero.smartcontract.abi.model.AbiParameter; +import com.openelements.hiero.smartcontract.abi.model.AbiParameterType; +import com.openelements.hiero.smartcontract.abi.util.HexConverter; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import org.bouncycastle.jcajce.provider.digest.Keccak; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class AbiEventTests { + + @Test + void testEventSignatureCreation() { + // Given + final List inputs = List.of( + new AbiParameter("identifier_", AbiParameterType.STRING, List.of(), false) + ); + final AbiEvent event = new AbiEvent("HashAdded", inputs, false); + + + // When + String signature = event.createEventSignature(); + String hashAsHex = event.createEventSignatureHashAsHex(); + + // Then + Assertions.assertEquals("HashAdded(string)", signature); + Assertions.assertEquals("0xe0c9f5e6f5abddac86dac0e02afc9f3fda7b7fc6d9454a13c51fcb28621e1e5f", hashAsHex); + } + + @Test + void testEventSignatureCreation2() { + // Given + final List inputs = List.of( + new AbiParameter("count", AbiParameterType.UINT, List.of(), false) + ); + final AbiEvent event = new AbiEvent("MissingVerificationCountUpdated", inputs, false); + + + // When + String signature = event.createEventSignature(); + String hashAsHex = event.createEventSignatureHashAsHex(); + + // Then + Assertions.assertEquals("MissingVerificationCountUpdated(uint256)", signature); + Assertions.assertEquals("0x271219bdbb9b91472a5df68ef7a9d3f8de02f3c27b93a35306f888acf081ea60", hashAsHex); + } + +} diff --git a/abi-parser/src/test/java/module-info.java b/abi-parser/src/test/java/module-info.java new file mode 100644 index 0000000..8c7bcb8 --- /dev/null +++ b/abi-parser/src/test/java/module-info.java @@ -0,0 +1,5 @@ +open module com.openelements.hiero.smartcontract.abi.test { + requires com.openelements.hiero.smartcontract.abi; + requires org.junit.jupiter.api; + requires org.bouncycastle.provider; +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index f137bec..d2a0bb2 100644 --- a/pom.xml +++ b/pom.xml @@ -66,6 +66,7 @@ 1.9.0 2.17.1 1.1.0 + 1.76 @@ -197,6 +198,11 @@ keccakj ${keccakj.version} + + org.bouncycastle + bcprov-jdk15to18 + ${bouncycastle.version} + From 10cbb326a9fd41d487f146a22d97cc63cb0ac6f4 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Fri, 21 Feb 2025 20:00:09 -0500 Subject: [PATCH 3/8] Create Event Signature --- .../smartcontract/abi/model/AbiParameterType.java | 11 +++++++++++ .../hiero/smartcontract/abi/test/AbiEventTests.java | 4 ---- pom.xml | 5 ----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java index 84016d4..2e52e42 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java @@ -39,4 +39,15 @@ public boolean isDynamic() { default -> false; }; } + + public int getFixedSize() { + return switch (this) { + case ADDRESS -> 20; + case BOOL -> 1; + case UINT256 -> 32; + case UINT -> 32; + case BYTE32 -> 32; + default -> throw new IllegalStateException("No fixed size for type: " + this); + }; + } } diff --git a/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/AbiEventTests.java b/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/AbiEventTests.java index 959ea79..5623e73 100644 --- a/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/AbiEventTests.java +++ b/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/AbiEventTests.java @@ -3,11 +3,7 @@ import com.openelements.hiero.smartcontract.abi.model.AbiEvent; import com.openelements.hiero.smartcontract.abi.model.AbiParameter; import com.openelements.hiero.smartcontract.abi.model.AbiParameterType; -import com.openelements.hiero.smartcontract.abi.util.HexConverter; -import java.nio.charset.StandardCharsets; -import java.util.Base64; import java.util.List; -import org.bouncycastle.jcajce.provider.digest.Keccak; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/pom.xml b/pom.xml index d2a0bb2..5b78d52 100644 --- a/pom.xml +++ b/pom.xml @@ -193,11 +193,6 @@ gson ${gson.version} - - com.github.aelstad - keccakj - ${keccakj.version} - org.bouncycastle bcprov-jdk15to18 From 7b2752304766bba24de14094debf7656a81e5047 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Fri, 21 Feb 2025 20:03:06 -0500 Subject: [PATCH 4/8] Create Event Signature --- .../hiero/smartcontract/abi/model/AbiEvent.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java index 46ea1e2..c1a3c43 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java @@ -19,7 +19,16 @@ public AbiEntryType type() { return AbiEntryType.EVENT; } - @NonNull + public List getIndexedInputParameters() { + return inputs.stream().filter(AbiParameter::indexed).toList(); + } + + public List getNonIndexedInputParameters() { + return inputs.stream().filter(parameter -> !parameter.indexed()).toList(); + } + + + @NonNull public String createEventSignature() { final List canonicalParameterTypes = inputs.stream().map(AbiParameter::getCanonicalType).toList(); return name + "(" + String.join(",", canonicalParameterTypes) + ")"; From 23baea552fdd0795f4de46d1c407b8b5e78a53e5 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Fri, 21 Feb 2025 20:04:55 -0500 Subject: [PATCH 5/8] Create Event Signature --- .../hiero/smartcontract/abi/model/AbiEvent.java | 12 +++++------- .../hiero/smartcontract/abi/util/KeccakUtil.java | 12 ++++++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakUtil.java diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java index c1a3c43..bbc2231 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java @@ -1,6 +1,7 @@ package com.openelements.hiero.smartcontract.abi.model; import com.openelements.hiero.smartcontract.abi.util.HexConverter; +import com.openelements.hiero.smartcontract.abi.util.KeccakUtil; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; @@ -27,19 +28,16 @@ public List getNonIndexedInputParameters() { return inputs.stream().filter(parameter -> !parameter.indexed()).toList(); } - - @NonNull - public String createEventSignature() { + @NonNull + private String createEventSignature() { final List canonicalParameterTypes = inputs.stream().map(AbiParameter::getCanonicalType).toList(); return name + "(" + String.join(",", canonicalParameterTypes) + ")"; } @NonNull - public byte[] createEventSignatureHash() { + private byte[] createEventSignatureHash() { final String eventSignature = createEventSignature(); - Keccak.DigestKeccak kecc = new Keccak.Digest256(); - kecc.update(eventSignature.getBytes(StandardCharsets.UTF_8)); - return kecc.digest(); + return KeccakUtil.keccak256(eventSignature.getBytes(StandardCharsets.UTF_8)); } @NonNull diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakUtil.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakUtil.java new file mode 100644 index 0000000..7def4a5 --- /dev/null +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakUtil.java @@ -0,0 +1,12 @@ +package com.openelements.hiero.smartcontract.abi.util; + +import org.bouncycastle.jcajce.provider.digest.Keccak; + +public class KeccakUtil { + + public static byte[] keccak256(final byte[] data) { + Keccak.DigestKeccak kecc = new Keccak.Digest256(); + kecc.update(data); + return kecc.digest(); + } +} From 945fe113bd8397e0fdbed0e31cb6f0621b04a29a Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Fri, 21 Feb 2025 20:07:35 -0500 Subject: [PATCH 6/8] Create Event Signature --- .../hiero/smartcontract/abi/test/AbiEventTests.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/AbiEventTests.java b/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/AbiEventTests.java index 5623e73..4db2c54 100644 --- a/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/AbiEventTests.java +++ b/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/AbiEventTests.java @@ -19,12 +19,10 @@ void testEventSignatureCreation() { // When - String signature = event.createEventSignature(); - String hashAsHex = event.createEventSignatureHashAsHex(); + String hex = event.createEventSignatureHashAsHex(); // Then - Assertions.assertEquals("HashAdded(string)", signature); - Assertions.assertEquals("0xe0c9f5e6f5abddac86dac0e02afc9f3fda7b7fc6d9454a13c51fcb28621e1e5f", hashAsHex); + Assertions.assertEquals("0xe0c9f5e6f5abddac86dac0e02afc9f3fda7b7fc6d9454a13c51fcb28621e1e5f", hex); } @Test @@ -37,12 +35,10 @@ void testEventSignatureCreation2() { // When - String signature = event.createEventSignature(); - String hashAsHex = event.createEventSignatureHashAsHex(); + String hex = event.createEventSignatureHashAsHex(); // Then - Assertions.assertEquals("MissingVerificationCountUpdated(uint256)", signature); - Assertions.assertEquals("0x271219bdbb9b91472a5df68ef7a9d3f8de02f3c27b93a35306f888acf081ea60", hashAsHex); + Assertions.assertEquals("0x271219bdbb9b91472a5df68ef7a9d3f8de02f3c27b93a35306f888acf081ea60", hex); } } From 3d8112a2449cbddd6746ff6936135c50451ea4a9 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Sun, 23 Feb 2025 10:18:05 -0700 Subject: [PATCH 7/8] small additions --- .../hiero/smartcontract/abi/model/AbiEvent.java | 3 ++- .../smartcontract/abi/model/AbiParameterType.java | 1 + .../hiero/smartcontract/abi/util/HexConverter.java | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java index bbc2231..38a90c4 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiEvent.java @@ -5,7 +5,6 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; -import org.bouncycastle.jcajce.provider.digest.Keccak; import org.jspecify.annotations.NonNull; public record AbiEvent(@NonNull String name, @NonNull List inputs, boolean anonymous) implements AbiEntry{ @@ -20,10 +19,12 @@ public AbiEntryType type() { return AbiEntryType.EVENT; } + @NonNull public List getIndexedInputParameters() { return inputs.stream().filter(AbiParameter::indexed).toList(); } + @NonNull public List getNonIndexedInputParameters() { return inputs.stream().filter(parameter -> !parameter.indexed()).toList(); } diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java index 2e52e42..2215d8d 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiParameterType.java @@ -17,6 +17,7 @@ public static AbiParameterType of(final String name) { case "bool" -> BOOL; case "uint256" -> UINT256; case "uint" -> UINT; + case "tuple" -> TUPLE; default -> throw new IllegalArgumentException("Unknown value type: " + name); }; } diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java index 09b549e..e82b41b 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java @@ -20,4 +20,14 @@ public static String bytesToHex(@NonNull final byte[] bytes) { } return new String(hexChars, StandardCharsets.UTF_8).toLowerCase(); } + + public static byte[] hexToBytes(@NonNull final String hex) { + int len = hex.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + + Character.digit(hex.charAt(i+1), 16)); + } + return data; + } } From ef9c4264c7007ea557ce2be198ea8b1664d3b984 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Sun, 23 Feb 2025 15:27:56 -0700 Subject: [PATCH 8/8] small additions --- .../abi/simple/GsonAbiParser.java | 1 - .../implementation/AbiEntryTypeAdapter.java | 3 +- .../implementation/AbiModelTypeAdapter.java | 2 +- .../AbiParameterTypeAdapter.java | 3 +- .../implementation/AbiTypeAdapterFactory.java | 5 ++- .../implementation/BasicTypeAdapter.java | 16 ++++++---- .../smartcontract/abi/model/AbiFunction.java | 1 - .../smartcontract/abi/model/AbiModel.java | 7 +++- .../smartcontract/abi/util/HexConverter.java | 4 ++- .../smartcontract/abi/util/KeccakUtil.java | 5 +-- .../abi/test/HexConverterTests.java | 32 +++++++++++++++++++ abi-parser/src/test/java/module-info.java | 2 +- pom.xml | 5 +-- 13 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/HexConverterTests.java diff --git a/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/GsonAbiParser.java b/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/GsonAbiParser.java index b07d4fd..e042e5e 100644 --- a/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/GsonAbiParser.java +++ b/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/GsonAbiParser.java @@ -18,7 +18,6 @@ public GsonAbiParser() { this.gson = new GsonBuilder() .registerTypeAdapterFactory(new AbiTypeAdapterFactory()) .create(); - } public @NonNull AbiModel parse(@NonNull Reader abiReader) throws AbiParserException { diff --git a/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiEntryTypeAdapter.java b/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiEntryTypeAdapter.java index 8196991..4144d6f 100644 --- a/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiEntryTypeAdapter.java +++ b/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiEntryTypeAdapter.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.jspecify.annotations.NonNull; public class AbiEntryTypeAdapter extends BasicTypeAdapter { @@ -20,7 +21,7 @@ public AbiEntryTypeAdapter(Gson gson) { } @Override - public AbiEntry read(JsonReader in) throws IOException { + public AbiEntry read(@NonNull final JsonReader in) throws IOException { final JsonObject abiEntryObject = readObject(in); final AbiEntryType type = getStringValue(abiEntryObject, "type") .map(AbiEntryType::of) diff --git a/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiModelTypeAdapter.java b/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiModelTypeAdapter.java index 5c8811f..28da97b 100644 --- a/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiModelTypeAdapter.java +++ b/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiModelTypeAdapter.java @@ -18,7 +18,7 @@ public AbiModelTypeAdapter(@NonNull Gson gson) { } @Override - public AbiModel read(JsonReader in) throws IOException { + public AbiModel read(@NonNull final JsonReader in) throws IOException { JsonArray array = readArray(in); final List entries = new ArrayList<>(); array.forEach(jsonElement -> { diff --git a/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiParameterTypeAdapter.java b/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiParameterTypeAdapter.java index dcd73c4..5028fd3 100644 --- a/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiParameterTypeAdapter.java +++ b/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiParameterTypeAdapter.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.jspecify.annotations.NonNull; public class AbiParameterTypeAdapter extends BasicTypeAdapter { @@ -17,7 +18,7 @@ public AbiParameterTypeAdapter(Gson gson) { } @Override - public AbiParameter read(JsonReader in) throws IOException { + public AbiParameter read(@NonNull final JsonReader in) throws IOException { final JsonObject parameterObject = readObject(in); final String name = getStringValue(parameterObject, "name") diff --git a/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiTypeAdapterFactory.java b/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiTypeAdapterFactory.java index 2b029c6..f1b600a 100644 --- a/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiTypeAdapterFactory.java +++ b/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/AbiTypeAdapterFactory.java @@ -7,11 +7,14 @@ import com.openelements.hiero.smartcontract.abi.model.AbiEntry; import com.openelements.hiero.smartcontract.abi.model.AbiModel; import com.openelements.hiero.smartcontract.abi.model.AbiParameter; +import java.util.Objects; +import org.jspecify.annotations.NonNull; public class AbiTypeAdapterFactory implements TypeAdapterFactory { @Override - public TypeAdapter create(Gson gson, TypeToken type) { + public TypeAdapter create(@NonNull final Gson gson, @NonNull final TypeToken type) { + Objects.requireNonNull(type, "type must not be null"); if (type.getRawType().equals(AbiModel.class)) { return (TypeAdapter) new AbiModelTypeAdapter(gson); } diff --git a/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/BasicTypeAdapter.java b/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/BasicTypeAdapter.java index b25273e..6c87c30 100644 --- a/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/BasicTypeAdapter.java +++ b/abi-parser-simple/src/main/java/com/openelements/hiero/smartcontract/abi/simple/implementation/BasicTypeAdapter.java @@ -21,31 +21,30 @@ public abstract class BasicTypeAdapter extends TypeAdapter { private final TypeAdapter arrayTypeAdapter; - - public BasicTypeAdapter(final Gson gson) { + public BasicTypeAdapter(@NonNull final Gson gson) { this.gson = Objects.requireNonNull(gson, "gson"); objectTypeAdapter = gson.getAdapter(JsonObject.class); arrayTypeAdapter = gson.getAdapter(JsonArray.class); } - protected JsonObject readObject(JsonReader in) throws IOException { + protected JsonObject readObject(@NonNull final JsonReader in) throws IOException { return objectTypeAdapter.read(in); } - protected JsonArray readArray(JsonReader in) throws IOException { + protected JsonArray readArray(@NonNull final JsonReader in) throws IOException { return arrayTypeAdapter.read(in); } - - protected T fromJson(JsonElement json, Class classOfT) throws JsonSyntaxException { + protected T fromJson(@NonNull final JsonElement json, @NonNull final Class classOfT) throws JsonSyntaxException { return gson.fromJson(json, classOfT); } - + @NonNull protected Gson getGson() { return gson; } + @NonNull protected TypeAdapter getObjectTypeAdapter() { return objectTypeAdapter; } @@ -55,6 +54,7 @@ public void write(JsonWriter out, T value) throws IOException { throw new UnsupportedOperationException("Not supported yet."); } + @NonNull protected JsonArray getArray(@NonNull final JsonObject jsonObject, @NonNull final String key) { Objects.requireNonNull(jsonObject, "jsonObject"); Objects.requireNonNull(key, "key"); @@ -69,6 +69,7 @@ protected JsonArray getArray(@NonNull final JsonObject jsonObject, @NonNull fina return new JsonArray(); } + @NonNull protected Optional getStringValue(@NonNull final JsonObject jsonObject, @NonNull final String key) { Objects.requireNonNull(jsonObject, "jsonObject"); Objects.requireNonNull(key, "key"); @@ -82,6 +83,7 @@ protected Optional getStringValue(@NonNull final JsonObject jsonObject, return Optional.empty(); } + @NonNull protected Optional getBooleanValue(@NonNull final JsonObject jsonObject, @NonNull final String key) { Objects.requireNonNull(jsonObject, "jsonObject"); Objects.requireNonNull(key, "key"); diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiFunction.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiFunction.java index 8887f96..1ab1da0 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiFunction.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiFunction.java @@ -36,6 +36,5 @@ public record AbiFunction(@NonNull AbiEntryType type, @Nullable String name, @No Objects.requireNonNull(inputs, "inputs"); Objects.requireNonNull(outputs, "outputs"); Objects.requireNonNull(stateMutability, "stateMutability"); - } } diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiModel.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiModel.java index e027b79..35ea604 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiModel.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/model/AbiModel.java @@ -10,19 +10,24 @@ public record AbiModel(@NonNull List entries) { Objects.requireNonNull(entries, "entries"); } + @NonNull public List getFunctions() { return getEntriesOfType(AbiFunction.class); } + @NonNull public List getEvents() { return getEntriesOfType(AbiEvent.class); } + @NonNull public List getErrors() { return getEntriesOfType(AbiError.class); } - private List getEntriesOfType(Class type) { + @NonNull + private List getEntriesOfType(@NonNull final Class type) { + Objects.requireNonNull(type, "type must not be null"); return entries.stream() .filter(type::isInstance) .map(type::cast) diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java index e82b41b..909b0f6 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/HexConverter.java @@ -11,7 +11,7 @@ public class HexConverter { @NonNull public static String bytesToHex(@NonNull final byte[] bytes) { //see https://stackoverflow.com/questions/9655181/java-convert-a-byte-array-to-a-hex-string - Objects.requireNonNull(bytes, "bytes"); + Objects.requireNonNull(bytes, "bytes must not be null"); final byte[] hexChars = new byte[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; @@ -21,7 +21,9 @@ public static String bytesToHex(@NonNull final byte[] bytes) { return new String(hexChars, StandardCharsets.UTF_8).toLowerCase(); } + @NonNull public static byte[] hexToBytes(@NonNull final String hex) { + Objects.requireNonNull(hex, "hex must not be null"); int len = hex.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { diff --git a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakUtil.java b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakUtil.java index 7def4a5..9a87a53 100644 --- a/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakUtil.java +++ b/abi-parser/src/main/java/com/openelements/hiero/smartcontract/abi/util/KeccakUtil.java @@ -1,11 +1,12 @@ package com.openelements.hiero.smartcontract.abi.util; import org.bouncycastle.jcajce.provider.digest.Keccak; +import org.jspecify.annotations.NonNull; public class KeccakUtil { - public static byte[] keccak256(final byte[] data) { - Keccak.DigestKeccak kecc = new Keccak.Digest256(); + public static byte[] keccak256(@NonNull final byte[] data) { + final Keccak.DigestKeccak kecc = new Keccak.Digest256(); kecc.update(data); return kecc.digest(); } diff --git a/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/HexConverterTests.java b/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/HexConverterTests.java new file mode 100644 index 0000000..107652d --- /dev/null +++ b/abi-parser/src/test/java/com/openelements/hiero/smartcontract/abi/test/HexConverterTests.java @@ -0,0 +1,32 @@ +package com.openelements.hiero.smartcontract.abi.test; + +import com.openelements.hiero.smartcontract.abi.util.HexConverter; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class HexConverterTests { + + @ParameterizedTest + @MethodSource("provideArgumentsForHexToBytesTest") + void hexToBytesTest(String hexInput, byte[] expectedBytes) { + //given + final String hex = "00"; + + //when + final byte[] bytes = HexConverter.hexToBytes(hex); + + //then + Assertions.assertArrayEquals(new byte[]{0x0}, bytes); + } + + private static Stream provideArgumentsForHexToBytesTest() { + return Stream.of( + Arguments.of("00", new byte[]{0x0}), + Arguments.of("01", new byte[]{0x1}) + ); + } +} diff --git a/abi-parser/src/test/java/module-info.java b/abi-parser/src/test/java/module-info.java index 8c7bcb8..27881b2 100644 --- a/abi-parser/src/test/java/module-info.java +++ b/abi-parser/src/test/java/module-info.java @@ -1,5 +1,5 @@ open module com.openelements.hiero.smartcontract.abi.test { requires com.openelements.hiero.smartcontract.abi; - requires org.junit.jupiter.api; requires org.bouncycastle.provider; + requires org.junit.jupiter.params; } \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5b78d52..4da8c19 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,9 @@ 1.1.1 2.10.1 3.6.1.Final + 1.1.0 + 1.76 + 3.3.1 3.13.0 3.3.1 @@ -65,8 +68,6 @@ 2.8.0 1.9.0 2.17.1 - 1.1.0 - 1.76