diff --git a/hiero-enterprise-base/pom.xml b/hiero-enterprise-base/pom.xml index 05d62e78..e99b9f94 100644 --- a/hiero-enterprise-base/pom.xml +++ b/hiero-enterprise-base/pom.xml @@ -23,6 +23,14 @@ com.hedera.hashgraph sdk + + com.open-elements.solidity + abi-parser + + + org.web3j + abi + io.github.cdimascio dotenv-java diff --git a/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/ContractEventInstance.java b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/ContractEventInstance.java new file mode 100644 index 00000000..4ef98770 --- /dev/null +++ b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/ContractEventInstance.java @@ -0,0 +1,26 @@ +package com.openelements.hiero.base.data; + +import com.hedera.hashgraph.sdk.ContractId; +import com.openelements.hiero.smartcontract.abi.model.AbiParameterType; +import java.util.List; +import java.util.Objects; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +public record ContractEventInstance(@NonNull ContractId contractId, @Nullable String eventName, + @NonNull List parameters) { + + public ContractEventInstance { + Objects.requireNonNull(contractId, "contractId must be provided"); + Objects.requireNonNull(parameters, "parameters must be provided"); + } + + public record ParameterInstance(@NonNull String name, @NonNull AbiParameterType type, @NonNull byte[] value) { + + public ParameterInstance { + Objects.requireNonNull(name, "name must be provided"); + Objects.requireNonNull(type, "type must be provided"); + Objects.requireNonNull(value, "value must be provided"); + } + } +} diff --git a/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/ContractLog.java b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/ContractLog.java new file mode 100644 index 00000000..3aae4747 --- /dev/null +++ b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/ContractLog.java @@ -0,0 +1,83 @@ +package com.openelements.hiero.base.data; + +import com.hedera.hashgraph.sdk.ContractId; +import com.openelements.hiero.base.solidity.SolidityTools; +import com.openelements.hiero.smartcontract.abi.model.AbiEvent; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Objects; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +/** + * Represents a log entry for a contract. + * + * @param address The hex encoded EVM address of the contract. example: + * {@code 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef} - pattern: + * {@code ^0x[0-9A-Fa-f]{40}$} + * @param bloom The hex encoded bloom filter of the contract log. example: + * {@code 0x549358c4c2e573e02410ef7b5a5ffa5f36dd7398} + * @param contractId Network entity ID in the format of shard.realm.num example: {@code 0.0.1234} + * @param data The hex encoded data of the contract log - example: + * {@code 0x00000000000000000000000000000000000000000000000000000000000000fa} + * @param index The index of the contract log in the chain of logs for an execution + * @param topics A list of hex encoded topics associated with this log event. example: + * {@code List [ "0xf4757a49b326036464bec6fe419a4ae38c8a02ce3e68bf0809674f6aab8ad300" ]} + * @param block_hash The hex encoded block (record file chain) hash. example: + * {@code + * 0x553f9311833391c0a3b2f9ed64540a89f2190a511986cd94889f1c0cf7fa63e898b1c6730f14a61755d1fb4ca05fb073} + * @param blockNumber The block height calculated as the number of record files starting from zero since network + * start. + * @param rootContractId The executed contract that created this contract log. pattern: + * {@code ^\d{1,10}\.\d{1,10}\.\d{1,10}$}. example: {@code 0.0.1234} + * @param timestamp A Unix timestamp in seconds.nanoseconds format. pattern: {@code ^\d{1,10}(\.\d{1,9})?$}. + * example: {@code 1586567700.453054000} + * @param transactionHash A hex encoded transaction hash. example: + * {@code 0x397022d1e5baeb89d0ab66e6bf602640610e6fb7e55d78638db861e2c6339aa9} + * @param transactionIndex The position of the transaction in the block + */ +public record ContractLog(@NonNull String address, @Nullable String bloom, @Nullable ContractId contractId, + @Nullable String data, long index, + @NonNull List topics, @NonNull String block_hash, long blockNumber, + @Nullable ContractId rootContractId, @NonNull String timestamp, + @NonNull String transactionHash, + @Nullable Long transactionIndex) { + + public ContractLog { + Objects.requireNonNull(address, "address must not be null"); + Objects.requireNonNull(topics, "topics must not be null"); + Objects.requireNonNull(block_hash, "block_hash must not be null"); + Objects.requireNonNull(timestamp, "timestamp must not be null"); + Objects.requireNonNull(transactionHash, "transactionHash must not be null"); + } + + public ZonedDateTime getTimestamp() { + String[] parts = timestamp.split("\\."); + long seconds = Long.parseLong(parts[0]); + int nanoseconds = parts.length > 1 ? Integer.parseInt(parts[1]) : 0; + Instant instant = Instant.ofEpochSecond(seconds, nanoseconds); + return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC); + } + + public boolean isEventOfType(final @NonNull AbiEvent event) { + Objects.requireNonNull(event, "event"); + if (event.anonymous()) { + throw new IllegalStateException("Cannot check anonymous event"); + } + if (topics.isEmpty()) { + return false; + } + final String eventHashAsHex = event.createEventSignatureHashAsHex(); + return topics.get(0).equals(eventHashAsHex); + } + + public ContractEventInstance asEventInstance(final @NonNull AbiEvent event) { + Objects.requireNonNull(event, "event must not be null"); + if (!isEventOfType(event)) { + throw new IllegalArgumentException("Event does not match log"); + } + return SolidityTools.asEventInstance(this, event); + } +} diff --git a/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/Order.java b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/Order.java new file mode 100644 index 00000000..00afb41d --- /dev/null +++ b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/Order.java @@ -0,0 +1,6 @@ +package com.openelements.hiero.base.data; + +public enum Order { + ASC, + DESC +} diff --git a/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/mirrornode/ContractRepository.java b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/mirrornode/ContractRepository.java new file mode 100644 index 00000000..3778f622 --- /dev/null +++ b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/mirrornode/ContractRepository.java @@ -0,0 +1,246 @@ +package com.openelements.hiero.base.mirrornode; + +import com.hedera.hashgraph.sdk.ContractId; +import com.openelements.hiero.base.data.ContractLog; +import com.openelements.hiero.base.data.Order; +import com.openelements.hiero.base.data.Page; +import com.openelements.hiero.smartcontract.abi.model.AbiEvent; +import java.time.ZonedDateTime; +import java.util.Objects; +import org.jspecify.annotations.NonNull; + +public interface ContractRepository { + + default Page findLogsByContract(@NonNull String contractId) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContract(ContractId.fromString(contractId)); + } + + default Page findLogsByContract(@NonNull String contractId, @NonNull Order order) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContract(ContractId.fromString(contractId), order); + } + + default Page findLogsByContract(@NonNull String contractId, @NonNull AbiEvent abiEvent) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContract(ContractId.fromString(contractId), abiEvent); + } + + default Page findLogsByContract(@NonNull String contractId, @NonNull AbiEvent abiEvent, + @NonNull Order order) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContract(ContractId.fromString(contractId), abiEvent, order); + } + + default Page findLogsByContract(@NonNull String contractId, int pageLimit) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContract(ContractId.fromString(contractId), pageLimit); + } + + default Page findLogsByContract(@NonNull String contractId, @NonNull Order order, int pageLimit) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContract(ContractId.fromString(contractId), order, pageLimit); + } + + default Page findLogsByContract(@NonNull String contractId, @NonNull AbiEvent abiEvent, + int pageLimit) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContract(ContractId.fromString(contractId), abiEvent, pageLimit); + } + + default Page findLogsByContract(@NonNull String contractId, @NonNull AbiEvent abiEvent, + @NonNull Order order, int pageLimit) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContract(ContractId.fromString(contractId), abiEvent, order, pageLimit); + } + + default Page findLogsByContract(@NonNull ContractId contractId) { + return findLogsByContract(contractId, Order.DESC); + } + + default Page findLogsByContract(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent) { + return findLogsByContract(contractId, abiEvent, Order.DESC); + } + + default Page findLogsByContract(@NonNull ContractId contractId, int pageLimit) { + return findLogsByContract(contractId, Order.DESC, pageLimit); + } + + default Page findLogsByContract(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent, + int pageLimit) { + return findLogsByContract(contractId, abiEvent, Order.DESC, pageLimit); + } + + Page findLogsByContract(@NonNull ContractId contractId, @NonNull Order order); + + Page findLogsByContract(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent, + @NonNull Order order); + + Page findLogsByContract(@NonNull ContractId contractId, @NonNull Order order, int pageLimit); + + Page findLogsByContract(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent, + @NonNull Order order, int pageLimit); + + + default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), timestamp); + } + + default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, @NonNull Order order, + ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), order, timestamp); + } + + default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent, + ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), abiEvent, timestamp); + } + + default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent, + @NonNull Order order, ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), abiEvent, order, timestamp); + } + + default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, int pageLimit, + ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), pageLimit, timestamp); + } + + default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, @NonNull Order order, + int pageLimit, ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), order, pageLimit, timestamp); + } + + default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent, + int pageLimit, ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), abiEvent, pageLimit, timestamp); + } + + default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent, + @NonNull Order order, int pageLimit, ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), abiEvent, order, pageLimit, + timestamp); + } + + default Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, + ZonedDateTime timestamp) { + return findLogsByContractBeforeTimestamp(contractId, Order.DESC, timestamp); + } + + default Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, + @NonNull AbiEvent abiEvent, ZonedDateTime timestamp) { + return findLogsByContractBeforeTimestamp(contractId, abiEvent, Order.DESC, timestamp); + } + + default Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, int pageLimit, + ZonedDateTime timestamp) { + return findLogsByContractBeforeTimestamp(contractId, Order.DESC, pageLimit, timestamp); + } + + default Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, + @NonNull AbiEvent abiEvent, + int pageLimit, ZonedDateTime timestamp) { + return findLogsByContractBeforeTimestamp(contractId, abiEvent, Order.DESC, pageLimit, timestamp); + } + + Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, @NonNull Order order, + ZonedDateTime timestamp); + + Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent, + @NonNull Order order, ZonedDateTime timestamp); + + Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, @NonNull Order order, + int pageLimit, ZonedDateTime timestamp); + + Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent, + @NonNull Order order, int pageLimit, ZonedDateTime timestamp); + + default Page findLogsByContractAfterTimestamp(@NonNull String contractId, ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), timestamp); + } + + default Page findLogsByContractAfterTimestamp(@NonNull String contractId, @NonNull Order order, + ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), order, timestamp); + } + + default Page findLogsByContractAfterTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent, + ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), abiEvent, timestamp); + } + + default Page findLogsByContractAfterTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent, + @NonNull Order order, ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), abiEvent, order, timestamp); + } + + default Page findLogsByContractAfterTimestamp(@NonNull String contractId, int pageLimit, + ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), pageLimit, timestamp); + } + + default Page findLogsByContractAfterTimestamp(@NonNull String contractId, @NonNull Order order, + int pageLimit, ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), order, pageLimit, timestamp); + } + + default Page findLogsByContractAfterTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent, + int pageLimit, ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), abiEvent, pageLimit, timestamp); + } + + default Page findLogsByContractAfterTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent, + @NonNull Order order, int pageLimit, ZonedDateTime timestamp) { + Objects.requireNonNull(contractId, "contractId must be provided"); + return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), abiEvent, order, pageLimit, + timestamp); + } + + default Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, + ZonedDateTime timestamp) { + return findLogsByContractAfterTimestamp(contractId, Order.DESC, timestamp); + } + + default Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, + @NonNull AbiEvent abiEvent, ZonedDateTime timestamp) { + return findLogsByContractAfterTimestamp(contractId, abiEvent, Order.DESC, timestamp); + } + + default Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, int pageLimit, + ZonedDateTime timestamp) { + return findLogsByContractAfterTimestamp(contractId, Order.DESC, pageLimit, timestamp); + } + + default Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, + @NonNull AbiEvent abiEvent, + int pageLimit, ZonedDateTime timestamp) { + return findLogsByContractAfterTimestamp(contractId, abiEvent, Order.DESC, pageLimit, timestamp); + } + + Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, @NonNull Order order, + ZonedDateTime timestamp); + + Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent, + @NonNull Order order, ZonedDateTime timestamp); + + Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, @NonNull Order order, + int pageLimit, ZonedDateTime timestamp); + + Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent, + @NonNull Order order, int pageLimit, ZonedDateTime timestamp); +} diff --git a/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/solidity/SolidityTools.java b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/solidity/SolidityTools.java new file mode 100644 index 00000000..7ea356c0 --- /dev/null +++ b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/solidity/SolidityTools.java @@ -0,0 +1,95 @@ +package com.openelements.hiero.base.solidity; + +import com.openelements.hiero.base.data.ContractEventInstance; +import com.openelements.hiero.base.data.ContractEventInstance.ParameterInstance; +import com.openelements.hiero.base.data.ContractLog; +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.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.jspecify.annotations.NonNull; +import org.web3j.abi.TypeDecoder; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.Bool; +import org.web3j.abi.datatypes.DynamicStruct; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.Utf8String; +import org.web3j.abi.datatypes.generated.Bytes32; +import org.web3j.abi.datatypes.generated.Uint256; + +public class SolidityTools { + + private static Class getMatchingTypeClass( + AbiParameterType abiParameter) { + return switch (abiParameter) { + case AbiParameterType.ADDRESS -> Address.class; + case STRING -> Utf8String.class; + case BYTE32 -> Bytes32.class; + case AbiParameterType.BOOL -> Bool.class; + case UINT256 -> Uint256.class; + case UINT -> Uint256.class; + case TUPLE -> DynamicStruct.class; + default -> throw new IllegalArgumentException("Unknown type: " + abiParameter); + }; + } + + public static ContractEventInstance asEventInstance(final @NonNull ContractLog contractLog, + final @NonNull AbiEvent event) { + Objects.requireNonNull(contractLog, "contractLog must not be null"); + Objects.requireNonNull(event, "event must not be null"); + + if (!event.anonymous()) { + if (!contractLog.isEventOfType(event)) { + throw new IllegalArgumentException("Event does not match log"); + } + } + //see https://github.com/web3/web3.js/blob/bf1691765bd9d4b0f7a4479e915207707d69226d/packages/web3-eth-abi/src/api/logs_api.ts#L74 + //see https://docs.soliditylang.org/en/latest/abi-spec.html#index-0 + int topicIndex = 0; + if (!event.anonymous()) { + topicIndex = 1; + } + + final List indexedParameters = event.getIndexedInputParameters(); + final List nonIndexedParameters = event.getNonIndexedInputParameters(); + final Map mapping = new HashMap<>(); + + for (AbiParameter parameter : indexedParameters) { + mapping.put(parameter, new ParameterInstance(parameter.name(), parameter.type(), + contractLog.topics().get(topicIndex++).getBytes(StandardCharsets.UTF_8))); + } + + final String data = contractLog.data().substring(2); + int offset = 0; + for (AbiParameter parameter : nonIndexedParameters) { + final Class web3jTypeClass = getMatchingTypeClass(parameter.type()); + final Type web3jType = TypeDecoder.decode(contractLog.data(), offset, web3jTypeClass); + final int byteLength = web3jType.bytes32PaddedLength(); + final String substring = data.substring(offset, byteLength * 2); + final byte[] value = HexConverter.hexToBytes(substring); + mapping.put(parameter, new ParameterInstance(parameter.name(), parameter.type(), value)); + offset += byteLength * 2; + } + + final List parameters = new ArrayList<>(); + for (AbiParameter parameter : event.inputs()) { + parameters.add(mapping.get(parameter)); + } + return new ContractEventInstance(contractLog.contractId(), event.name(), + Collections.unmodifiableList(parameters)); + } + + public static T getValue(ParameterInstance parameterInstance) { + final Class web3jTypeClass = getMatchingTypeClass(parameterInstance.type()); + final String dataAsHex = HexConverter.bytesToHex(parameterInstance.value()); + final Type web3jType = TypeDecoder.decode(dataAsHex, web3jTypeClass); + return (T) web3jType.getValue(); + } +} diff --git a/hiero-enterprise-base/src/main/java/module-info.java b/hiero-enterprise-base/src/main/java/module-info.java index 546a85e2..4a617e44 100644 --- a/hiero-enterprise-base/src/main/java/module-info.java +++ b/hiero-enterprise-base/src/main/java/module-info.java @@ -3,6 +3,7 @@ exports com.openelements.hiero.base.protocol; exports com.openelements.hiero.base.mirrornode; exports com.openelements.hiero.base.verification; + exports com.openelements.hiero.base.solidity; exports com.openelements.hiero.base.data; exports com.openelements.hiero.base.config; exports com.openelements.hiero.base.implementation to com.openelements.hiero.base.test; @@ -15,7 +16,10 @@ requires transitive sdk; //Hedera SDK requires org.slf4j; - requires com.google.protobuf; //TODO: We should not have the need to use it requires static org.jspecify; + requires com.google.protobuf; //TODO: We should not have the need to use it requires com.google.auto.service; + requires com.openelements.hiero.smartcontract.abi; + requires abi; + requires org.bouncycastle.provider; } \ No newline at end of file diff --git a/hiero-enterprise-base/src/test/java/com/openelements/hiero/base/test/data/ContractLogTest.java b/hiero-enterprise-base/src/test/java/com/openelements/hiero/base/test/data/ContractLogTest.java new file mode 100644 index 00000000..873544f5 --- /dev/null +++ b/hiero-enterprise-base/src/test/java/com/openelements/hiero/base/test/data/ContractLogTest.java @@ -0,0 +1,83 @@ +package com.openelements.hiero.base.test.data; + +import com.hedera.hashgraph.sdk.ContractId; +import com.openelements.hiero.base.data.ContractEventInstance; +import com.openelements.hiero.base.data.ContractLog; +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 ContractLogTest { + + @Test + void test() { + //given + // from https://testnet.mirrornode.hedera.com/api/v1/contracts/0.0.5615061/results/logs?limit=10&order=asc + final String address = "0x000000000000000000000000000000000055add5"; + final String bloom = "0x00000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000"; + final ContractId contractId = ContractId.fromString("0.0.5615061"); + final String data = "0x0000000000000000000000000000000000000000000000000000000000000001"; + final long index = 1; + final List topics = List.of("0x271219bdbb9b91472a5df68ef7a9d3f8de02f3c27b93a35306f888acf081ea60"); + final String block_hash = "0x3c33e36ebaa60192ff6c714ec15adb3829e8dd24e0f28b607fa88f6052600fb07ab29aeeb828e3faffd85b90960cc679"; + final long blockNumber = 16076698; + final ContractId rootContractId = ContractId.fromString("0.0.5615061"); + final String timestamp = "1740183080.776076000"; + final String transactionHash = "0x254fa8781dc4babe1df5925bc2e3e8aa50d1b57d019189116f93fbdbb5497812"; + final Long transactionIndex = 6L; + final ContractLog contractLog = new ContractLog(address, bloom, contractId, data, index, topics, block_hash, + blockNumber, rootContractId, timestamp, transactionHash, transactionIndex); + final List inputs = List.of( + new AbiParameter("count", AbiParameterType.UINT, List.of(), false) + ); + final AbiEvent event = new AbiEvent("MissingVerificationCountUpdated", inputs, false); + + //then + Assertions.assertTrue(contractLog.isEventOfType(event)); + } + + @Test + void test2() { + //given + // from https://testnet.mirrornode.hedera.com/api/v1/contracts/0.0.5615061/results/logs?limit=10&order=asc + final String address = "0x000000000000000000000000000000000055add5"; + final String bloom = "0x00000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000"; + final ContractId contractId = ContractId.fromString("0.0.5615061"); + final String data = "0x0000000000000000000000000000000000000000000000000000000000000001"; + final long index = 1; + final List topics = List.of("0x271219bdbb9b91472a5df68ef7a9d3f8de02f3c27b93a35306f888acf081ea60"); + final String block_hash = "0x3c33e36ebaa60192ff6c714ec15adb3829e8dd24e0f28b607fa88f6052600fb07ab29aeeb828e3faffd85b90960cc679"; + final long blockNumber = 16076698; + final ContractId rootContractId = ContractId.fromString("0.0.5615061"); + final String timestamp = "1740183080.776076000"; + final String transactionHash = "0x254fa8781dc4babe1df5925bc2e3e8aa50d1b57d019189116f93fbdbb5497812"; + final Long transactionIndex = 6L; + final ContractLog contractLog = new ContractLog(address, bloom, contractId, data, index, topics, block_hash, + blockNumber, rootContractId, timestamp, transactionHash, transactionIndex); + final List inputs = List.of( + new AbiParameter("count", AbiParameterType.UINT, List.of(), false) + ); + final AbiEvent event = new AbiEvent("MissingVerificationCountUpdated", inputs, false); + + //when + final ContractEventInstance eventInstance = contractLog.asEventInstance(event); + + //then + Assertions.assertNotNull(eventInstance); + Assertions.assertEquals(contractId, eventInstance.contractId()); + Assertions.assertEquals("MissingVerificationCountUpdated", eventInstance.eventName()); + Assertions.assertEquals(1, eventInstance.parameters().size()); + Assertions.assertEquals("count", eventInstance.parameters().get(0).name()); + Assertions.assertEquals(AbiParameterType.UINT, eventInstance.parameters().get(0).type()); + Assertions.assertArrayEquals( + new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01}, + eventInstance.parameters().get(0).value()); + } + +} diff --git a/hiero-enterprise-base/src/test/java/com/openelements/hiero/base/test/solidty/SolidityToolsTests.java b/hiero-enterprise-base/src/test/java/com/openelements/hiero/base/test/solidty/SolidityToolsTests.java new file mode 100644 index 00000000..64d91817 --- /dev/null +++ b/hiero-enterprise-base/src/test/java/com/openelements/hiero/base/test/solidty/SolidityToolsTests.java @@ -0,0 +1,48 @@ +package com.openelements.hiero.base.test.solidty; + +import com.hedera.hashgraph.sdk.ContractId; +import com.openelements.hiero.base.data.ContractEventInstance; +import com.openelements.hiero.base.data.ContractLog; +import com.openelements.hiero.base.solidity.SolidityTools; +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.math.BigInteger; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class SolidityToolsTests { + + @Test + void test() { + //given + // from https://testnet.mirrornode.hedera.com/api/v1/contracts/0.0.5615061/results/logs?limit=10&order=asc + final String address = "0x000000000000000000000000000000000055add5"; + final String bloom = "0x00000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000"; + final ContractId contractId = ContractId.fromString("0.0.5615061"); + final String data = "0x0000000000000000000000000000000000000000000000000000000000000001"; + final long index = 1; + final List topics = List.of("0x271219bdbb9b91472a5df68ef7a9d3f8de02f3c27b93a35306f888acf081ea60"); + final String block_hash = "0x3c33e36ebaa60192ff6c714ec15adb3829e8dd24e0f28b607fa88f6052600fb07ab29aeeb828e3faffd85b90960cc679"; + final long blockNumber = 16076698; + final ContractId rootContractId = ContractId.fromString("0.0.5615061"); + final String timestamp = "1740183080.776076000"; + final String transactionHash = "0x254fa8781dc4babe1df5925bc2e3e8aa50d1b57d019189116f93fbdbb5497812"; + final Long transactionIndex = 6L; + final ContractLog contractLog = new ContractLog(address, bloom, contractId, data, index, topics, block_hash, + blockNumber, rootContractId, timestamp, transactionHash, transactionIndex); + final List inputs = List.of( + new AbiParameter("count", AbiParameterType.UINT, List.of(), false) + ); + final AbiEvent event = new AbiEvent("MissingVerificationCountUpdated", inputs, false); + + //when + final ContractEventInstance eventInstance = SolidityTools.asEventInstance(contractLog, event); + final BigInteger value = SolidityTools.getValue(eventInstance.parameters().get(0)); + + //then + Assertions.assertNotNull(eventInstance); + Assertions.assertEquals(BigInteger.ONE, value); + } +} diff --git a/hiero-enterprise-base/src/test/java/module-info.java b/hiero-enterprise-base/src/test/java/module-info.java index c5ac374a..7f9ace69 100644 --- a/hiero-enterprise-base/src/test/java/module-info.java +++ b/hiero-enterprise-base/src/test/java/module-info.java @@ -1,11 +1,12 @@ open module com.openelements.hiero.base.test { requires com.openelements.hiero.base; requires io.github.cdimascio.dotenv.java; - requires static org.jspecify; requires org.junit.jupiter.api; requires org.junit.jupiter.params; requires org.mockito; requires org.slf4j; + requires static org.jspecify; + requires com.openelements.hiero.smartcontract.abi; provides com.openelements.hiero.base.config.NetworkSettingsProvider with com.openelements.hiero.base.test.config.SoloActionNetworkSettingsProvider; } \ No newline at end of file diff --git a/hiero-enterprise-spring/src/test/java/com/openelements/hiero/spring/test/TopicRepositoryTest.java b/hiero-enterprise-spring/src/test/java/com/openelements/hiero/spring/test/TopicRepositoryTest.java index 66417c11..c834a4ed 100644 --- a/hiero-enterprise-spring/src/test/java/com/openelements/hiero/spring/test/TopicRepositoryTest.java +++ b/hiero-enterprise-spring/src/test/java/com/openelements/hiero/spring/test/TopicRepositoryTest.java @@ -8,14 +8,13 @@ import com.openelements.hiero.base.data.TopicMessage; import com.openelements.hiero.base.mirrornode.TopicRepository; import com.openelements.hiero.test.HieroTestUtils; +import java.util.Optional; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import java.util.Optional; - -@SpringBootTest(classes = TestConfig.class) +@SpringBootTest(classes = HieroTestConfig.class) public class TopicRepositoryTest { @Autowired private TopicRepository topicRepository; diff --git a/pom.xml b/pom.xml index 26199ff9..9b1405ba 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,9 @@ 3.17.2 1.1.1 3.6.1.Final + 0.3.0 + 4.12.3 + 3.3.1 3.13.0 3.3.1 @@ -120,6 +123,11 @@ + + com.open-elements.solidity + abi-parser + ${solidity-enterprise.version} + io.grpc grpc-okhttp @@ -200,6 +208,11 @@ auto-service-annotations ${google.auto.version} + + org.web3j + abi + ${web3j.version} +