Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public GsonAbiParser() {
this.gson = new GsonBuilder()
.registerTypeAdapterFactory(new AbiTypeAdapterFactory())
.create();

}

public @NonNull AbiModel parse(@NonNull Reader abiReader) throws AbiParserException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AbiEntry> {

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AbiEntry> entries = new ArrayList<>();
array.forEach(jsonElement -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AbiParameter> {

Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
public <T> TypeAdapter<T> create(@NonNull final Gson gson, @NonNull final TypeToken<T> type) {
Objects.requireNonNull(type, "type must not be null");
if (type.getRawType().equals(AbiModel.class)) {
return (TypeAdapter<T>) new AbiModelTypeAdapter(gson);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,30 @@ public abstract class BasicTypeAdapter<T> extends TypeAdapter<T> {

private final TypeAdapter<JsonArray> 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> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
protected <T> T fromJson(@NonNull final JsonElement json, @NonNull final Class<T> classOfT) throws JsonSyntaxException {
return gson.fromJson(json, classOfT);
}


@NonNull
protected Gson getGson() {
return gson;
}

@NonNull
protected TypeAdapter<JsonObject> getObjectTypeAdapter() {
return objectTypeAdapter;
}
Expand All @@ -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");
Expand All @@ -69,6 +69,7 @@ protected JsonArray getArray(@NonNull final JsonObject jsonObject, @NonNull fina
return new JsonArray();
}

@NonNull
protected Optional<String> getStringValue(@NonNull final JsonObject jsonObject, @NonNull final String key) {
Objects.requireNonNull(jsonObject, "jsonObject");
Objects.requireNonNull(key, "key");
Expand All @@ -82,6 +83,7 @@ protected Optional<String> getStringValue(@NonNull final JsonObject jsonObject,
return Optional.empty();
}

@NonNull
protected Optional<Boolean> getBooleanValue(@NonNull final JsonObject jsonObject, @NonNull final String key) {
Objects.requireNonNull(jsonObject, "jsonObject");
Objects.requireNonNull(key, "key");
Expand Down
9 changes: 9 additions & 0 deletions abi-parser/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@
<artifactId>jspecify</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.KeccakUtil;
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<AbiParameter> inputs, boolean anonymous) implements AbiEntry{

Expand All @@ -16,4 +18,32 @@ public record AbiEvent(@NonNull String name, @NonNull List<AbiParameter> inputs,
public AbiEntryType type() {
return AbiEntryType.EVENT;
}

@NonNull
public List<AbiParameter> getIndexedInputParameters() {
return inputs.stream().filter(AbiParameter::indexed).toList();
}

@NonNull
public List<AbiParameter> getNonIndexedInputParameters() {
return inputs.stream().filter(parameter -> !parameter.indexed()).toList();
}

@NonNull
private String createEventSignature() {
final List<String> canonicalParameterTypes = inputs.stream().map(AbiParameter::getCanonicalType).toList();
return name + "(" + String.join(",", canonicalParameterTypes) + ")";
}

@NonNull
private byte[] createEventSignatureHash() {
final String eventSignature = createEventSignature();
return KeccakUtil.keccak256(eventSignature.getBytes(StandardCharsets.UTF_8));
}

@NonNull
public String createEventSignatureHashAsHex() {
final byte[] eventSignatureHash = createEventSignatureHash();
return "0x" + HexConverter.bytesToHex(eventSignatureHash);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,24 @@ public record AbiModel(@NonNull List<AbiEntry> entries) {
Objects.requireNonNull(entries, "entries");
}

@NonNull
public List<AbiFunction> getFunctions() {
return getEntriesOfType(AbiFunction.class);
}

@NonNull
public List<AbiEvent> getEvents() {
return getEntriesOfType(AbiEvent.class);
}

@NonNull
public List<AbiError> getErrors() {
return getEntriesOfType(AbiError.class);
}

private <T extends AbiEntry> List<T> getEntriesOfType(Class<T> type) {
@NonNull
private <T extends AbiEntry> List<T> getEntriesOfType(@NonNull final Class<T> type) {
Objects.requireNonNull(type, "type must not be null");
return entries.stream()
.filter(type::isInstance)
.map(type::cast)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ public record AbiParameter(@NonNull String name, @NonNull AbiParameterType type,
}
}

public String getCanonicalType() {
return type.getCanonicalType();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public enum AbiParameterType {
BYTE32,
BOOL,
UINT256,
UINT,
TUPLE;

public static AbiParameterType of(final String name) {
Expand All @@ -15,7 +16,39 @@ public static AbiParameterType of(final String name) {
case "bytes32" -> BYTE32;
case "bool" -> BOOL;
case "uint256" -> UINT256;
case "uint" -> UINT;
case "tuple" -> TUPLE;
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";
case UINT -> "uint256";
};
}

public boolean isDynamic() {
return switch (this) {
case STRING, BYTE32 -> true;
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);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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 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;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
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) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i+1), 16));
}
return data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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(@NonNull final byte[] data) {
final Keccak.DigestKeccak kecc = new Keccak.Digest256();
kecc.update(data);
return kecc.digest();
}
}
2 changes: 2 additions & 0 deletions abi-parser/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -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 org.bouncycastle.provider;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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 java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class AbiEventTests {

@Test
void testEventSignatureCreation() {
// Given
final List<AbiParameter> inputs = List.of(
new AbiParameter("identifier_", AbiParameterType.STRING, List.of(), false)
);
final AbiEvent event = new AbiEvent("HashAdded", inputs, false);


// When
String hex = event.createEventSignatureHashAsHex();

// Then
Assertions.assertEquals("0xe0c9f5e6f5abddac86dac0e02afc9f3fda7b7fc6d9454a13c51fcb28621e1e5f", hex);
}

@Test
void testEventSignatureCreation2() {
// Given
final List<AbiParameter> inputs = List.of(
new AbiParameter("count", AbiParameterType.UINT, List.of(), false)
);
final AbiEvent event = new AbiEvent("MissingVerificationCountUpdated", inputs, false);


// When
String hex = event.createEventSignatureHashAsHex();

// Then
Assertions.assertEquals("0x271219bdbb9b91472a5df68ef7a9d3f8de02f3c27b93a35306f888acf081ea60", hex);
}

}
Loading