From 8b7c1265c5dfb417795b95e9dd44ed6e3b6b053d Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Mon, 11 Nov 2024 18:01:20 +0200 Subject: [PATCH 01/16] feat: initial implementation Signed-off-by: Ivan Ivanov --- .../hedera/hashgraph/sdk/EntityIdHelper.java | 73 ++++-- .../sdk/MirrorNodeContractQuery.java | 209 ++++++++++++++++++ .../ContractCallIntegrationTest.java | 34 ++- .../ContractExecuteIntegrationTest.java | 10 +- ...ractFunctionParametersIntegrationTest.java | 49 +++- 5 files changed, 343 insertions(+), 32 deletions(-) create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java index 0a07af183..82cd46667 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java @@ -26,12 +26,14 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.net.http.HttpTimeoutException; import java.nio.ByteBuffer; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.regex.Pattern; import javax.annotation.Nullable; import org.bouncycastle.util.encoders.DecoderException; @@ -290,7 +292,7 @@ public static boolean isLongZeroAddress(byte[] address) { */ public static CompletableFuture getAccountNumFromMirrorNodeAsync(Client client, String evmAddress) { String apiEndpoint = "/accounts/" + evmAddress; - return performQueryToMirrorNodeAsync(client, apiEndpoint) + return performQueryToMirrorNodeAsync(client, apiEndpoint, null, false) .thenApply(response -> parseNumFromMirrorNodeResponse(response, "account")); } @@ -309,7 +311,7 @@ public static CompletableFuture getAccountNumFromMirrorNodeAsync(Client cl */ public static CompletableFuture getEvmAddressFromMirrorNodeAsync(Client client, long num) { String apiEndpoint = "/accounts/" + num; - return performQueryToMirrorNodeAsync(client, apiEndpoint) + return performQueryToMirrorNodeAsync(client, apiEndpoint, null, false) .thenApply(response -> EvmAddress.fromString(parseEvmAddressFromMirrorNodeResponse(response, "evm_address"))); } @@ -329,13 +331,34 @@ public static CompletableFuture getEvmAddressFromMirrorNodeAsync(Cli public static CompletableFuture getContractNumFromMirrorNodeAsync(Client client, String evmAddress) { String apiEndpoint = "/contracts/" + evmAddress; - CompletableFuture responseFuture = performQueryToMirrorNodeAsync(client, apiEndpoint); + CompletableFuture responseFuture = performQueryToMirrorNodeAsync(client, apiEndpoint, null, false); return responseFuture.thenApply(response -> parseNumFromMirrorNodeResponse(response, "contract_id")); } - private static CompletableFuture performQueryToMirrorNodeAsync(Client client, String apiEndpoint) { + + + + + private static long parseNumFromMirrorNodeResponse(String responseBody, String memberName) { + JsonParser jsonParser = new JsonParser(); + JsonObject jsonObject = jsonParser.parse(responseBody).getAsJsonObject(); + + String num = jsonObject.get(memberName).getAsString(); + + return Long.parseLong(num.substring(num.lastIndexOf(".") + 1)); + } + + public static CompletableFuture getContractAddressFromMirrorNodeAsync(Client client, String id) { + String apiEndpoint = "/contracts/" + id; + CompletableFuture responseFuture = performQueryToMirrorNodeAsync(client, apiEndpoint, null, false); + + return responseFuture.thenApply(response -> + parseEvmAddressFromMirrorNodeResponse(response, "evm_address")); + } + + static CompletableFuture performQueryToMirrorNodeAsync(Client client, String apiEndpoint, String jsonBody, boolean isContractCall) { Optional mirrorUrl = client.getMirrorNetwork().stream() .map(url -> url.substring(0, url.indexOf(":"))) .findFirst(); @@ -347,26 +370,40 @@ private static CompletableFuture performQueryToMirrorNodeAsync(Client cl String apiUrl = "https://" + mirrorUrl.get() + "/api/v1" + apiEndpoint; if (client.getLedgerId() == null) { - apiUrl = "http://" + mirrorUrl.get() + ":5551/api/v1" + apiEndpoint; + if (isContractCall) { + apiUrl = "http://" + mirrorUrl.get() + ":8545/api/v1" + apiEndpoint; + } else { + apiUrl = "http://" + mirrorUrl.get() + ":5551/api/v1" + apiEndpoint; + } } HttpClient httpClient = HttpClient.newHttpClient(); - HttpRequest httpRequest = HttpRequest.newBuilder() + var httpBuilder = HttpRequest.newBuilder() .timeout(MIRROR_NODE_CONNECTION_TIMEOUT) - .uri(URI.create(apiUrl)) - .build(); + .uri(URI.create(apiUrl)); - return httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()) - .thenApply(HttpResponse::body); - } - - private static long parseNumFromMirrorNodeResponse(String responseBody, String memberName) { - JsonParser jsonParser = new JsonParser(); - JsonObject jsonObject = jsonParser.parse(responseBody).getAsJsonObject(); - - String num = jsonObject.get(memberName).getAsString(); + if (jsonBody != null) { + httpBuilder.header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(jsonBody)); + } + var httpRequest = httpBuilder.build(); - return Long.parseLong(num.substring(num.lastIndexOf(".") + 1)); + return httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()) + .handle((response, ex) -> { + if (ex != null) { + if (ex instanceof HttpTimeoutException) { + throw new CompletionException(new RuntimeException("Request to Mirror Node timed out", ex)); + } else { + throw new CompletionException(new RuntimeException("Failed to send request to Mirror Node", ex)); + } + } + + int statusCode = response.statusCode(); + if (statusCode != 200) { + throw new CompletionException(new RuntimeException("Received non-200 response from Mirror Node: " + response.body())); + } + return response.body(); + }); } private static String parseEvmAddressFromMirrorNodeResponse(String responseBody, String memberName) { diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java new file mode 100644 index 000000000..87b01364c --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java @@ -0,0 +1,209 @@ +/*- + * + * Hedera Java SDK + * + * Copyright (C) 2020 - 2024 Hedera Hashgraph, LLC + * + * Licensed 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 com.hedera.hashgraph.sdk; + +import static com.hedera.hashgraph.sdk.EntityIdHelper.getContractAddressFromMirrorNodeAsync; +import static com.hedera.hashgraph.sdk.EntityIdHelper.performQueryToMirrorNodeAsync; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.protobuf.ByteString; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import org.bouncycastle.util.encoders.Hex; + +/** + * MirrorNodeContractQuery returns a result from EVM execution such as cost-free execution of read-only smart contract + * queries, gas estimation, and transient simulation of read-write operations. + */ +public class MirrorNodeContractQuery { + private ContractId contractId = null; + private String contractEvmAddress = null; + private byte[] functionParameters; + private long blockNumber; + + public ContractId getContractId() { + return this.contractId; + } + + /** + * Sets the contract instance to call. + * + * @param contractId The ContractId to be set + * @return {@code this} + */ + public MirrorNodeContractQuery setContractId(ContractId contractId) { + Objects.requireNonNull(contractId); + this.contractId = contractId; + return this; + } + + public String getContractEvmAddress() { + return this.contractEvmAddress; + } + + /** + * Set the 20-byte EVM address of the contract to call. + * + * @param contractEvmAddress + * @return {@code this} + */ + public MirrorNodeContractQuery setContractEvmAddress(String contractEvmAddress) { + Objects.requireNonNull(contractEvmAddress); + this.contractEvmAddress = contractEvmAddress; + this.contractId = null; + return this; + } + + public byte[] getFunctionParameters() { + return functionParameters; + } + + /** + * Sets the function to call, and the parameters to pass to the function. + * + * @param name The String to be set as the function name + * @param params The function parameters to be set + * @return {@code this} + */ + public MirrorNodeContractQuery setFunction(String name, ContractFunctionParameters params) { + Objects.requireNonNull(params); + return setFunctionParameters(params.toBytes(name)); + } + + /** + * Sets the function name to call. + *

+ * The function will be called with no parameters. Use {@link #setFunction(String, ContractFunctionParameters)} to + * call a function with parameters. + * + * @param name The String to be set as the function name + * @return {@code this} + */ + public MirrorNodeContractQuery setFunction(String name) { + return setFunction(name, new ContractFunctionParameters()); + } + + /** + * Sets the function parameters as their raw bytes. + *

+ * Use this instead of {@link #setFunction(String, ContractFunctionParameters)} if you have already pre-encoded a + * solidity function call. + * + * @param functionParameters The function parameters to be set + * @return {@code this} + */ + public MirrorNodeContractQuery setFunctionParameters(ByteString functionParameters) { + Objects.requireNonNull(functionParameters); + this.functionParameters = functionParameters.toByteArray(); + return this; + } + + public long getBlockNumber() { + return blockNumber; + } + + public void setBlockNumber(long blockNumber) { + this.blockNumber = blockNumber; + } + + /** + * Returns gas estimation for the EVM execution + * + * @param client + * @throws ExecutionException + * @throws InterruptedException + */ + public long estimate(Client client) throws ExecutionException, InterruptedException { + if (this.contractEvmAddress == null) { + Objects.requireNonNull(this.contractId); + this.contractEvmAddress = getContractAddressFromMirrorNodeAsync(client, this.contractId.toString()).get(); + } + return getEstimateGasFromMirrorNodeAsync(client, this.functionParameters, this.contractEvmAddress).get(); + } + + /** + * Does transient simulation of read-write operations and returns the result in hexadecimal string format + * + * @param client + * @throws ExecutionException + * @throws InterruptedException + */ + public String call(Client client) throws ExecutionException, InterruptedException { + if (this.contractEvmAddress == null) { + Objects.requireNonNull(this.contractId); + this.contractEvmAddress = getContractAddressFromMirrorNodeAsync(client, this.contractId.toString()).get(); + } + + var blockNum = this.blockNumber == 0 ? "" : String.valueOf(this.blockNumber); + return getContractCallResultFromMirrorNodeAsync(client, this.functionParameters, this.contractEvmAddress, + blockNum).get(); + } + + private static CompletableFuture getContractCallResultFromMirrorNodeAsync(Client client, byte[] data, + String contractAddress, String blockNumber) { + String apiEndpoint = "/contracts/call"; + String jsonPayload = createJsonPayload(data, contractAddress, blockNumber, false); + return performQueryToMirrorNodeAsync(client, apiEndpoint, jsonPayload, true) + .exceptionally(ex -> { + client.getLogger().error("Error in while performing post request to Mirror Node: " + ex.getMessage()); + throw new CompletionException(ex); + }) + .thenApply(MirrorNodeContractQuery::parseContractCallResult); + } + + public static CompletableFuture getEstimateGasFromMirrorNodeAsync(Client client, byte[] data, + String contractAddress) { + String apiEndpoint = "/contracts/call"; + String jsonPayload = createJsonPayload(data, contractAddress, "latest", true); + return performQueryToMirrorNodeAsync(client, apiEndpoint, jsonPayload, true) + .exceptionally(ex -> { + client.getLogger().error("Error in while performing post request to Mirror Node: " + ex.getMessage()); + throw new CompletionException(ex); + }) + .thenApply(MirrorNodeContractQuery::parseHexEstimateToLong); + } + + private static String createJsonPayload(byte[] data, String contractAddress, String blockNumber, boolean estimate) { + String hexData = Hex.toHexString(data); + return String.format(""" + { + "data": "%s", + "to": "%s", + "estimate": %b, + "blockNumber": "%s" + } + """, hexData, contractAddress, estimate, blockNumber); + } + + private static String parseContractCallResult(String responseBody) { + JsonParser jsonParser = new JsonParser(); + JsonObject jsonObject = jsonParser.parse(responseBody).getAsJsonObject(); + + return jsonObject.get("result").getAsString(); + } + + private static long parseHexEstimateToLong(String responseBody) { + return Integer.parseInt(parseContractCallResult(responseBody).substring(2), 16); + } +} diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractCallIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractCallIntegrationTest.java index fe9619810..e5c0a824a 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractCallIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractCallIntegrationTest.java @@ -27,6 +27,7 @@ import com.hedera.hashgraph.sdk.FileDeleteTransaction; import com.hedera.hashgraph.sdk.Hbar; import com.hedera.hashgraph.sdk.MaxQueryPaymentExceededException; +import com.hedera.hashgraph.sdk.MirrorNodeContractQuery; import com.hedera.hashgraph.sdk.PrecheckStatusException; import com.hedera.hashgraph.sdk.Status; import org.junit.jupiter.api.DisplayName; @@ -62,9 +63,15 @@ void canCallContractFunction() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + Thread.sleep(2000); + var gas = new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("getMessage") + .estimate(testEnv.client); + var callQuery = new ContractCallQuery() .setContractId(contractId) - .setGas(200000) + .setGas(gas) .setFunction("getMessage") .setQueryPayment(new Hbar(1)); @@ -244,9 +251,15 @@ void getCostBigMaxContractCallFunction() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + Thread.sleep(2000); + var gas = new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("getMessage") + .estimate(testEnv.client); + var callQuery = new ContractCallQuery() .setContractId(contractId) - .setGas(100000) + .setGas(gas) .setFunction("getMessage") .setQueryPayment(new Hbar(1)); @@ -291,9 +304,15 @@ void getCostSmallMaxContractCallFunction() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + Thread.sleep(2000); + var gas = new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("getMessage") + .estimate(testEnv.client); + var callQuery = new ContractCallQuery() .setContractId(contractId) - .setGas(100000) + .setGas(gas) .setFunction("getMessage") .setMaxQueryPayment(Hbar.fromTinybars(1)); @@ -337,9 +356,15 @@ void getCostInsufficientTxFeeContractCallFunction() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + Thread.sleep(2000); + var gas = new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("getMessage") + .estimate(testEnv.client); + var callQuery = new ContractCallQuery() .setContractId(contractId) - .setGas(100000) + .setGas(gas) .setFunction("getMessage") .setMaxQueryPayment(new Hbar(100)); @@ -357,7 +382,6 @@ void getCostInsufficientTxFeeContractCallFunction() throws Exception { .setFileId(fileId) .execute(testEnv.client) .getReceipt(testEnv.client); - } } } diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java index eaaff2eb9..2334d28df 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java @@ -28,6 +28,7 @@ import com.hedera.hashgraph.sdk.ContractFunctionParameters; import com.hedera.hashgraph.sdk.FileCreateTransaction; import com.hedera.hashgraph.sdk.FileDeleteTransaction; +import com.hedera.hashgraph.sdk.MirrorNodeContractQuery; import com.hedera.hashgraph.sdk.PrecheckStatusException; import com.hedera.hashgraph.sdk.ReceiptStatusException; import com.hedera.hashgraph.sdk.Status; @@ -60,9 +61,16 @@ void canExecuteContractMethods() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + Thread.sleep(2000); + // TODO: estimates lower than actual + var gas = new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("setMessage", new ContractFunctionParameters().addString("new message")) + .estimate(testEnv.client); + var receipt = new ContractExecuteTransaction() .setContractId(contractId) - .setGas(100000) + .setGas(gas + 15000) .setFunction("setMessage", new ContractFunctionParameters().addString("new message")) .execute(testEnv.client) .getReceipt(testEnv.client); diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractFunctionParametersIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractFunctionParametersIntegrationTest.java index bd75046aa..e51b82c4d 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractFunctionParametersIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractFunctionParametersIntegrationTest.java @@ -32,6 +32,7 @@ import com.hedera.hashgraph.sdk.FileDeleteTransaction; import com.hedera.hashgraph.sdk.FileId; import com.hedera.hashgraph.sdk.Hbar; +import com.hedera.hashgraph.sdk.MirrorNodeContractQuery; import java.math.BigInteger; import java.util.Arrays; import java.util.Objects; @@ -84,7 +85,11 @@ public void afterEach() throws InterruptedException { @Test @DisplayName("Can receive uint8 min value from contract call") void canCallContractFunctionUint8Min() throws Exception { - var response = new ContractCallQuery().setContractId(contractId).setGas(1500000) + var gas = new MirrorNodeContractQuery().setContractId(contractId) + .setFunction("returnUint8", new ContractFunctionParameters().addUint8((byte) 0x0)) + .estimate(testEnv.client); + + var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint8", new ContractFunctionParameters().addUint8((byte) 0x0)) .setQueryPayment(new Hbar(10)).execute(testEnv.client); @@ -97,7 +102,11 @@ void canCallContractFunctionUint8Max() throws Exception { int uint8Max = 255; byte uint8MaxByte = (byte) uint8Max; - var response = new ContractCallQuery().setContractId(contractId).setGas(1500000) + var gas = new MirrorNodeContractQuery().setContractId(contractId) + .setFunction("returnUint8", new ContractFunctionParameters().addUint8(uint8MaxByte)) + .estimate(testEnv.client); + + var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint8", new ContractFunctionParameters().addUint8(uint8MaxByte)) .setQueryPayment(new Hbar(10)).execute(testEnv.client); @@ -114,7 +123,11 @@ void canCallContractFunctionUint8Array() throws Exception { byte uint8MaxByte = (byte) uint8Max; byte[] uint8Array = {uint8MinByte, uint8MaxByte}; - var response = new ContractCallQuery().setContractId(contractId).setGas(1500000) + var gas = new MirrorNodeContractQuery().setContractId(contractId) + .setFunction("returnUint8Arr", new ContractFunctionParameters().addUint8Array(uint8Array)) + .estimate(testEnv.client); + + var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint8Arr", new ContractFunctionParameters().addUint8Array(uint8Array)) .setQueryPayment(new Hbar(10)).execute(testEnv.client); @@ -140,7 +153,11 @@ void canCallContractFunctionUint16Max() throws Exception { var uint16Max = "65535"; int uint16MaxInt = Integer.parseUnsignedInt(uint16Max); - var response = new ContractCallQuery().setContractId(contractId).setGas(1500000) + var gas = new MirrorNodeContractQuery().setContractId(contractId) + .setFunction("returnUint16", new ContractFunctionParameters().addUint16(uint16MaxInt)) + .estimate(testEnv.client); + + var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint16", new ContractFunctionParameters().addUint16(uint16MaxInt)) .setQueryPayment(new Hbar(10)).execute(testEnv.client); @@ -157,7 +174,11 @@ void canCallContractFunctionUint16Array() throws Exception { int uint16MaxInt = Integer.parseUnsignedInt(uint16Max); int[] uint16Array = {uint16MinInt, uint16MaxInt}; - var response = new ContractCallQuery().setContractId(contractId).setGas(1500000) + var gas = new MirrorNodeContractQuery().setContractId(contractId) + .setFunction("returnUint16Arr", new ContractFunctionParameters().addUint16Array(uint16Array)) + .estimate(testEnv.client); + + var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint16Arr", new ContractFunctionParameters().addUint16Array(uint16Array)) .setQueryPayment(new Hbar(10)).execute(testEnv.client); @@ -169,7 +190,11 @@ void canCallContractFunctionUint16Array() throws Exception { @Test @DisplayName("Can receive uint24 min value from contract call") void canCallContractFunctionUint24Min() throws Exception { - var response = new ContractCallQuery().setContractId(contractId).setGas(1500000) + var gas = new MirrorNodeContractQuery().setContractId(contractId) + .setFunction("returnUint24", new ContractFunctionParameters().addUint24(0)) + .estimate(testEnv.client); + + var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint24", new ContractFunctionParameters().addUint24(0)).setQueryPayment(new Hbar(10)) .execute(testEnv.client); @@ -182,7 +207,11 @@ void canCallContractFunctionUint24Max() throws Exception { var uint24Max = "16777215"; int uint24MaxInt = Integer.parseUnsignedInt(uint24Max); - var response = new ContractCallQuery().setContractId(contractId).setGas(1500000) + var gas = new MirrorNodeContractQuery().setContractId(contractId) + .setFunction("returnUint24", new ContractFunctionParameters().addUint24(uint24MaxInt)) + .estimate(testEnv.client); + + var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint24", new ContractFunctionParameters().addUint24(uint24MaxInt)) .setQueryPayment(new Hbar(10)).execute(testEnv.client); @@ -199,7 +228,11 @@ void canCallContractFunctionUint24Array() throws Exception { int uint24MaxInt = Integer.parseUnsignedInt(uint24Max); int[] uint24Array = {uint24MinInt, uint24MaxInt}; - var response = new ContractCallQuery().setContractId(contractId).setGas(1500000) + var gas = new MirrorNodeContractQuery().setContractId(contractId) + .setFunction("returnUint24Arr", new ContractFunctionParameters().addUint24Array(uint24Array)) + .estimate(testEnv.client); + + var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint24Arr", new ContractFunctionParameters().addUint24Array(uint24Array)) .setQueryPayment(new Hbar(10)).execute(testEnv.client); From e02b001126f52b009908a4bf96fdcfd00c7e18ff Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 12 Nov 2024 13:35:21 +0200 Subject: [PATCH 02/16] test: add unit tests Signed-off-by: Ivan Ivanov --- .../sdk/MirrorNodeContractQuery.java | 22 ++- .../sdk/MirrorNodeContractQueryTest.java | 139 ++++++++++++++++++ .../sdk/MirrorNodeContractQueryTest.snap | 0 3 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.snap diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java index 87b01364c..95704a225 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java @@ -39,7 +39,7 @@ public class MirrorNodeContractQuery { private ContractId contractId = null; private String contractEvmAddress = null; - private byte[] functionParameters; + private byte[] callData; private long blockNumber; public ContractId getContractId() { @@ -75,8 +75,8 @@ public MirrorNodeContractQuery setContractEvmAddress(String contractEvmAddress) return this; } - public byte[] getFunctionParameters() { - return functionParameters; + public byte[] getCallData() { + return this.callData; } /** @@ -115,7 +115,7 @@ public MirrorNodeContractQuery setFunction(String name) { */ public MirrorNodeContractQuery setFunctionParameters(ByteString functionParameters) { Objects.requireNonNull(functionParameters); - this.functionParameters = functionParameters.toByteArray(); + this.callData = functionParameters.toByteArray(); return this; } @@ -139,7 +139,7 @@ public long estimate(Client client) throws ExecutionException, InterruptedExcept Objects.requireNonNull(this.contractId); this.contractEvmAddress = getContractAddressFromMirrorNodeAsync(client, this.contractId.toString()).get(); } - return getEstimateGasFromMirrorNodeAsync(client, this.functionParameters, this.contractEvmAddress).get(); + return getEstimateGasFromMirrorNodeAsync(client, this.callData, this.contractEvmAddress).get(); } /** @@ -156,7 +156,7 @@ public String call(Client client) throws ExecutionException, InterruptedExceptio } var blockNum = this.blockNumber == 0 ? "" : String.valueOf(this.blockNumber); - return getContractCallResultFromMirrorNodeAsync(client, this.functionParameters, this.contractEvmAddress, + return getContractCallResultFromMirrorNodeAsync(client, this.callData, this.contractEvmAddress, blockNum).get(); } @@ -184,7 +184,7 @@ public static CompletableFuture getEstimateGasFromMirrorNodeAsync(Client c .thenApply(MirrorNodeContractQuery::parseHexEstimateToLong); } - private static String createJsonPayload(byte[] data, String contractAddress, String blockNumber, boolean estimate) { + static String createJsonPayload(byte[] data, String contractAddress, String blockNumber, boolean estimate) { String hexData = Hex.toHexString(data); return String.format(""" { @@ -196,14 +196,12 @@ private static String createJsonPayload(byte[] data, String contractAddress, Str """, hexData, contractAddress, estimate, blockNumber); } - private static String parseContractCallResult(String responseBody) { - JsonParser jsonParser = new JsonParser(); - JsonObject jsonObject = jsonParser.parse(responseBody).getAsJsonObject(); - + static String parseContractCallResult(String responseBody) { + JsonObject jsonObject = JsonParser.parseString(responseBody).getAsJsonObject(); return jsonObject.get("result").getAsString(); } - private static long parseHexEstimateToLong(String responseBody) { + static long parseHexEstimateToLong(String responseBody) { return Integer.parseInt(parseContractCallResult(responseBody).substring(2), 16); } } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java new file mode 100644 index 000000000..80b267279 --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java @@ -0,0 +1,139 @@ +/*- + * + * Hedera Java SDK + * + * Copyright (C) 2020 - 2024 Hedera Hashgraph, LLC + * + * Licensed 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 com.hedera.hashgraph.sdk; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.protobuf.ByteString; +import io.github.jsonSnapshot.SnapshotMatcher; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class MirrorNodeContractQueryTest { + + private MirrorNodeContractQuery query; + private ContractId mockContractId; + + @BeforeAll + public static void beforeAll() { + SnapshotMatcher.start(Snapshot::asJsonString); + } + + @AfterAll + public static void afterAll() { + SnapshotMatcher.validateSnapshots(); + } + + @BeforeEach + void setUp() { + query = new MirrorNodeContractQuery(); + mockContractId = Mockito.mock(ContractId.class); + } + + @Test + void testSetAndGetContractId() { + query.setContractId(mockContractId); + assertEquals(mockContractId, query.getContractId()); + } + + @Test + void testSetContractIdWithNull_ThrowsException() { + assertThrows(NullPointerException.class, () -> query.setContractId(null)); + } + + @Test + void testSetAndGetContractEvmAddress() { + String evmAddress = "0x1234567890abcdef1234567890abcdef12345678"; + query.setContractEvmAddress(evmAddress); + assertEquals(evmAddress, query.getContractEvmAddress()); + assertNull(query.getContractId(), "Setting EVM address should reset ContractId to null."); + } + + @Test + void testSetContractEvmAddressWithNull_ThrowsException() { + assertThrows(NullPointerException.class, () -> query.setContractEvmAddress(null)); + } + + @Test + void testSetAndGetcallData() { + ByteString params = ByteString.copyFromUtf8("test"); + query.setFunctionParameters(params); + assertArrayEquals(params.toByteArray(), query.getCallData()); + } + + @Test + void testSetFunctionWithoutParameters() { + query.setFunction("myFunction"); + assertNotNull(query.getCallData(), "Function parameters should not be null after setting a function."); + } + + @Test + void testSetAndGetBlockNumber() { + long blockNumber = 123456; + query.setBlockNumber(blockNumber); + assertEquals(blockNumber, query.getBlockNumber()); + } + + @Test + void testEstimateGasWithMissingContractIdOrEvmAddress_ThrowsException() { + ByteString params = ByteString.copyFromUtf8("gasParams"); + query.setFunctionParameters(params); + + assertThrows(NullPointerException.class, () -> query.estimate(null)); + } + + @Test + void testCreateJsonPayload() { + String contractAddress = "0xabcdefabcdefabcdefabcdefabcdefabcdef"; + byte[] data = "data".getBytes(); + String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, contractAddress, "latest", true); + + String expectedJson = """ + { + "data": "64617461", + "to": "0xabcdefabcdefabcdefabcdefabcdefabcdef", + "estimate": true, + "blockNumber": "latest" + } + """; + assertEquals(expectedJson.trim(), jsonPayload.trim()); + } + + @Test + void testParseHexEstimateToLong() { + String responseBody = "{\"result\": \"0x1234\"}"; + long parsedResult = MirrorNodeContractQuery.parseHexEstimateToLong(responseBody); + assertEquals(0x1234, parsedResult); + } + + @Test + void testParseContractCallResult() { + String responseBody = "{\"result\": \"0x1234abcdef\"}"; + String parsedResult = MirrorNodeContractQuery.parseContractCallResult(responseBody); + assertEquals("0x1234abcdef", parsedResult); + } +} diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.snap new file mode 100644 index 000000000..e69de29bb From 094e29c8fefc07ee5cc11f0d7a2c90fde98a92cc Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 12 Nov 2024 14:11:00 +0200 Subject: [PATCH 03/16] chore: add tests Signed-off-by: Ivan Ivanov --- .../hedera/hashgraph/sdk/EntityIdHelper.java | 5 +- .../sdk/MirrorNodeContractQuery.java | 3 +- ...irrorNodeContractQueryIntegrationTest.java | 118 ++++++++++++++++++ 3 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java index 82cd46667..f3a74b234 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java @@ -407,11 +407,8 @@ static CompletableFuture performQueryToMirrorNodeAsync(Client client, St } private static String parseEvmAddressFromMirrorNodeResponse(String responseBody, String memberName) { - JsonParser jsonParser = new JsonParser(); - JsonObject jsonObject = jsonParser.parse(responseBody).getAsJsonObject(); - + JsonObject jsonObject = JsonParser.parseString(responseBody).getAsJsonObject(); String evmAddress = jsonObject.get(memberName).getAsString(); - return evmAddress.substring(evmAddress.lastIndexOf(".") + 1); } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java index 95704a225..a08a586d2 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java @@ -143,7 +143,8 @@ public long estimate(Client client) throws ExecutionException, InterruptedExcept } /** - * Does transient simulation of read-write operations and returns the result in hexadecimal string format + * Does transient simulation of read-write operations and returns the result in hexadecimal string format. + * The result can be any solidity type. * * @param client * @throws ExecutionException diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java new file mode 100644 index 000000000..59ecd5bae --- /dev/null +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java @@ -0,0 +1,118 @@ +/*- + * + * Hedera Java SDK + * + * Copyright (C) 2020 - 2024 Hedera Hashgraph, LLC + * + * Licensed 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 com.hedera.hashgraph.sdk.test.integration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.hedera.hashgraph.sdk.ContractCallQuery; +import com.hedera.hashgraph.sdk.ContractCreateTransaction; +import com.hedera.hashgraph.sdk.ContractDeleteTransaction; +import com.hedera.hashgraph.sdk.ContractId; +import com.hedera.hashgraph.sdk.FileCreateTransaction; +import com.hedera.hashgraph.sdk.FileDeleteTransaction; +import com.hedera.hashgraph.sdk.Hbar; +import com.hedera.hashgraph.sdk.MirrorNodeContractQuery; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MirrorNodeContractQueryIntegrationTest { + private static final String SMART_CONTRACT_BYTECODE = "6080604052348015600e575f80fd5b50335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102228061005b5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80637065cb4814610038578063893d20e814610054575b5f80fd5b610052600480360381019061004d9190610199565b610072565b005b61005c610114565b60405161006991906101d3565b60405180910390f35b805f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600181908060018154018082558091505060019003905f5260205f20015f9091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b5f805f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101688261013f565b9050919050565b6101788161015e565b8114610182575f80fd5b50565b5f813590506101938161016f565b92915050565b5f602082840312156101ae576101ad61013b565b5b5f6101bb84828501610185565b91505092915050565b6101cd8161015e565b82525050565b5f6020820190506101e65f8301846101c4565b9291505056fea2646970667358221220a02fed47a387783e8f429664c5f72014d9e3c1a31c5a9c197090b10fc5ea96f864736f6c634300081a0033"; + + @Test + @DisplayName("Can estimate and simulate transaction") + void canSimulateTransaction() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var response = new FileCreateTransaction() + .setKeys(testEnv.operatorKey) + .setContents(SMART_CONTRACT_BYTECODE) + .execute(testEnv.client); + + var fileId = Objects.requireNonNull(response.getReceipt(testEnv.client).fileId); + + response = new ContractCreateTransaction() + .setAdminKey(testEnv.operatorKey) + .setGas(200000) + .setBytecodeFileId(fileId) + .execute(testEnv.client); + + var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + + Thread.sleep(2000); + + var gas = new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("getOwner") + .estimate(testEnv.client); + + var callQuery = new ContractCallQuery() + .setContractId(contractId) + .setGas(gas) + .setFunction("getOwner") + .setQueryPayment(new Hbar(1)); + + var result = callQuery + .execute(testEnv.client); + + var simulationResult = new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("getOwner") + .call(testEnv.client); + + assertThat(result.getAddress(0)).isEqualTo(simulationResult.substring(26)); + + new ContractDeleteTransaction() + .setTransferAccountId(testEnv.operatorId) + .setContractId(contractId) + .execute(testEnv.client) + .getReceipt(testEnv.client); + + new FileDeleteTransaction() + .setFileId(fileId) + .execute(testEnv.client) + .getReceipt(testEnv.client); + } + } + + @Test + @DisplayName("Can estimate and simulate transaction") + void failsWhenContractIsNotDeployed() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var contractId = new ContractId(1231456); + + assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { + new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("getOwner") + .estimate(testEnv.client); + }).withMessageContaining("Received non-200 response from Mirror Node"); + + assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { + new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("getOwner") + .call(testEnv.client); + }).withMessageContaining("Received non-200 response from Mirror Node"); + } + } +} From b82e02c9bcfd4b74de84332dd2fac268e908e0c5 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 12 Nov 2024 15:12:52 +0200 Subject: [PATCH 04/16] chore: formatting Signed-off-by: Ivan Ivanov --- .../hedera/hashgraph/sdk/EntityIdHelper.java | 22 ++++++------------- .../ContractExecuteIntegrationTest.java | 5 ++--- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java index f3a74b234..c2504d159 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java @@ -313,7 +313,7 @@ public static CompletableFuture getEvmAddressFromMirrorNodeAsync(Cli String apiEndpoint = "/accounts/" + num; return performQueryToMirrorNodeAsync(client, apiEndpoint, null, false) .thenApply(response -> - EvmAddress.fromString(parseEvmAddressFromMirrorNodeResponse(response, "evm_address"))); + EvmAddress.fromString(parseStringMirrorNodeResponse(response, "evm_address"))); } /** @@ -338,24 +338,12 @@ public static CompletableFuture getContractNumFromMirrorNodeAsync(Client c } - - - - private static long parseNumFromMirrorNodeResponse(String responseBody, String memberName) { - JsonParser jsonParser = new JsonParser(); - JsonObject jsonObject = jsonParser.parse(responseBody).getAsJsonObject(); - - String num = jsonObject.get(memberName).getAsString(); - - return Long.parseLong(num.substring(num.lastIndexOf(".") + 1)); - } - public static CompletableFuture getContractAddressFromMirrorNodeAsync(Client client, String id) { String apiEndpoint = "/contracts/" + id; CompletableFuture responseFuture = performQueryToMirrorNodeAsync(client, apiEndpoint, null, false); return responseFuture.thenApply(response -> - parseEvmAddressFromMirrorNodeResponse(response, "evm_address")); + parseStringMirrorNodeResponse(response, "evm_address")); } static CompletableFuture performQueryToMirrorNodeAsync(Client client, String apiEndpoint, String jsonBody, boolean isContractCall) { @@ -406,12 +394,16 @@ static CompletableFuture performQueryToMirrorNodeAsync(Client client, St }); } - private static String parseEvmAddressFromMirrorNodeResponse(String responseBody, String memberName) { + private static String parseStringMirrorNodeResponse(String responseBody, String memberName) { JsonObject jsonObject = JsonParser.parseString(responseBody).getAsJsonObject(); String evmAddress = jsonObject.get(memberName).getAsString(); return evmAddress.substring(evmAddress.lastIndexOf(".") + 1); } + private static long parseNumFromMirrorNodeResponse(String responseBody, String memberName) { + return Long.parseLong(parseStringMirrorNodeResponse(responseBody, memberName)); + } + @FunctionalInterface interface WithIdNums { R apply(long shard, long realm, long num, @Nullable String checksum); diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java index 2334d28df..cabc7213a 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java @@ -62,15 +62,14 @@ void canExecuteContractMethods() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); Thread.sleep(2000); - // TODO: estimates lower than actual + var gas = new MirrorNodeContractQuery() .setContractId(contractId) .setFunction("setMessage", new ContractFunctionParameters().addString("new message")) .estimate(testEnv.client); - var receipt = new ContractExecuteTransaction() .setContractId(contractId) - .setGas(gas + 15000) + .setGas(gas + 10000) .setFunction("setMessage", new ContractFunctionParameters().addString("new message")) .execute(testEnv.client) .getReceipt(testEnv.client); From 0bf9b6b715faece496e7fa653f1d0994311459c8 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 14 Nov 2024 10:28:21 +0200 Subject: [PATCH 05/16] feat: add from, gas, gasPrice and value as parameters Signed-off-by: Ivan Ivanov --- .../sdk/MirrorNodeContractQuery.java | 120 ++++++++++++++--- .../sdk/MirrorNodeContractQueryTest.java | 126 ++++++++++++++++-- 2 files changed, 213 insertions(+), 33 deletions(-) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java index a08a586d2..72b43f855 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java @@ -37,9 +37,21 @@ * queries, gas estimation, and transient simulation of read-write operations. */ public class MirrorNodeContractQuery { + // The contract we are sending the transaction to private ContractId contractId = null; private String contractEvmAddress = null; + // The account we are sending the transaction from + private AccountId sender = null; + private String senderEvmAddress = null; + // The transaction callData private byte[] callData; + // The amount we are sending to payable functions + private long value; + // The amount of gas we are sending + private long gas; + // The gas price + private long gasPrice; + // The block number for the simulation private long blockNumber; public ContractId getContractId() { @@ -75,6 +87,39 @@ public MirrorNodeContractQuery setContractEvmAddress(String contractEvmAddress) return this; } + public AccountId getSender() { + return this.sender; + } + + /** + * Sets the sender of the transaction simulation. + * + * @param sender The AccountId to be set + * @return {@code this} + */ + public MirrorNodeContractQuery setSender(AccountId sender) { + Objects.requireNonNull(sender); + this.sender = sender; + return this; + } + + public String getSenderEvmAddress() { + return this.senderEvmAddress; + } + + /** + * Set the 20-byte EVM address of the sender. + * + * @param senderEvmAddress + * @return {@code this} + */ + public MirrorNodeContractQuery setSenderEvmAddress(String senderEvmAddress) { + Objects.requireNonNull(sender); + this.senderEvmAddress = senderEvmAddress; + this.sender = null; + return this; + } + public byte[] getCallData() { return this.callData; } @@ -119,6 +164,30 @@ public MirrorNodeContractQuery setFunctionParameters(ByteString functionParamete return this; } + public long getValue() { + return value; + } + + public void setValue(long value) { + this.value = value; + } + + public long getGas() { + return gas; + } + + public void setGas(long gas) { + this.gas = gas; + } + + public long getGasPrice() { + return gasPrice; + } + + public void setGasPrice(long gasPrice) { + this.gasPrice = gasPrice; + } + public long getBlockNumber() { return blockNumber; } @@ -139,12 +208,12 @@ public long estimate(Client client) throws ExecutionException, InterruptedExcept Objects.requireNonNull(this.contractId); this.contractEvmAddress = getContractAddressFromMirrorNodeAsync(client, this.contractId.toString()).get(); } - return getEstimateGasFromMirrorNodeAsync(client, this.callData, this.contractEvmAddress).get(); + return getEstimateGasFromMirrorNodeAsync(client).get(); } /** - * Does transient simulation of read-write operations and returns the result in hexadecimal string format. - * The result can be any solidity type. + * Does transient simulation of read-write operations and returns the result in hexadecimal string format. The + * result can be any solidity type. * * @param client * @throws ExecutionException @@ -157,14 +226,12 @@ public String call(Client client) throws ExecutionException, InterruptedExceptio } var blockNum = this.blockNumber == 0 ? "" : String.valueOf(this.blockNumber); - return getContractCallResultFromMirrorNodeAsync(client, this.callData, this.contractEvmAddress, - blockNum).get(); + return getContractCallResultFromMirrorNodeAsync(client, blockNum).get(); } - private static CompletableFuture getContractCallResultFromMirrorNodeAsync(Client client, byte[] data, - String contractAddress, String blockNumber) { + private CompletableFuture getContractCallResultFromMirrorNodeAsync(Client client ,String blockNumber) { String apiEndpoint = "/contracts/call"; - String jsonPayload = createJsonPayload(data, contractAddress, blockNumber, false); + String jsonPayload = createJsonPayload(this.callData, this.senderEvmAddress, this.contractEvmAddress, this.gas, this.gasPrice, this.value, blockNumber, false); return performQueryToMirrorNodeAsync(client, apiEndpoint, jsonPayload, true) .exceptionally(ex -> { client.getLogger().error("Error in while performing post request to Mirror Node: " + ex.getMessage()); @@ -173,10 +240,9 @@ private static CompletableFuture getContractCallResultFromMirrorNodeAsyn .thenApply(MirrorNodeContractQuery::parseContractCallResult); } - public static CompletableFuture getEstimateGasFromMirrorNodeAsync(Client client, byte[] data, - String contractAddress) { + public CompletableFuture getEstimateGasFromMirrorNodeAsync(Client client) { String apiEndpoint = "/contracts/call"; - String jsonPayload = createJsonPayload(data, contractAddress, "latest", true); + String jsonPayload = createJsonPayload(this.callData, this.contractEvmAddress, this.contractEvmAddress, this.gas, this.gasPrice, this.value, "latest", true); return performQueryToMirrorNodeAsync(client, apiEndpoint, jsonPayload, true) .exceptionally(ex -> { client.getLogger().error("Error in while performing post request to Mirror Node: " + ex.getMessage()); @@ -185,16 +251,30 @@ public static CompletableFuture getEstimateGasFromMirrorNodeAsync(Client c .thenApply(MirrorNodeContractQuery::parseHexEstimateToLong); } - static String createJsonPayload(byte[] data, String contractAddress, String blockNumber, boolean estimate) { + static String createJsonPayload(byte[] data, String senderAddress, String contractAddress, long gas, long gasPrice, long value, String blockNumber, boolean estimate) { String hexData = Hex.toHexString(data); - return String.format(""" - { - "data": "%s", - "to": "%s", - "estimate": %b, - "blockNumber": "%s" - } - """, hexData, contractAddress, estimate, blockNumber); + + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("data", hexData); + jsonObject.addProperty("to", contractAddress); + jsonObject.addProperty("estimate", estimate); + jsonObject.addProperty("blockNumber", blockNumber); + + // Conditionally add fields if they are set to non-default values + if (senderAddress != null && !senderAddress.isEmpty()) { + jsonObject.addProperty("from", senderAddress); + } + if (gas > 0) { + jsonObject.addProperty("gas", gas); + } + if (gasPrice > 0) { + jsonObject.addProperty("gasPrice", gasPrice); + } + if (value > 0) { + jsonObject.addProperty("value", value); + } + + return jsonObject.toString(); } static String parseContractCallResult(String responseBody) { diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java index 80b267279..269205f20 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import io.github.jsonSnapshot.SnapshotMatcher; import org.junit.jupiter.api.AfterAll; @@ -98,6 +99,27 @@ void testSetAndGetBlockNumber() { assertEquals(blockNumber, query.getBlockNumber()); } + @Test + void testSetAndGetValue() { + long value = 1000; + query.setValue(value); + assertEquals(value, query.getValue()); + } + + @Test + void testSetAndGetGas() { + long gas = 50000; + query.setGas(gas); + assertEquals(gas, query.getGas()); + } + + @Test + void testSetAndGetGasPrice() { + long gasPrice = 200; + query.setGasPrice(gasPrice); + assertEquals(gasPrice, query.getGasPrice()); + } + @Test void testEstimateGasWithMissingContractIdOrEvmAddress_ThrowsException() { ByteString params = ByteString.copyFromUtf8("gasParams"); @@ -107,20 +129,98 @@ void testEstimateGasWithMissingContractIdOrEvmAddress_ThrowsException() { } @Test - void testCreateJsonPayload() { + void testCreateJsonPayload_AllFieldsSet() { + byte[] data = "testData".getBytes(); + String senderAddress = "0x1234567890abcdef1234567890abcdef12345678"; + String contractAddress = "0xabcdefabcdefabcdefabcdefabcdefabcdef"; + long gas = 50000; + long gasPrice = 2000; + long value = 1000; + String blockNumber = "latest"; + boolean estimate = true; + + String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, senderAddress, contractAddress, gas, gasPrice, value, blockNumber, estimate); + + JsonObject expectedJson = new JsonObject(); + expectedJson.addProperty("data", "7465737444617461"); // "testData" in hex + expectedJson.addProperty("to", contractAddress); + expectedJson.addProperty("estimate", estimate); + expectedJson.addProperty("blockNumber", blockNumber); + expectedJson.addProperty("from", senderAddress); + expectedJson.addProperty("gas", gas); + expectedJson.addProperty("gasPrice", gasPrice); + expectedJson.addProperty("value", value); + + assertEquals(expectedJson.toString(), jsonPayload); + } + + @Test + void testCreateJsonPayload_OnlyRequiredFieldsSet() { + byte[] data = "testData".getBytes(); + String senderAddress = ""; + String contractAddress = "0xabcdefabcdefabcdefabcdefabcdefabcdef"; + long gas = 0; + long gasPrice = 0; + long value = 0; + String blockNumber = "latest"; + boolean estimate = true; + + String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, senderAddress, contractAddress, gas, gasPrice, value, blockNumber, estimate); + + JsonObject expectedJson = new JsonObject(); + expectedJson.addProperty("data", "7465737444617461"); // "testData" in hex + expectedJson.addProperty("to", contractAddress); + expectedJson.addProperty("estimate", estimate); + expectedJson.addProperty("blockNumber", blockNumber); + + assertEquals(expectedJson.toString(), jsonPayload); + } + + @Test + void testCreateJsonPayload_SomeOptionalFieldsSet() { + byte[] data = "testData".getBytes(); + String senderAddress = "0x1234567890abcdef1234567890abcdef12345678"; + String contractAddress = "0xabcdefabcdefabcdefabcdefabcdefabcdef"; + long gas = 50000; + long gasPrice = 0; + long value = 1000; + String blockNumber = "latest"; + boolean estimate = false; + + String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, senderAddress, contractAddress, gas, gasPrice, value, blockNumber, estimate); + + JsonObject expectedJson = new JsonObject(); + expectedJson.addProperty("data", "7465737444617461"); // "testData" in hex + expectedJson.addProperty("to", contractAddress); + expectedJson.addProperty("estimate", estimate); + expectedJson.addProperty("blockNumber", blockNumber); + expectedJson.addProperty("from", senderAddress); + expectedJson.addProperty("gas", gas); + expectedJson.addProperty("value", value); + + assertEquals(expectedJson.toString(), jsonPayload); + } + + @Test + void testCreateJsonPayload_AllOptionalFieldsDefault() { + byte[] data = "testData".getBytes(); String contractAddress = "0xabcdefabcdefabcdefabcdefabcdefabcdef"; - byte[] data = "data".getBytes(); - String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, contractAddress, "latest", true); - - String expectedJson = """ - { - "data": "64617461", - "to": "0xabcdefabcdefabcdefabcdefabcdefabcdef", - "estimate": true, - "blockNumber": "latest" - } - """; - assertEquals(expectedJson.trim(), jsonPayload.trim()); + String senderAddress = ""; + long gas = 0; + long gasPrice = 0; + long value = 0; + String blockNumber = "latest"; + boolean estimate = false; + + String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, senderAddress, contractAddress, gas, gasPrice, value, blockNumber, estimate); + + JsonObject expectedJson = new JsonObject(); + expectedJson.addProperty("data", "7465737444617461"); // "testData" in hex + expectedJson.addProperty("to", contractAddress); + expectedJson.addProperty("estimate", estimate); + expectedJson.addProperty("blockNumber", blockNumber); + + assertEquals(expectedJson.toString(), jsonPayload); } @Test From 53587b9c9bc552b88e56f0bb82b2497d6cab16fb Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 14 Nov 2024 13:33:27 +0200 Subject: [PATCH 06/16] test: add more integration tests Signed-off-by: Ivan Ivanov --- .../hedera/hashgraph/sdk/EntityIdHelper.java | 2 +- .../sdk/MirrorNodeContractQuery.java | 69 ++++--- .../sdk/MirrorNodeContractQueryTest.java | 18 +- ...irrorNodeContractQueryIntegrationTest.java | 172 +++++++++++++++++- 4 files changed, 215 insertions(+), 46 deletions(-) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java index c2504d159..baf42af0d 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java @@ -42,7 +42,7 @@ /** * Utility class used internally by the sdk. */ -class EntityIdHelper { +public class EntityIdHelper { /** * The length of a Solidity address in bytes. */ diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java index 72b43f855..e54072675 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java @@ -21,6 +21,7 @@ package com.hedera.hashgraph.sdk; import static com.hedera.hashgraph.sdk.EntityIdHelper.getContractAddressFromMirrorNodeAsync; +import static com.hedera.hashgraph.sdk.EntityIdHelper.getEvmAddressFromMirrorNodeAsync; import static com.hedera.hashgraph.sdk.EntityIdHelper.performQueryToMirrorNodeAsync; import com.google.gson.JsonObject; @@ -47,8 +48,8 @@ public class MirrorNodeContractQuery { private byte[] callData; // The amount we are sending to payable functions private long value; - // The amount of gas we are sending - private long gas; + // The gas limit + private long gasLimit; // The gas price private long gasPrice; // The block number for the simulation @@ -114,7 +115,7 @@ public String getSenderEvmAddress() { * @return {@code this} */ public MirrorNodeContractQuery setSenderEvmAddress(String senderEvmAddress) { - Objects.requireNonNull(sender); + Objects.requireNonNull(senderEvmAddress); this.senderEvmAddress = senderEvmAddress; this.sender = null; return this; @@ -165,31 +166,34 @@ public MirrorNodeContractQuery setFunctionParameters(ByteString functionParamete } public long getValue() { - return value; + return this.value; } - public void setValue(long value) { + public MirrorNodeContractQuery setValue(long value) { this.value = value; + return this; } - public long getGas() { - return gas; + public long getGasLimit() { + return this.gasLimit; } - public void setGas(long gas) { - this.gas = gas; + public MirrorNodeContractQuery setGasLimit(long gasLimit) { + this.gasLimit = gasLimit; + return this; } public long getGasPrice() { return gasPrice; } - public void setGasPrice(long gasPrice) { + public MirrorNodeContractQuery setGasPrice(long gasPrice) { this.gasPrice = gasPrice; + return this; } public long getBlockNumber() { - return blockNumber; + return this.blockNumber; } public void setBlockNumber(long blockNumber) { @@ -204,10 +208,7 @@ public void setBlockNumber(long blockNumber) { * @throws InterruptedException */ public long estimate(Client client) throws ExecutionException, InterruptedException { - if (this.contractEvmAddress == null) { - Objects.requireNonNull(this.contractId); - this.contractEvmAddress = getContractAddressFromMirrorNodeAsync(client, this.contractId.toString()).get(); - } + fillEvmAddresses(client); return getEstimateGasFromMirrorNodeAsync(client).get(); } @@ -220,38 +221,46 @@ public long estimate(Client client) throws ExecutionException, InterruptedExcept * @throws InterruptedException */ public String call(Client client) throws ExecutionException, InterruptedException { + fillEvmAddresses(client); + var blockNum = this.blockNumber == 0 ? "latest" : String.valueOf(this.blockNumber); + return getContractCallResultFromMirrorNodeAsync(client, blockNum).get(); + } + + private void fillEvmAddresses(Client client) throws ExecutionException, InterruptedException { if (this.contractEvmAddress == null) { Objects.requireNonNull(this.contractId); this.contractEvmAddress = getContractAddressFromMirrorNodeAsync(client, this.contractId.toString()).get(); } - var blockNum = this.blockNumber == 0 ? "" : String.valueOf(this.blockNumber); - return getContractCallResultFromMirrorNodeAsync(client, blockNum).get(); + if (this.senderEvmAddress == null && this.sender != null) { + this.senderEvmAddress = getEvmAddressFromMirrorNodeAsync(client, this.sender.num).get().toString(); + } } - private CompletableFuture getContractCallResultFromMirrorNodeAsync(Client client ,String blockNumber) { - String apiEndpoint = "/contracts/call"; - String jsonPayload = createJsonPayload(this.callData, this.senderEvmAddress, this.contractEvmAddress, this.gas, this.gasPrice, this.value, blockNumber, false); - return performQueryToMirrorNodeAsync(client, apiEndpoint, jsonPayload, true) - .exceptionally(ex -> { - client.getLogger().error("Error in while performing post request to Mirror Node: " + ex.getMessage()); - throw new CompletionException(ex); - }) + private CompletableFuture getContractCallResultFromMirrorNodeAsync(Client client, String blockNumber) { + return executeMirrorNodeRequest(client, blockNumber, false) .thenApply(MirrorNodeContractQuery::parseContractCallResult); } public CompletableFuture getEstimateGasFromMirrorNodeAsync(Client client) { + return executeMirrorNodeRequest(client, "latest", true) + .thenApply(MirrorNodeContractQuery::parseHexEstimateToLong); + } + + private CompletableFuture executeMirrorNodeRequest(Client client, String blockNumber, boolean estimate) { String apiEndpoint = "/contracts/call"; - String jsonPayload = createJsonPayload(this.callData, this.contractEvmAddress, this.contractEvmAddress, this.gas, this.gasPrice, this.value, "latest", true); + String jsonPayload = createJsonPayload(this.callData, this.senderEvmAddress, this.contractEvmAddress, + this.gasLimit, this.gasPrice, this.value, blockNumber, estimate); + return performQueryToMirrorNodeAsync(client, apiEndpoint, jsonPayload, true) .exceptionally(ex -> { - client.getLogger().error("Error in while performing post request to Mirror Node: " + ex.getMessage()); + client.getLogger().error("Error while performing post request to Mirror Node: " + ex.getMessage()); throw new CompletionException(ex); - }) - .thenApply(MirrorNodeContractQuery::parseHexEstimateToLong); + }); } - static String createJsonPayload(byte[] data, String senderAddress, String contractAddress, long gas, long gasPrice, long value, String blockNumber, boolean estimate) { + static String createJsonPayload(byte[] data, String senderAddress, String contractAddress, long gas, long gasPrice, + long value, String blockNumber, boolean estimate) { String hexData = Hex.toHexString(data); JsonObject jsonObject = new JsonObject(); diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java index 269205f20..05af0ee9b 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java @@ -62,7 +62,7 @@ void testSetAndGetContractId() { } @Test - void testSetContractIdWithNull_ThrowsException() { + void testSetContractIdWithNullThrowsException() { assertThrows(NullPointerException.class, () -> query.setContractId(null)); } @@ -75,7 +75,7 @@ void testSetAndGetContractEvmAddress() { } @Test - void testSetContractEvmAddressWithNull_ThrowsException() { + void testSetContractEvmAddressWithNullThrowsException() { assertThrows(NullPointerException.class, () -> query.setContractEvmAddress(null)); } @@ -109,8 +109,8 @@ void testSetAndGetValue() { @Test void testSetAndGetGas() { long gas = 50000; - query.setGas(gas); - assertEquals(gas, query.getGas()); + query.setGasLimit(gas); + assertEquals(gas, query.getGasLimit()); } @Test @@ -121,7 +121,7 @@ void testSetAndGetGasPrice() { } @Test - void testEstimateGasWithMissingContractIdOrEvmAddress_ThrowsException() { + void testEstimateGasWithMissingContractIdOrEvmAddressThrowsException() { ByteString params = ByteString.copyFromUtf8("gasParams"); query.setFunctionParameters(params); @@ -129,7 +129,7 @@ void testEstimateGasWithMissingContractIdOrEvmAddress_ThrowsException() { } @Test - void testCreateJsonPayload_AllFieldsSet() { + void testCreateJsonPayloadAllFieldsSet() { byte[] data = "testData".getBytes(); String senderAddress = "0x1234567890abcdef1234567890abcdef12345678"; String contractAddress = "0xabcdefabcdefabcdefabcdefabcdefabcdef"; @@ -155,7 +155,7 @@ void testCreateJsonPayload_AllFieldsSet() { } @Test - void testCreateJsonPayload_OnlyRequiredFieldsSet() { + void testCreateJsonPayloadOnlyRequiredFieldsSet() { byte[] data = "testData".getBytes(); String senderAddress = ""; String contractAddress = "0xabcdefabcdefabcdefabcdefabcdefabcdef"; @@ -177,7 +177,7 @@ void testCreateJsonPayload_OnlyRequiredFieldsSet() { } @Test - void testCreateJsonPayload_SomeOptionalFieldsSet() { + void testCreateJsonPayloadSomeOptionalFieldsSet() { byte[] data = "testData".getBytes(); String senderAddress = "0x1234567890abcdef1234567890abcdef12345678"; String contractAddress = "0xabcdefabcdefabcdefabcdefabcdefabcdef"; @@ -202,7 +202,7 @@ void testCreateJsonPayload_SomeOptionalFieldsSet() { } @Test - void testCreateJsonPayload_AllOptionalFieldsDefault() { + void testCreateJsonPayloadAllOptionalFieldsDefault() { byte[] data = "testData".getBytes(); String contractAddress = "0xabcdefabcdefabcdefabcdefabcdefabcdef"; String senderAddress = ""; diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java index 59ecd5bae..e760eecc4 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java @@ -20,24 +20,30 @@ package com.hedera.hashgraph.sdk.test.integration; +import static com.hedera.hashgraph.sdk.EntityIdHelper.getEvmAddressFromMirrorNodeAsync; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import com.hedera.hashgraph.sdk.AccountCreateTransaction; import com.hedera.hashgraph.sdk.ContractCallQuery; import com.hedera.hashgraph.sdk.ContractCreateTransaction; import com.hedera.hashgraph.sdk.ContractDeleteTransaction; +import com.hedera.hashgraph.sdk.ContractExecuteTransaction; +import com.hedera.hashgraph.sdk.ContractFunctionParameters; import com.hedera.hashgraph.sdk.ContractId; import com.hedera.hashgraph.sdk.FileCreateTransaction; import com.hedera.hashgraph.sdk.FileDeleteTransaction; import com.hedera.hashgraph.sdk.Hbar; import com.hedera.hashgraph.sdk.MirrorNodeContractQuery; +import com.hedera.hashgraph.sdk.PrivateKey; import java.util.Objects; import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class MirrorNodeContractQueryIntegrationTest { - private static final String SMART_CONTRACT_BYTECODE = "6080604052348015600e575f80fd5b50335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102228061005b5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80637065cb4814610038578063893d20e814610054575b5f80fd5b610052600480360381019061004d9190610199565b610072565b005b61005c610114565b60405161006991906101d3565b60405180910390f35b805f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600181908060018154018082558091505060019003905f5260205f20015f9091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b5f805f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101688261013f565b9050919050565b6101788161015e565b8114610182575f80fd5b50565b5f813590506101938161016f565b92915050565b5f602082840312156101ae576101ad61013b565b5b5f6101bb84828501610185565b91505092915050565b6101cd8161015e565b82525050565b5f6020820190506101e65f8301846101c4565b9291505056fea2646970667358221220a02fed47a387783e8f429664c5f72014d9e3c1a31c5a9c197090b10fc5ea96f864736f6c634300081a0033"; + private static final String SMART_CONTRACT_BYTECODE = "6080604052348015600e575f80fd5b50335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506104a38061005b5f395ff3fe608060405260043610610033575f3560e01c8063607a4427146100375780637065cb4814610053578063893d20e81461007b575b5f80fd5b610051600480360381019061004c919061033c565b6100a5565b005b34801561005e575f80fd5b50610079600480360381019061007491906103a2565b610215565b005b348015610086575f80fd5b5061008f6102b7565b60405161009c91906103dc565b60405180910390f35b3373ffffffffffffffffffffffffffffffffffffffff165f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146100fb575f80fd5b805f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600181908060018154018082558091505060019003905f5260205f20015f9091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f8173ffffffffffffffffffffffffffffffffffffffff166108fc3490811502906040515f60405180830381858888f19350505050905080610211576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102089061044f565b60405180910390fd5b5050565b805f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600181908060018154018082558091505060019003905f5260205f20015f9091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b5f805f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61030b826102e2565b9050919050565b61031b81610301565b8114610325575f80fd5b50565b5f8135905061033681610312565b92915050565b5f60208284031215610351576103506102de565b5b5f61035e84828501610328565b91505092915050565b5f610371826102e2565b9050919050565b61038181610367565b811461038b575f80fd5b50565b5f8135905061039c81610378565b92915050565b5f602082840312156103b7576103b66102de565b5b5f6103c48482850161038e565b91505092915050565b6103d681610367565b82525050565b5f6020820190506103ef5f8301846103cd565b92915050565b5f82825260208201905092915050565b7f5472616e73666572206661696c656400000000000000000000000000000000005f82015250565b5f610439600f836103f5565b915061044482610405565b602082019050919050565b5f6020820190508181035f8301526104668161042d565b905091905056fea26469706673582212206c46ddb2acdbcc4290e15be83eb90cd0b2ce5bd82b9bfe58a0709c5aec96305564736f6c634300081a0033"; + private static final String ADDRESS = "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"; @Test @DisplayName("Can estimate and simulate transaction") @@ -65,13 +71,11 @@ void canSimulateTransaction() throws Exception { .setFunction("getOwner") .estimate(testEnv.client); - var callQuery = new ContractCallQuery() + var result = new ContractCallQuery() .setContractId(contractId) .setGas(gas) .setFunction("getOwner") - .setQueryPayment(new Hbar(1)); - - var result = callQuery + .setQueryPayment(new Hbar(1)) .execute(testEnv.client); var simulationResult = new MirrorNodeContractQuery() @@ -81,6 +85,23 @@ void canSimulateTransaction() throws Exception { assertThat(result.getAddress(0)).isEqualTo(simulationResult.substring(26)); + gas = new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("addOwner", new ContractFunctionParameters().addAddress(ADDRESS)) + .estimate(testEnv.client); + + new ContractExecuteTransaction() + .setContractId(contractId) + .setGas(gas) + .setFunction("addOwner", new ContractFunctionParameters().addAddress(ADDRESS)) + .execute(testEnv.client) + .getReceipt(testEnv.client); + + new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("addOwner", new ContractFunctionParameters().addAddress(ADDRESS)) + .call(testEnv.client); + new ContractDeleteTransaction() .setTransferAccountId(testEnv.operatorId) .setContractId(contractId) @@ -95,7 +116,7 @@ void canSimulateTransaction() throws Exception { } @Test - @DisplayName("Can estimate and simulate transaction") + @DisplayName("Fails when contract is not deployed") void failsWhenContractIsNotDeployed() throws Exception { try (var testEnv = new IntegrationTestEnv(1)) { var contractId = new ContractId(1231456); @@ -115,4 +136,143 @@ void failsWhenContractIsNotDeployed() throws Exception { }).withMessageContaining("Received non-200 response from Mirror Node"); } } + + @Test + @DisplayName("Fails when gas limit is low") + void failsWhenGasLimitIsLow() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var response = new FileCreateTransaction() + .setKeys(testEnv.operatorKey) + .setContents(SMART_CONTRACT_BYTECODE) + .execute(testEnv.client); + + var fileId = Objects.requireNonNull(response.getReceipt(testEnv.client).fileId); + + response = new ContractCreateTransaction() + .setAdminKey(testEnv.operatorKey) + .setGas(200000) + .setBytecodeFileId(fileId) + .execute(testEnv.client); + + var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + + Thread.sleep(2000); + + assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { + new MirrorNodeContractQuery() + .setContractId(contractId) + .setGasLimit(100) + .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(ADDRESS)) + .estimate(testEnv.client); + }).withMessageContaining("Received non-200 response from Mirror Node"); + + assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { + new MirrorNodeContractQuery() + .setContractId(contractId) + .setGasLimit(100) + .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(ADDRESS)) + .call(testEnv.client); + }).withMessageContaining("Received non-200 response from Mirror Node"); + + } + } + + @Test + @DisplayName("Fails when sender is not set") + void failsWhenSenderIsNotSet() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var response = new FileCreateTransaction() + .setKeys(testEnv.operatorKey) + .setContents(SMART_CONTRACT_BYTECODE) + .execute(testEnv.client); + + var fileId = Objects.requireNonNull(response.getReceipt(testEnv.client).fileId); + + response = new ContractCreateTransaction() + .setAdminKey(testEnv.operatorKey) + .setGas(200000) + .setBytecodeFileId(fileId) + .execute(testEnv.client); + + var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + + Thread.sleep(2000); + + assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { + new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(ADDRESS)) + .estimate(testEnv.client); + }).withMessageContaining("Received non-200 response from Mirror Node"); + + assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { + new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(ADDRESS)) + .call(testEnv.client); + }).withMessageContaining("Received non-200 response from Mirror Node"); + + } + } + + @Test + @DisplayName("Can simulate with sender set") + void canSimulateWithSenderSet() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var response = new FileCreateTransaction() + .setKeys(testEnv.operatorKey) + .setContents(SMART_CONTRACT_BYTECODE) + .execute(testEnv.client); + + var fileId = Objects.requireNonNull(response.getReceipt(testEnv.client).fileId); + + response = new ContractCreateTransaction() + .setAdminKey(testEnv.operatorKey) + .setGas(200000) + .setBytecodeFileId(fileId) + .execute(testEnv.client); + + var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + + var receiverAccountId = new AccountCreateTransaction() + .setKey(PrivateKey.generateED25519()) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .accountId; + Thread.sleep(2000); + + var receiverEvmAddress = getEvmAddressFromMirrorNodeAsync(testEnv.client, receiverAccountId.num).get() + .toString(); + + var owner = new MirrorNodeContractQuery() + .setContractId(contractId) + .setFunction("getOwner") + .call(testEnv.client) + .substring(26); + + var gas = new MirrorNodeContractQuery() + .setContractId(contractId) + .setGasLimit(1_000_000) + .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(receiverEvmAddress)) + .setSenderEvmAddress(owner) + .setValue(123) + .estimate(testEnv.client); + + new ContractExecuteTransaction() + .setContractId(contractId) + .setGas(gas) + .setPayableAmount(new Hbar(1)) + .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(receiverEvmAddress)) + .execute(testEnv.client) + .getReceipt(testEnv.client); + + new MirrorNodeContractQuery() + .setContractId(contractId) + .setGasLimit(1_000_000) + .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(receiverEvmAddress)) + .setSenderEvmAddress(owner) + .setValue(123) + .call(testEnv.client); + } + } } From 3141debe98ded9ce0a96be11d859c72db1f9d160 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 14 Nov 2024 13:40:56 +0200 Subject: [PATCH 07/16] chore: fix javadoc Signed-off-by: Ivan Ivanov --- .../com/hedera/hashgraph/sdk/EntityIdHelper.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java index baf42af0d..7258c7f44 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java @@ -114,7 +114,7 @@ private static R fromSolidityAddress(byte[] address, WithIdNums withAddre * @param address the string representation * @return the decoded address */ - public static byte[] decodeSolidityAddress(String address) { + static byte[] decodeSolidityAddress(String address) { address = address.startsWith("0x") ? address.substring(2) : address; if (address.length() != SOLIDITY_ADDRESS_LEN_HEX) { @@ -269,7 +269,7 @@ static String toStringWithChecksum(long shard, long realm, long num, Client clie * @param address * @return */ - public static boolean isLongZeroAddress(byte[] address) { + static boolean isLongZeroAddress(byte[] address) { for (int i = 0; i < 12; i++) { if (address[i] != 0) { return false; @@ -286,11 +286,8 @@ public static boolean isLongZeroAddress(byte[] address) { * * @param client * @param evmAddress - * @return - * @throws IOException - * @throws InterruptedException */ - public static CompletableFuture getAccountNumFromMirrorNodeAsync(Client client, String evmAddress) { + static CompletableFuture getAccountNumFromMirrorNodeAsync(Client client, String evmAddress) { String apiEndpoint = "/accounts/" + evmAddress; return performQueryToMirrorNodeAsync(client, apiEndpoint, null, false) .thenApply(response -> @@ -305,9 +302,6 @@ public static CompletableFuture getAccountNumFromMirrorNodeAsync(Client cl * * @param client * @param num - * @return - * @throws IOException - * @throws InterruptedException */ public static CompletableFuture getEvmAddressFromMirrorNodeAsync(Client client, long num) { String apiEndpoint = "/accounts/" + num; @@ -324,9 +318,6 @@ public static CompletableFuture getEvmAddressFromMirrorNodeAsync(Cli * * @param client * @param evmAddress - * @return - * @throws IOException - * @throws InterruptedException */ public static CompletableFuture getContractNumFromMirrorNodeAsync(Client client, String evmAddress) { String apiEndpoint = "/contracts/" + evmAddress; From 5f2e4377f23516a89ed4f9cbefb8837d3462db84 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 14 Nov 2024 13:55:21 +0200 Subject: [PATCH 08/16] chore: implement toString Signed-off-by: Ivan Ivanov --- .../sdk/MirrorNodeContractQuery.java | 19 ++++++- .../sdk/MirrorNodeContractQueryTest.java | 55 +++++++++++++++---- .../sdk/MirrorNodeContractQueryTest.snap | 3 + 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java index e54072675..e2925adec 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java @@ -27,6 +27,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.protobuf.ByteString; +import java.util.Arrays; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -196,8 +197,9 @@ public long getBlockNumber() { return this.blockNumber; } - public void setBlockNumber(long blockNumber) { + public MirrorNodeContractQuery setBlockNumber(long blockNumber) { this.blockNumber = blockNumber; + return this; } /** @@ -294,4 +296,19 @@ static String parseContractCallResult(String responseBody) { static long parseHexEstimateToLong(String responseBody) { return Integer.parseInt(parseContractCallResult(responseBody).substring(2), 16); } + + @Override + public String toString() { + return "MirrorNodeContractQuery{" + + "contractId=" + contractId + + ", contractEvmAddress='" + contractEvmAddress + '\'' + + ", sender=" + sender + + ", senderEvmAddress='" + senderEvmAddress + '\'' + + ", callData=" + Arrays.toString(callData) + + ", value=" + value + + ", gasLimit=" + gasLimit + + ", gasPrice=" + gasPrice + + ", blockNumber=" + blockNumber + + '}'; + } } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java index 05af0ee9b..2239624c0 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java @@ -71,7 +71,7 @@ void testSetAndGetContractEvmAddress() { String evmAddress = "0x1234567890abcdef1234567890abcdef12345678"; query.setContractEvmAddress(evmAddress); assertEquals(evmAddress, query.getContractEvmAddress()); - assertNull(query.getContractId(), "Setting EVM address should reset ContractId to null."); + assertNull(query.getContractId()); } @Test @@ -89,7 +89,7 @@ void testSetAndGetcallData() { @Test void testSetFunctionWithoutParameters() { query.setFunction("myFunction"); - assertNotNull(query.getCallData(), "Function parameters should not be null after setting a function."); + assertNotNull(query.getCallData()); } @Test @@ -139,10 +139,11 @@ void testCreateJsonPayloadAllFieldsSet() { String blockNumber = "latest"; boolean estimate = true; - String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, senderAddress, contractAddress, gas, gasPrice, value, blockNumber, estimate); + String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, senderAddress, contractAddress, gas, + gasPrice, value, blockNumber, estimate); JsonObject expectedJson = new JsonObject(); - expectedJson.addProperty("data", "7465737444617461"); // "testData" in hex + expectedJson.addProperty("data", "testData"); expectedJson.addProperty("to", contractAddress); expectedJson.addProperty("estimate", estimate); expectedJson.addProperty("blockNumber", blockNumber); @@ -165,10 +166,11 @@ void testCreateJsonPayloadOnlyRequiredFieldsSet() { String blockNumber = "latest"; boolean estimate = true; - String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, senderAddress, contractAddress, gas, gasPrice, value, blockNumber, estimate); + String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, senderAddress, contractAddress, gas, + gasPrice, value, blockNumber, estimate); JsonObject expectedJson = new JsonObject(); - expectedJson.addProperty("data", "7465737444617461"); // "testData" in hex + expectedJson.addProperty("data", "testData"); expectedJson.addProperty("to", contractAddress); expectedJson.addProperty("estimate", estimate); expectedJson.addProperty("blockNumber", blockNumber); @@ -187,10 +189,11 @@ void testCreateJsonPayloadSomeOptionalFieldsSet() { String blockNumber = "latest"; boolean estimate = false; - String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, senderAddress, contractAddress, gas, gasPrice, value, blockNumber, estimate); + String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, senderAddress, contractAddress, gas, + gasPrice, value, blockNumber, estimate); JsonObject expectedJson = new JsonObject(); - expectedJson.addProperty("data", "7465737444617461"); // "testData" in hex + expectedJson.addProperty("data", "testData"); expectedJson.addProperty("to", contractAddress); expectedJson.addProperty("estimate", estimate); expectedJson.addProperty("blockNumber", blockNumber); @@ -212,10 +215,11 @@ void testCreateJsonPayloadAllOptionalFieldsDefault() { String blockNumber = "latest"; boolean estimate = false; - String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, senderAddress, contractAddress, gas, gasPrice, value, blockNumber, estimate); + String jsonPayload = MirrorNodeContractQuery.createJsonPayload(data, senderAddress, contractAddress, gas, + gasPrice, value, blockNumber, estimate); JsonObject expectedJson = new JsonObject(); - expectedJson.addProperty("data", "7465737444617461"); // "testData" in hex + expectedJson.addProperty("data", "testData"); expectedJson.addProperty("to", contractAddress); expectedJson.addProperty("estimate", estimate); expectedJson.addProperty("blockNumber", blockNumber); @@ -236,4 +240,35 @@ void testParseContractCallResult() { String parsedResult = MirrorNodeContractQuery.parseContractCallResult(responseBody); assertEquals("0x1234abcdef", parsedResult); } + + @Test + void shouldSerialize() { + ContractId testContractId = new ContractId(0, 0, 1234); + String testEvmAddress = "0x1234567890abcdef1234567890abcdef12345678"; + AccountId testSenderId = new AccountId(0, 0, 5678); + String testSenderEvmAddress = "0xabcdefabcdefabcdefabcdefabcdefabcdef"; + ByteString testCallData = ByteString.copyFromUtf8("testData"); + String testFunctionName = "myFunction"; + ContractFunctionParameters testParams = new ContractFunctionParameters().addString("param1"); + + long testValue = 1000L; + long testGasLimit = 500000L; + long testGasPrice = 20L; + long testBlockNumber = 123456L; + + var query = new MirrorNodeContractQuery() + .setContractId(testContractId) + .setContractEvmAddress(testEvmAddress) + .setSender(testSenderId) + .setSenderEvmAddress(testSenderEvmAddress) + .setFunction(testFunctionName, testParams) + .setFunctionParameters(testCallData) + .setValue(testValue) + .setGasLimit(testGasLimit) + .setGasPrice(testGasPrice) + .setBlockNumber(testBlockNumber); + + SnapshotMatcher.expect(query.toString() + ).toMatchSnapshot(); + } } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.snap index e69de29bb..6f22e4bfe 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.snap @@ -0,0 +1,3 @@ +com.hedera.hashgraph.sdk.MirrorNodeContractQueryTest.shouldSerialize=[ + "MirrorNodeContractQuery{contractId=null, contractEvmAddress='0x1234567890abcdef1234567890abcdef12345678', sender=null, senderEvmAddress='0xabcdefabcdefabcdefabcdefabcdefabcdef', callData=[116, 101, 115, 116, 68, 97, 116, 97], value=1000, gasLimit=500000, gasPrice=20, blockNumber=123456}" +] \ No newline at end of file From d8bd3a90a4904b98f6762ffb244cfa5739fcc9b9 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 14 Nov 2024 14:32:51 +0200 Subject: [PATCH 09/16] chore: fix tests Signed-off-by: Ivan Ivanov --- .../kotlin/com.hedera.gradle.java.gradle.kts | 1 + .../sdk/MirrorNodeContractQuery.java | 34 ++++++++++++++++++- .../sdk/MirrorNodeContractQueryTest.java | 8 ++--- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.java.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.java.gradle.kts index db6a9e48f..2106a4a82 100644 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.java.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.java.gradle.kts @@ -37,6 +37,7 @@ testing.suites { testType = TestSuiteType.INTEGRATION_TEST targets.all { testTask { + failFast = true group = "build" systemProperty("CONFIG_FILE", providers.gradleProperty("CONFIG_FILE").getOrElse("")) systemProperty("HEDERA_NETWORK", providers.gradleProperty("HEDERA_NETWORK").getOrElse("")) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java index e2925adec..c49032c38 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java @@ -47,7 +47,7 @@ public class MirrorNodeContractQuery { private String senderEvmAddress = null; // The transaction callData private byte[] callData; - // The amount we are sending to payable functions + // The amount we are sending to the contract private long value; // The gas limit private long gasLimit; @@ -170,6 +170,14 @@ public long getValue() { return this.value; } + /** + * Sets the amount of value (in tinybars or wei) to be sent to the contract in the transaction. + *

+ * Use this to specify an amount for a payable function call. + * + * @param value the amount of value to send, in tinybars or wei + * @return {@code this} + */ public MirrorNodeContractQuery setValue(long value) { this.value = value; return this; @@ -179,6 +187,14 @@ public long getGasLimit() { return this.gasLimit; } + /** + * Sets the gas limit for the contract call. + *

+ * This specifies the maximum amount of gas that the transaction can consume. + * + * @param gasLimit the maximum gas allowed for the transaction + * @return {@code this} + */ public MirrorNodeContractQuery setGasLimit(long gasLimit) { this.gasLimit = gasLimit; return this; @@ -188,6 +204,14 @@ public long getGasPrice() { return gasPrice; } + /** + * Sets the gas price to be used for the contract call. + *

+ * This specifies the price of each unit of gas used in the transaction. + * + * @param gasPrice the gas price, in tinybars or wei, for each unit of gas + * @return {@code this} + */ public MirrorNodeContractQuery setGasPrice(long gasPrice) { this.gasPrice = gasPrice; return this; @@ -197,6 +221,14 @@ public long getBlockNumber() { return this.blockNumber; } + /** + * Sets the block number for the simulation of the contract call. + *

+ * The block number determines the context of the contract call simulation within the blockchain. + * + * @param blockNumber the block number at which to simulate the contract call + * @return {@code this} + */ public MirrorNodeContractQuery setBlockNumber(long blockNumber) { this.blockNumber = blockNumber; return this; diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java index 2239624c0..1f612aa8c 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java @@ -143,7 +143,7 @@ void testCreateJsonPayloadAllFieldsSet() { gasPrice, value, blockNumber, estimate); JsonObject expectedJson = new JsonObject(); - expectedJson.addProperty("data", "testData"); + expectedJson.addProperty("data", "7465737444617461"); expectedJson.addProperty("to", contractAddress); expectedJson.addProperty("estimate", estimate); expectedJson.addProperty("blockNumber", blockNumber); @@ -170,7 +170,7 @@ void testCreateJsonPayloadOnlyRequiredFieldsSet() { gasPrice, value, blockNumber, estimate); JsonObject expectedJson = new JsonObject(); - expectedJson.addProperty("data", "testData"); + expectedJson.addProperty("data", "7465737444617461"); expectedJson.addProperty("to", contractAddress); expectedJson.addProperty("estimate", estimate); expectedJson.addProperty("blockNumber", blockNumber); @@ -193,7 +193,7 @@ void testCreateJsonPayloadSomeOptionalFieldsSet() { gasPrice, value, blockNumber, estimate); JsonObject expectedJson = new JsonObject(); - expectedJson.addProperty("data", "testData"); + expectedJson.addProperty("data", "7465737444617461"); expectedJson.addProperty("to", contractAddress); expectedJson.addProperty("estimate", estimate); expectedJson.addProperty("blockNumber", blockNumber); @@ -219,7 +219,7 @@ void testCreateJsonPayloadAllOptionalFieldsDefault() { gasPrice, value, blockNumber, estimate); JsonObject expectedJson = new JsonObject(); - expectedJson.addProperty("data", "testData"); + expectedJson.addProperty("data", "7465737444617461"); expectedJson.addProperty("to", contractAddress); expectedJson.addProperty("estimate", estimate); expectedJson.addProperty("blockNumber", blockNumber); From dd5edd557b95300f7d6971e776b04d405a0b0e9b Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 19 Nov 2024 15:11:43 +0200 Subject: [PATCH 10/16] chore: fix PR comments Signed-off-by: Ivan Ivanov --- .../sdk/test/integration/ContractCallIntegrationTest.java | 8 ++++++++ .../test/integration/ContractExecuteIntegrationTest.java | 1 + .../MirrorNodeContractQueryIntegrationTest.java | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractCallIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractCallIntegrationTest.java index e5c0a824a..192fcb17b 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractCallIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractCallIntegrationTest.java @@ -63,7 +63,9 @@ void canCallContractFunction() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + // Wait for mirror node to import data Thread.sleep(2000); + var gas = new MirrorNodeContractQuery() .setContractId(contractId) .setFunction("getMessage") @@ -251,7 +253,9 @@ void getCostBigMaxContractCallFunction() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + // Wait for mirror node to import data Thread.sleep(2000); + var gas = new MirrorNodeContractQuery() .setContractId(contractId) .setFunction("getMessage") @@ -304,7 +308,9 @@ void getCostSmallMaxContractCallFunction() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + // Wait for mirror node to import data Thread.sleep(2000); + var gas = new MirrorNodeContractQuery() .setContractId(contractId) .setFunction("getMessage") @@ -356,7 +362,9 @@ void getCostInsufficientTxFeeContractCallFunction() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + // Wait for mirror node to import data Thread.sleep(2000); + var gas = new MirrorNodeContractQuery() .setContractId(contractId) .setFunction("getMessage") diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java index cabc7213a..8ab6bc419 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java @@ -61,6 +61,7 @@ void canExecuteContractMethods() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + // Wait for mirror node to import data Thread.sleep(2000); var gas = new MirrorNodeContractQuery() diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java index e760eecc4..a161d802e 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java @@ -64,6 +64,7 @@ void canSimulateTransaction() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + // Wait for mirror node to import data Thread.sleep(2000); var gas = new MirrorNodeContractQuery() @@ -156,6 +157,7 @@ void failsWhenGasLimitIsLow() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + // Wait for mirror node to import data Thread.sleep(2000); assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { @@ -196,6 +198,7 @@ void failsWhenSenderIsNotSet() throws Exception { var contractId = Objects.requireNonNull(response.getReceipt(testEnv.client).contractId); + // Wait for mirror node to import data Thread.sleep(2000); assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { @@ -239,6 +242,8 @@ void canSimulateWithSenderSet() throws Exception { .execute(testEnv.client) .getReceipt(testEnv.client) .accountId; + + // Wait for mirror node to import data Thread.sleep(2000); var receiverEvmAddress = getEvmAddressFromMirrorNodeAsync(testEnv.client, receiverAccountId.num).get() From 2bcf1d6d387b5ece3c61e67f222fbef03c748340 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 27 Nov 2024 12:28:29 +0200 Subject: [PATCH 11/16] chore: create separate classes for estimate and call Signed-off-by: Ivan Ivanov --- .../MirrorNodeContractQueriesExample.java | 176 ++++++++++++++++++ .../sdk/MirrorNodeContractCallQuery.java | 42 +++++ .../MirrorNodeContractEstimateGasQuery.java | 43 +++++ .../sdk/MirrorNodeContractQuery.java | 55 +++--- .../sdk/MirrorNodeContractQueryTest.java | 91 ++++++--- .../sdk/MirrorNodeContractQueryTest.snap | 2 +- .../ContractCallIntegrationTest.java | 17 +- .../ContractExecuteIntegrationTest.java | 5 +- ...ractFunctionParametersIntegrationTest.java | 33 ++-- ...irrorNodeContractQueryIntegrationTest.java | 56 +++--- 10 files changed, 416 insertions(+), 104 deletions(-) create mode 100644 examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractCallQuery.java create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractEstimateGasQuery.java diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java new file mode 100644 index 000000000..0180cc224 --- /dev/null +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java @@ -0,0 +1,176 @@ +/*- + * + * Hedera Java SDK + * + * Copyright (C) 2023 - 2024 Hedera Hashgraph, LLC + * + * Licensed 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 com.hedera.hashgraph.sdk.examples; + +import com.hedera.hashgraph.sdk.AccountId; +import com.hedera.hashgraph.sdk.Client; +import com.hedera.hashgraph.sdk.ContractCallQuery; +import com.hedera.hashgraph.sdk.ContractCreateTransaction; +import com.hedera.hashgraph.sdk.ContractFunctionParameters; +import com.hedera.hashgraph.sdk.FileCreateTransaction; +import com.hedera.hashgraph.sdk.FileId; +import com.hedera.hashgraph.sdk.Hbar; +import com.hedera.hashgraph.sdk.MirrorNodeContractCallQuery; +import com.hedera.hashgraph.sdk.MirrorNodeContractEstimateGasQuery; +import com.hedera.hashgraph.sdk.MirrorNodeContractQuery; +import com.hedera.hashgraph.sdk.PrivateKey; +import com.hedera.hashgraph.sdk.PublicKey; +import com.hedera.hashgraph.sdk.TransactionReceipt; +import com.hedera.hashgraph.sdk.TransactionResponse; +import com.hedera.hashgraph.sdk.logger.LogLevel; +import com.hedera.hashgraph.sdk.logger.Logger; +import io.github.cdimascio.dotenv.Dotenv; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +public class MirrorNodeContractQueriesExample { + /* + * See .env.sample in the examples folder root for how to specify values below + * or set environment variables with the same names. + */ + + /** + * Operator's account ID. + * Used to sign and pay for operations on Hedera. + */ + private static final AccountId OPERATOR_ID = AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID"))); + + /** + * Operator's private key. + */ + private static final PrivateKey OPERATOR_KEY = PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY"))); + + /** + * HEDERA_NETWORK defaults to testnet if not specified in dotenv file. + * Network can be: localhost, testnet, previewnet or mainnet. + */ + private static final String HEDERA_NETWORK = Dotenv.load().get("HEDERA_NETWORK", "testnet"); + + /** + * SDK_LOG_LEVEL defaults to SILENT if not specified in dotenv file. + * Log levels can be: TRACE, DEBUG, INFO, WARN, ERROR, SILENT. + *

+ * Important pre-requisite: set simple logger log level to same level as the SDK_LOG_LEVEL, + * for example via VM options: -Dorg.slf4j.simpleLogger.log.com.hedera.hashgraph=trace + */ + private static final String SDK_LOG_LEVEL = Dotenv.load().get("SDK_LOG_LEVEL", "SILENT"); + + private static final String SMART_CONTRACT_BYTECODE = "608060405234801561001057600080fd5b506040516104d73803806104d78339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b506040525050600080546001600160a01b0319163317905550805161010890600190602084019061010f565b50506101aa565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061015057805160ff191683800117855561017d565b8280016001018555821561017d579182015b8281111561017d578251825591602001919060010190610162565b5061018992915061018d565b5090565b6101a791905b808211156101895760008155600101610193565b90565b61031e806101b96000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063368b87721461004657806341c0e1b5146100ee578063ce6d41de146100f6575b600080fd5b6100ec6004803603602081101561005c57600080fd5b81019060208101813564010000000081111561007757600080fd5b82018360208201111561008957600080fd5b803590602001918460018302840111640100000000831117156100ab57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610173945050505050565b005b6100ec6101a2565b6100fe6101ba565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610138578181015183820152602001610120565b50505050905090810190601f1680156101655780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6000546001600160a01b0316331461018a5761019f565b805161019d906001906020840190610250565b505b50565b6000546001600160a01b03163314156101b85733ff5b565b60018054604080516020601f600260001961010087891615020190951694909404938401819004810282018101909252828152606093909290918301828280156102455780601f1061021a57610100808354040283529160200191610245565b820191906000526020600020905b81548152906001019060200180831161022857829003601f168201915b505050505090505b90565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061029157805160ff19168380011785556102be565b828001600101855582156102be579182015b828111156102be5782518255916020019190600101906102a3565b506102ca9291506102ce565b5090565b61024d91905b808211156102ca57600081556001016102d456fea264697066735822122084964d4c3f6bc912a9d20e14e449721012d625aa3c8a12de41ae5519752fc89064736f6c63430006000033"; + + public static void main(String[] args) throws Exception { + System.out.println("Mirror node contract query Example Start!"); + + /* + * Step 0: + * Create and configure the SDK Client. + */ + Client client = ClientHelper.forName(HEDERA_NETWORK); + // All generated transactions will be paid by this account and signed by this key. + client.setOperator(OPERATOR_ID, OPERATOR_KEY); + // Attach logger to the SDK Client. + client.setLogger(new Logger(LogLevel.valueOf(SDK_LOG_LEVEL))); + + var operatorPublicKey = OPERATOR_KEY.getPublicKey(); + + /* + * Step 1: + * Create the contract + */ + System.out.println("Creating new file..."); + TransactionResponse fileCreateTxResponse = new FileCreateTransaction() + // Use the same key as the operator to "own" this file. + .setKeys(operatorPublicKey) + .setContents(SMART_CONTRACT_BYTECODE) + // The default max fee of 1 Hbar is not enough to create a file (starts around ~1.1 Hbar). + .setMaxTransactionFee(Hbar.from(2)) + .execute(client); + + TransactionReceipt fileCreateTxReceipt = fileCreateTxResponse.getReceipt(client); + FileId newFileId = fileCreateTxReceipt.fileId; + + Objects.requireNonNull(newFileId); + System.out.println("Created new file with ID: " + newFileId); + + var response = new ContractCreateTransaction() + .setAdminKey(client.getOperatorPublicKey()) + .setGas(200000) + .setConstructorParameters(new ContractFunctionParameters().addString("Hello from Hedera.")) + .setBytecodeFileId(newFileId) + .setContractMemo("Simple contract with string field") + .execute(client); + + var contractId = Objects.requireNonNull(response.getReceipt(client).contractId); + + /* + * Step 3: + * Wait for mirror node to import data + */ + Thread.sleep(4000); + + /* + * Step 4: + * Estimate the gas needed + */ + var gas = new MirrorNodeContractEstimateGasQuery() + .setContractId(contractId) + .setFunction("getMessage") + .execute(client); + + System.out.println("Gas needed for this query: " + gas); + + /* + * Step 5: + * Do the query against the consensus node using the estimated gas + */ + var callQuery = new ContractCallQuery() + .setContractId(contractId) + .setGas(gas) + .setFunction("getMessage") + .setQueryPayment(new Hbar(1)); + + var result = callQuery + .execute(client); + + /* + * Step 5: + * Simulate the transaction for free, using the mirror node + */ + var simulationResult = new MirrorNodeContractCallQuery() + .setContractId(contractId) + .setFunction("getMessage") + .execute(client); + + System.out.println("Simulation result: " + decodeABIHex(simulationResult.substring(2))); + System.out.println("Contract call result: " + result.getString(0)); + } + private static String decodeABIHex(String hex) { + int length = Integer.parseInt(hex.substring(64, 128), 16); + + String hexStringData = hex.substring(128, 128 + length * 2); + + byte[] bytes = new byte[length]; + for (int i = 0; i < length; i++) { + bytes[i] = (byte) Integer.parseInt(hexStringData.substring(i * 2, i * 2 + 2), 16); + } + + return new String(bytes, StandardCharsets.UTF_8); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractCallQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractCallQuery.java new file mode 100644 index 000000000..7601f683c --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractCallQuery.java @@ -0,0 +1,42 @@ +/*- + * + * Hedera Java SDK + * + * Copyright (C) 2020 - 2024 Hedera Hashgraph, LLC + * + * Licensed 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 com.hedera.hashgraph.sdk; + +import java.util.concurrent.ExecutionException; + +public class MirrorNodeContractCallQuery extends MirrorNodeContractQuery { + /** + * Does transient simulation of read-write operations and returns the result in hexadecimal string format. + * + * @param client The Client instance to perform the operation with + * @return The result of the contract call + * @throws ExecutionException + * @throws InterruptedException + */ + public String execute(Client client) throws ExecutionException, InterruptedException { + return call(client); + } + + @Override + public String toString() { + return "MirrorNodeContractCallQuery" + super.toString(); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractEstimateGasQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractEstimateGasQuery.java new file mode 100644 index 000000000..2c9562c95 --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractEstimateGasQuery.java @@ -0,0 +1,43 @@ +/*- + * + * Hedera Java SDK + * + * Copyright (C) 2020 - 2024 Hedera Hashgraph, LLC + * + * Licensed 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 com.hedera.hashgraph.sdk; + +import java.util.concurrent.ExecutionException; + +public class MirrorNodeContractEstimateGasQuery extends MirrorNodeContractQuery { + + /** + * Returns gas estimation for the EVM execution. + * + * @param client The Client instance to perform the operation with + * @return The estimated gas cost + * @throws ExecutionException + * @throws InterruptedException + */ + public long execute(Client client) throws ExecutionException, InterruptedException { + return estimate(client); + } + + @Override + public String toString() { + return "MirrorNodeContractEstimateGasQuery" + super.toString(); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java index c49032c38..6047ea64c 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeContractQuery.java @@ -38,7 +38,7 @@ * MirrorNodeContractQuery returns a result from EVM execution such as cost-free execution of read-only smart contract * queries, gas estimation, and transient simulation of read-write operations. */ -public class MirrorNodeContractQuery { +public abstract class MirrorNodeContractQuery> { // The contract we are sending the transaction to private ContractId contractId = null; private String contractEvmAddress = null; @@ -56,6 +56,11 @@ public class MirrorNodeContractQuery { // The block number for the simulation private long blockNumber; + @SuppressWarnings("unchecked") + protected T self() { + return (T) this; + } + public ContractId getContractId() { return this.contractId; } @@ -66,10 +71,10 @@ public ContractId getContractId() { * @param contractId The ContractId to be set * @return {@code this} */ - public MirrorNodeContractQuery setContractId(ContractId contractId) { + public T setContractId(ContractId contractId) { Objects.requireNonNull(contractId); this.contractId = contractId; - return this; + return self(); } public String getContractEvmAddress() { @@ -82,11 +87,11 @@ public String getContractEvmAddress() { * @param contractEvmAddress * @return {@code this} */ - public MirrorNodeContractQuery setContractEvmAddress(String contractEvmAddress) { + public T setContractEvmAddress(String contractEvmAddress) { Objects.requireNonNull(contractEvmAddress); this.contractEvmAddress = contractEvmAddress; this.contractId = null; - return this; + return self(); } public AccountId getSender() { @@ -99,10 +104,10 @@ public AccountId getSender() { * @param sender The AccountId to be set * @return {@code this} */ - public MirrorNodeContractQuery setSender(AccountId sender) { + public T setSender(AccountId sender) { Objects.requireNonNull(sender); this.sender = sender; - return this; + return self(); } public String getSenderEvmAddress() { @@ -115,11 +120,11 @@ public String getSenderEvmAddress() { * @param senderEvmAddress * @return {@code this} */ - public MirrorNodeContractQuery setSenderEvmAddress(String senderEvmAddress) { + public T setSenderEvmAddress(String senderEvmAddress) { Objects.requireNonNull(senderEvmAddress); this.senderEvmAddress = senderEvmAddress; this.sender = null; - return this; + return self(); } public byte[] getCallData() { @@ -133,7 +138,7 @@ public byte[] getCallData() { * @param params The function parameters to be set * @return {@code this} */ - public MirrorNodeContractQuery setFunction(String name, ContractFunctionParameters params) { + public T setFunction(String name, ContractFunctionParameters params) { Objects.requireNonNull(params); return setFunctionParameters(params.toBytes(name)); } @@ -147,7 +152,7 @@ public MirrorNodeContractQuery setFunction(String name, ContractFunctionParamete * @param name The String to be set as the function name * @return {@code this} */ - public MirrorNodeContractQuery setFunction(String name) { + public T setFunction(String name) { return setFunction(name, new ContractFunctionParameters()); } @@ -160,10 +165,10 @@ public MirrorNodeContractQuery setFunction(String name) { * @param functionParameters The function parameters to be set * @return {@code this} */ - public MirrorNodeContractQuery setFunctionParameters(ByteString functionParameters) { + public T setFunctionParameters(ByteString functionParameters) { Objects.requireNonNull(functionParameters); this.callData = functionParameters.toByteArray(); - return this; + return self(); } public long getValue() { @@ -178,9 +183,9 @@ public long getValue() { * @param value the amount of value to send, in tinybars or wei * @return {@code this} */ - public MirrorNodeContractQuery setValue(long value) { + public T setValue(long value) { this.value = value; - return this; + return self(); } public long getGasLimit() { @@ -195,9 +200,9 @@ public long getGasLimit() { * @param gasLimit the maximum gas allowed for the transaction * @return {@code this} */ - public MirrorNodeContractQuery setGasLimit(long gasLimit) { + public T setGasLimit(long gasLimit) { this.gasLimit = gasLimit; - return this; + return self(); } public long getGasPrice() { @@ -212,9 +217,9 @@ public long getGasPrice() { * @param gasPrice the gas price, in tinybars or wei, for each unit of gas * @return {@code this} */ - public MirrorNodeContractQuery setGasPrice(long gasPrice) { + public T setGasPrice(long gasPrice) { this.gasPrice = gasPrice; - return this; + return self(); } public long getBlockNumber() { @@ -229,9 +234,9 @@ public long getBlockNumber() { * @param blockNumber the block number at which to simulate the contract call * @return {@code this} */ - public MirrorNodeContractQuery setBlockNumber(long blockNumber) { + public T setBlockNumber(long blockNumber) { this.blockNumber = blockNumber; - return this; + return self(); } /** @@ -241,7 +246,7 @@ public MirrorNodeContractQuery setBlockNumber(long blockNumber) { * @throws ExecutionException * @throws InterruptedException */ - public long estimate(Client client) throws ExecutionException, InterruptedException { + protected long estimate(Client client) throws ExecutionException, InterruptedException { fillEvmAddresses(client); return getEstimateGasFromMirrorNodeAsync(client).get(); } @@ -254,7 +259,7 @@ public long estimate(Client client) throws ExecutionException, InterruptedExcept * @throws ExecutionException * @throws InterruptedException */ - public String call(Client client) throws ExecutionException, InterruptedException { + protected String call(Client client) throws ExecutionException, InterruptedException { fillEvmAddresses(client); var blockNum = this.blockNumber == 0 ? "latest" : String.valueOf(this.blockNumber); return getContractCallResultFromMirrorNodeAsync(client, blockNum).get(); @@ -276,7 +281,7 @@ private CompletableFuture getContractCallResultFromMirrorNodeAsync(Clien .thenApply(MirrorNodeContractQuery::parseContractCallResult); } - public CompletableFuture getEstimateGasFromMirrorNodeAsync(Client client) { + private CompletableFuture getEstimateGasFromMirrorNodeAsync(Client client) { return executeMirrorNodeRequest(client, "latest", true) .thenApply(MirrorNodeContractQuery::parseHexEstimateToLong); } @@ -331,7 +336,7 @@ static long parseHexEstimateToLong(String responseBody) { @Override public String toString() { - return "MirrorNodeContractQuery{" + + return "{" + "contractId=" + contractId + ", contractEvmAddress='" + contractEvmAddress + '\'' + ", sender=" + sender + diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java index 1f612aa8c..888672bd1 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.java @@ -36,7 +36,8 @@ class MirrorNodeContractQueryTest { - private MirrorNodeContractQuery query; + private MirrorNodeContractEstimateGasQuery mirrorNodeContractEstimateGasQuery; + private MirrorNodeContractCallQuery mirrorNodeContractCallQuery; private ContractId mockContractId; @BeforeAll @@ -51,81 +52,108 @@ public static void afterAll() { @BeforeEach void setUp() { - query = new MirrorNodeContractQuery(); + mirrorNodeContractEstimateGasQuery = new MirrorNodeContractEstimateGasQuery(); + mirrorNodeContractCallQuery = new MirrorNodeContractCallQuery(); mockContractId = Mockito.mock(ContractId.class); } @Test void testSetAndGetContractId() { - query.setContractId(mockContractId); - assertEquals(mockContractId, query.getContractId()); + mirrorNodeContractEstimateGasQuery.setContractId(mockContractId); + assertEquals(mockContractId, mirrorNodeContractEstimateGasQuery.getContractId()); + + mirrorNodeContractCallQuery.setContractId(mockContractId); + assertEquals(mockContractId, mirrorNodeContractCallQuery.getContractId()); } @Test void testSetContractIdWithNullThrowsException() { - assertThrows(NullPointerException.class, () -> query.setContractId(null)); + assertThrows(NullPointerException.class, () -> mirrorNodeContractEstimateGasQuery.setContractId(null)); + assertThrows(NullPointerException.class, () -> mirrorNodeContractCallQuery.setContractId(null)); } @Test void testSetAndGetContractEvmAddress() { String evmAddress = "0x1234567890abcdef1234567890abcdef12345678"; - query.setContractEvmAddress(evmAddress); - assertEquals(evmAddress, query.getContractEvmAddress()); - assertNull(query.getContractId()); + mirrorNodeContractEstimateGasQuery.setContractEvmAddress(evmAddress); + assertEquals(evmAddress, mirrorNodeContractEstimateGasQuery.getContractEvmAddress()); + assertNull(mirrorNodeContractEstimateGasQuery.getContractId()); + + mirrorNodeContractCallQuery.setContractEvmAddress(evmAddress); + assertEquals(evmAddress, mirrorNodeContractCallQuery.getContractEvmAddress()); + assertNull(mirrorNodeContractCallQuery.getContractId()); } @Test void testSetContractEvmAddressWithNullThrowsException() { - assertThrows(NullPointerException.class, () -> query.setContractEvmAddress(null)); + assertThrows(NullPointerException.class, () -> mirrorNodeContractEstimateGasQuery.setContractEvmAddress(null)); + assertThrows(NullPointerException.class, () -> mirrorNodeContractCallQuery.setContractEvmAddress(null)); } @Test void testSetAndGetcallData() { ByteString params = ByteString.copyFromUtf8("test"); - query.setFunctionParameters(params); - assertArrayEquals(params.toByteArray(), query.getCallData()); + mirrorNodeContractEstimateGasQuery.setFunctionParameters(params); + assertArrayEquals(params.toByteArray(), mirrorNodeContractEstimateGasQuery.getCallData()); + + mirrorNodeContractCallQuery.setFunctionParameters(params); + assertArrayEquals(params.toByteArray(), mirrorNodeContractCallQuery.getCallData()); } @Test void testSetFunctionWithoutParameters() { - query.setFunction("myFunction"); - assertNotNull(query.getCallData()); + mirrorNodeContractEstimateGasQuery.setFunction("myFunction"); + assertNotNull(mirrorNodeContractEstimateGasQuery.getCallData()); } @Test void testSetAndGetBlockNumber() { long blockNumber = 123456; - query.setBlockNumber(blockNumber); - assertEquals(blockNumber, query.getBlockNumber()); + mirrorNodeContractEstimateGasQuery.setBlockNumber(blockNumber); + assertEquals(blockNumber, mirrorNodeContractEstimateGasQuery.getBlockNumber()); + + mirrorNodeContractCallQuery.setBlockNumber(blockNumber); + assertEquals(blockNumber, mirrorNodeContractCallQuery.getBlockNumber()); } @Test void testSetAndGetValue() { long value = 1000; - query.setValue(value); - assertEquals(value, query.getValue()); + mirrorNodeContractEstimateGasQuery.setValue(value); + assertEquals(value, mirrorNodeContractEstimateGasQuery.getValue()); + + mirrorNodeContractCallQuery.setValue(value); + assertEquals(value, mirrorNodeContractCallQuery.getValue()); } @Test void testSetAndGetGas() { long gas = 50000; - query.setGasLimit(gas); - assertEquals(gas, query.getGasLimit()); + mirrorNodeContractEstimateGasQuery.setGasLimit(gas); + assertEquals(gas, mirrorNodeContractEstimateGasQuery.getGasLimit()); + + mirrorNodeContractCallQuery.setGasLimit(gas); + assertEquals(gas, mirrorNodeContractCallQuery.getGasLimit()); } @Test void testSetAndGetGasPrice() { long gasPrice = 200; - query.setGasPrice(gasPrice); - assertEquals(gasPrice, query.getGasPrice()); + mirrorNodeContractEstimateGasQuery.setGasPrice(gasPrice); + assertEquals(gasPrice, mirrorNodeContractEstimateGasQuery.getGasPrice()); + + mirrorNodeContractCallQuery.setGasPrice(gasPrice); + assertEquals(gasPrice, mirrorNodeContractCallQuery.getGasPrice()); } @Test void testEstimateGasWithMissingContractIdOrEvmAddressThrowsException() { ByteString params = ByteString.copyFromUtf8("gasParams"); - query.setFunctionParameters(params); + mirrorNodeContractEstimateGasQuery.setFunctionParameters(params); + assertThrows(NullPointerException.class, () -> mirrorNodeContractEstimateGasQuery.estimate(null)); - assertThrows(NullPointerException.class, () -> query.estimate(null)); + mirrorNodeContractCallQuery.setFunctionParameters(params); + assertThrows(NullPointerException.class, () -> mirrorNodeContractCallQuery.estimate(null)); } @Test @@ -256,7 +284,7 @@ void shouldSerialize() { long testGasPrice = 20L; long testBlockNumber = 123456L; - var query = new MirrorNodeContractQuery() + var mirrorNodeContractEstimateGasQuery = new MirrorNodeContractEstimateGasQuery() .setContractId(testContractId) .setContractEvmAddress(testEvmAddress) .setSender(testSenderId) @@ -268,7 +296,20 @@ void shouldSerialize() { .setGasPrice(testGasPrice) .setBlockNumber(testBlockNumber); - SnapshotMatcher.expect(query.toString() + var mirrorNodeContractCallQuery = new MirrorNodeContractCallQuery() + .setContractId(testContractId) + .setContractEvmAddress(testEvmAddress) + .setSender(testSenderId) + .setSenderEvmAddress(testSenderEvmAddress) + .setFunction(testFunctionName, testParams) + .setFunctionParameters(testCallData) + .setValue(testValue) + .setGasLimit(testGasLimit) + .setGasPrice(testGasPrice) + .setBlockNumber(testBlockNumber); + + + SnapshotMatcher.expect(mirrorNodeContractEstimateGasQuery.toString() + mirrorNodeContractCallQuery.toString() ).toMatchSnapshot(); } } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.snap index 6f22e4bfe..2394e7ccf 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeContractQueryTest.snap @@ -1,3 +1,3 @@ com.hedera.hashgraph.sdk.MirrorNodeContractQueryTest.shouldSerialize=[ - "MirrorNodeContractQuery{contractId=null, contractEvmAddress='0x1234567890abcdef1234567890abcdef12345678', sender=null, senderEvmAddress='0xabcdefabcdefabcdefabcdefabcdefabcdef', callData=[116, 101, 115, 116, 68, 97, 116, 97], value=1000, gasLimit=500000, gasPrice=20, blockNumber=123456}" + "MirrorNodeContractEstimateGasQuery{contractId=null, contractEvmAddress='0x1234567890abcdef1234567890abcdef12345678', sender=null, senderEvmAddress='0xabcdefabcdefabcdefabcdefabcdefabcdef', callData=[116, 101, 115, 116, 68, 97, 116, 97], value=1000, gasLimit=500000, gasPrice=20, blockNumber=123456}MirrorNodeContractCallQuery{contractId=null, contractEvmAddress='0x1234567890abcdef1234567890abcdef12345678', sender=null, senderEvmAddress='0xabcdefabcdefabcdefabcdefabcdefabcdef', callData=[116, 101, 115, 116, 68, 97, 116, 97], value=1000, gasLimit=500000, gasPrice=20, blockNumber=123456}" ] \ No newline at end of file diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractCallIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractCallIntegrationTest.java index 192fcb17b..141bf9467 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractCallIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractCallIntegrationTest.java @@ -27,6 +27,7 @@ import com.hedera.hashgraph.sdk.FileDeleteTransaction; import com.hedera.hashgraph.sdk.Hbar; import com.hedera.hashgraph.sdk.MaxQueryPaymentExceededException; +import com.hedera.hashgraph.sdk.MirrorNodeContractEstimateGasQuery; import com.hedera.hashgraph.sdk.MirrorNodeContractQuery; import com.hedera.hashgraph.sdk.PrecheckStatusException; import com.hedera.hashgraph.sdk.Status; @@ -66,10 +67,10 @@ void canCallContractFunction() throws Exception { // Wait for mirror node to import data Thread.sleep(2000); - var gas = new MirrorNodeContractQuery() + var gas = new MirrorNodeContractEstimateGasQuery() .setContractId(contractId) .setFunction("getMessage") - .estimate(testEnv.client); + .execute(testEnv.client); var callQuery = new ContractCallQuery() .setContractId(contractId) @@ -256,10 +257,10 @@ void getCostBigMaxContractCallFunction() throws Exception { // Wait for mirror node to import data Thread.sleep(2000); - var gas = new MirrorNodeContractQuery() + long gas = new MirrorNodeContractEstimateGasQuery() .setContractId(contractId) .setFunction("getMessage") - .estimate(testEnv.client); + .execute(testEnv.client); var callQuery = new ContractCallQuery() .setContractId(contractId) @@ -311,10 +312,10 @@ void getCostSmallMaxContractCallFunction() throws Exception { // Wait for mirror node to import data Thread.sleep(2000); - var gas = new MirrorNodeContractQuery() + var gas = new MirrorNodeContractEstimateGasQuery() .setContractId(contractId) .setFunction("getMessage") - .estimate(testEnv.client); + .execute(testEnv.client); var callQuery = new ContractCallQuery() .setContractId(contractId) @@ -365,10 +366,10 @@ void getCostInsufficientTxFeeContractCallFunction() throws Exception { // Wait for mirror node to import data Thread.sleep(2000); - var gas = new MirrorNodeContractQuery() + var gas = new MirrorNodeContractEstimateGasQuery() .setContractId(contractId) .setFunction("getMessage") - .estimate(testEnv.client); + .execute(testEnv.client); var callQuery = new ContractCallQuery() .setContractId(contractId) diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java index 8ab6bc419..9f2b099bd 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractExecuteIntegrationTest.java @@ -28,6 +28,7 @@ import com.hedera.hashgraph.sdk.ContractFunctionParameters; import com.hedera.hashgraph.sdk.FileCreateTransaction; import com.hedera.hashgraph.sdk.FileDeleteTransaction; +import com.hedera.hashgraph.sdk.MirrorNodeContractEstimateGasQuery; import com.hedera.hashgraph.sdk.MirrorNodeContractQuery; import com.hedera.hashgraph.sdk.PrecheckStatusException; import com.hedera.hashgraph.sdk.ReceiptStatusException; @@ -64,10 +65,10 @@ void canExecuteContractMethods() throws Exception { // Wait for mirror node to import data Thread.sleep(2000); - var gas = new MirrorNodeContractQuery() + var gas = new MirrorNodeContractEstimateGasQuery() .setContractId(contractId) .setFunction("setMessage", new ContractFunctionParameters().addString("new message")) - .estimate(testEnv.client); + .execute(testEnv.client); var receipt = new ContractExecuteTransaction() .setContractId(contractId) .setGas(gas + 10000) diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractFunctionParametersIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractFunctionParametersIntegrationTest.java index e51b82c4d..3ef94829b 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractFunctionParametersIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/ContractFunctionParametersIntegrationTest.java @@ -32,6 +32,7 @@ import com.hedera.hashgraph.sdk.FileDeleteTransaction; import com.hedera.hashgraph.sdk.FileId; import com.hedera.hashgraph.sdk.Hbar; +import com.hedera.hashgraph.sdk.MirrorNodeContractEstimateGasQuery; import com.hedera.hashgraph.sdk.MirrorNodeContractQuery; import java.math.BigInteger; import java.util.Arrays; @@ -85,9 +86,9 @@ public void afterEach() throws InterruptedException { @Test @DisplayName("Can receive uint8 min value from contract call") void canCallContractFunctionUint8Min() throws Exception { - var gas = new MirrorNodeContractQuery().setContractId(contractId) + var gas = new MirrorNodeContractEstimateGasQuery().setContractId(contractId) .setFunction("returnUint8", new ContractFunctionParameters().addUint8((byte) 0x0)) - .estimate(testEnv.client); + .execute(testEnv.client); var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint8", new ContractFunctionParameters().addUint8((byte) 0x0)) @@ -102,9 +103,9 @@ void canCallContractFunctionUint8Max() throws Exception { int uint8Max = 255; byte uint8MaxByte = (byte) uint8Max; - var gas = new MirrorNodeContractQuery().setContractId(contractId) + var gas = new MirrorNodeContractEstimateGasQuery().setContractId(contractId) .setFunction("returnUint8", new ContractFunctionParameters().addUint8(uint8MaxByte)) - .estimate(testEnv.client); + .execute(testEnv.client); var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint8", new ContractFunctionParameters().addUint8(uint8MaxByte)) @@ -123,9 +124,9 @@ void canCallContractFunctionUint8Array() throws Exception { byte uint8MaxByte = (byte) uint8Max; byte[] uint8Array = {uint8MinByte, uint8MaxByte}; - var gas = new MirrorNodeContractQuery().setContractId(contractId) + var gas = new MirrorNodeContractEstimateGasQuery().setContractId(contractId) .setFunction("returnUint8Arr", new ContractFunctionParameters().addUint8Array(uint8Array)) - .estimate(testEnv.client); + .execute(testEnv.client); var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint8Arr", new ContractFunctionParameters().addUint8Array(uint8Array)) @@ -153,9 +154,9 @@ void canCallContractFunctionUint16Max() throws Exception { var uint16Max = "65535"; int uint16MaxInt = Integer.parseUnsignedInt(uint16Max); - var gas = new MirrorNodeContractQuery().setContractId(contractId) + var gas = new MirrorNodeContractEstimateGasQuery().setContractId(contractId) .setFunction("returnUint16", new ContractFunctionParameters().addUint16(uint16MaxInt)) - .estimate(testEnv.client); + .execute(testEnv.client); var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint16", new ContractFunctionParameters().addUint16(uint16MaxInt)) @@ -174,9 +175,9 @@ void canCallContractFunctionUint16Array() throws Exception { int uint16MaxInt = Integer.parseUnsignedInt(uint16Max); int[] uint16Array = {uint16MinInt, uint16MaxInt}; - var gas = new MirrorNodeContractQuery().setContractId(contractId) + var gas = new MirrorNodeContractEstimateGasQuery().setContractId(contractId) .setFunction("returnUint16Arr", new ContractFunctionParameters().addUint16Array(uint16Array)) - .estimate(testEnv.client); + .execute(testEnv.client); var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint16Arr", new ContractFunctionParameters().addUint16Array(uint16Array)) @@ -190,9 +191,9 @@ void canCallContractFunctionUint16Array() throws Exception { @Test @DisplayName("Can receive uint24 min value from contract call") void canCallContractFunctionUint24Min() throws Exception { - var gas = new MirrorNodeContractQuery().setContractId(contractId) + var gas = new MirrorNodeContractEstimateGasQuery().setContractId(contractId) .setFunction("returnUint24", new ContractFunctionParameters().addUint24(0)) - .estimate(testEnv.client); + .execute(testEnv.client); var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint24", new ContractFunctionParameters().addUint24(0)).setQueryPayment(new Hbar(10)) @@ -207,9 +208,9 @@ void canCallContractFunctionUint24Max() throws Exception { var uint24Max = "16777215"; int uint24MaxInt = Integer.parseUnsignedInt(uint24Max); - var gas = new MirrorNodeContractQuery().setContractId(contractId) + var gas = new MirrorNodeContractEstimateGasQuery().setContractId(contractId) .setFunction("returnUint24", new ContractFunctionParameters().addUint24(uint24MaxInt)) - .estimate(testEnv.client); + .execute(testEnv.client); var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint24", new ContractFunctionParameters().addUint24(uint24MaxInt)) @@ -228,9 +229,9 @@ void canCallContractFunctionUint24Array() throws Exception { int uint24MaxInt = Integer.parseUnsignedInt(uint24Max); int[] uint24Array = {uint24MinInt, uint24MaxInt}; - var gas = new MirrorNodeContractQuery().setContractId(contractId) + var gas = new MirrorNodeContractEstimateGasQuery().setContractId(contractId) .setFunction("returnUint24Arr", new ContractFunctionParameters().addUint24Array(uint24Array)) - .estimate(testEnv.client); + .execute(testEnv.client); var response = new ContractCallQuery().setContractId(contractId).setGas(gas) .setFunction("returnUint24Arr", new ContractFunctionParameters().addUint24Array(uint24Array)) diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java index a161d802e..d542df7e4 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java @@ -34,6 +34,8 @@ import com.hedera.hashgraph.sdk.FileCreateTransaction; import com.hedera.hashgraph.sdk.FileDeleteTransaction; import com.hedera.hashgraph.sdk.Hbar; +import com.hedera.hashgraph.sdk.MirrorNodeContractCallQuery; +import com.hedera.hashgraph.sdk.MirrorNodeContractEstimateGasQuery; import com.hedera.hashgraph.sdk.MirrorNodeContractQuery; import com.hedera.hashgraph.sdk.PrivateKey; import java.util.Objects; @@ -67,10 +69,10 @@ void canSimulateTransaction() throws Exception { // Wait for mirror node to import data Thread.sleep(2000); - var gas = new MirrorNodeContractQuery() + var gas = new MirrorNodeContractEstimateGasQuery() .setContractId(contractId) .setFunction("getOwner") - .estimate(testEnv.client); + .execute(testEnv.client); var result = new ContractCallQuery() .setContractId(contractId) @@ -79,17 +81,18 @@ void canSimulateTransaction() throws Exception { .setQueryPayment(new Hbar(1)) .execute(testEnv.client); - var simulationResult = new MirrorNodeContractQuery() + var simulationResult = new MirrorNodeContractCallQuery() + .setContractEvmAddress("asdf") .setContractId(contractId) .setFunction("getOwner") - .call(testEnv.client); + .execute(testEnv.client); assertThat(result.getAddress(0)).isEqualTo(simulationResult.substring(26)); - gas = new MirrorNodeContractQuery() + gas = new MirrorNodeContractEstimateGasQuery() .setContractId(contractId) .setFunction("addOwner", new ContractFunctionParameters().addAddress(ADDRESS)) - .estimate(testEnv.client); + .execute(testEnv.client); new ContractExecuteTransaction() .setContractId(contractId) @@ -98,10 +101,10 @@ void canSimulateTransaction() throws Exception { .execute(testEnv.client) .getReceipt(testEnv.client); - new MirrorNodeContractQuery() + new MirrorNodeContractCallQuery() .setContractId(contractId) .setFunction("addOwner", new ContractFunctionParameters().addAddress(ADDRESS)) - .call(testEnv.client); + .execute(testEnv.client); new ContractDeleteTransaction() .setTransferAccountId(testEnv.operatorId) @@ -123,17 +126,17 @@ void failsWhenContractIsNotDeployed() throws Exception { var contractId = new ContractId(1231456); assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { - new MirrorNodeContractQuery() + new MirrorNodeContractEstimateGasQuery() .setContractId(contractId) .setFunction("getOwner") - .estimate(testEnv.client); + .execute(testEnv.client); }).withMessageContaining("Received non-200 response from Mirror Node"); assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { - new MirrorNodeContractQuery() + new MirrorNodeContractCallQuery() .setContractId(contractId) .setFunction("getOwner") - .call(testEnv.client); + .execute(testEnv.client); }).withMessageContaining("Received non-200 response from Mirror Node"); } } @@ -161,21 +164,20 @@ void failsWhenGasLimitIsLow() throws Exception { Thread.sleep(2000); assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { - new MirrorNodeContractQuery() + new MirrorNodeContractEstimateGasQuery() .setContractId(contractId) .setGasLimit(100) .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(ADDRESS)) - .estimate(testEnv.client); + .execute(testEnv.client); }).withMessageContaining("Received non-200 response from Mirror Node"); assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { - new MirrorNodeContractQuery() + new MirrorNodeContractCallQuery() .setContractId(contractId) .setGasLimit(100) .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(ADDRESS)) - .call(testEnv.client); + .execute(testEnv.client); }).withMessageContaining("Received non-200 response from Mirror Node"); - } } @@ -202,17 +204,17 @@ void failsWhenSenderIsNotSet() throws Exception { Thread.sleep(2000); assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { - new MirrorNodeContractQuery() + new MirrorNodeContractEstimateGasQuery() .setContractId(contractId) .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(ADDRESS)) - .estimate(testEnv.client); + .execute(testEnv.client); }).withMessageContaining("Received non-200 response from Mirror Node"); assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> { - new MirrorNodeContractQuery() + new MirrorNodeContractCallQuery() .setContractId(contractId) .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(ADDRESS)) - .call(testEnv.client); + .execute(testEnv.client); }).withMessageContaining("Received non-200 response from Mirror Node"); } @@ -249,19 +251,19 @@ void canSimulateWithSenderSet() throws Exception { var receiverEvmAddress = getEvmAddressFromMirrorNodeAsync(testEnv.client, receiverAccountId.num).get() .toString(); - var owner = new MirrorNodeContractQuery() + var owner = new MirrorNodeContractCallQuery() .setContractId(contractId) .setFunction("getOwner") - .call(testEnv.client) + .execute(testEnv.client) .substring(26); - var gas = new MirrorNodeContractQuery() + var gas = new MirrorNodeContractEstimateGasQuery() .setContractId(contractId) .setGasLimit(1_000_000) .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(receiverEvmAddress)) .setSenderEvmAddress(owner) .setValue(123) - .estimate(testEnv.client); + .execute(testEnv.client); new ContractExecuteTransaction() .setContractId(contractId) @@ -271,13 +273,13 @@ void canSimulateWithSenderSet() throws Exception { .execute(testEnv.client) .getReceipt(testEnv.client); - new MirrorNodeContractQuery() + new MirrorNodeContractCallQuery() .setContractId(contractId) .setGasLimit(1_000_000) .setFunction("addOwnerAndTransfer", new ContractFunctionParameters().addAddress(receiverEvmAddress)) .setSenderEvmAddress(owner) .setValue(123) - .call(testEnv.client); + .execute(testEnv.client); } } } From 5f67c7a15477f897dfb748b729000536c4611ef1 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 27 Nov 2024 12:44:34 +0200 Subject: [PATCH 12/16] chore: fix tests Signed-off-by: Ivan Ivanov --- .../test/integration/MirrorNodeContractQueryIntegrationTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java index d542df7e4..1b24e271c 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/MirrorNodeContractQueryIntegrationTest.java @@ -82,7 +82,6 @@ void canSimulateTransaction() throws Exception { .execute(testEnv.client); var simulationResult = new MirrorNodeContractCallQuery() - .setContractEvmAddress("asdf") .setContractId(contractId) .setFunction("getOwner") .execute(testEnv.client); From d6bd034c2bcabccefb018cc0b9551f59cb565880 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Mon, 2 Dec 2024 17:51:57 +0200 Subject: [PATCH 13/16] chore: fix docs Signed-off-by: Ivan Ivanov --- .../sdk/examples/MirrorNodeContractQueriesExample.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java index 0180cc224..e7b44074c 100644 --- a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java @@ -76,7 +76,7 @@ public class MirrorNodeContractQueriesExample { private static final String SMART_CONTRACT_BYTECODE = "608060405234801561001057600080fd5b506040516104d73803806104d78339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b506040525050600080546001600160a01b0319163317905550805161010890600190602084019061010f565b50506101aa565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061015057805160ff191683800117855561017d565b8280016001018555821561017d579182015b8281111561017d578251825591602001919060010190610162565b5061018992915061018d565b5090565b6101a791905b808211156101895760008155600101610193565b90565b61031e806101b96000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063368b87721461004657806341c0e1b5146100ee578063ce6d41de146100f6575b600080fd5b6100ec6004803603602081101561005c57600080fd5b81019060208101813564010000000081111561007757600080fd5b82018360208201111561008957600080fd5b803590602001918460018302840111640100000000831117156100ab57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610173945050505050565b005b6100ec6101a2565b6100fe6101ba565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610138578181015183820152602001610120565b50505050905090810190601f1680156101655780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6000546001600160a01b0316331461018a5761019f565b805161019d906001906020840190610250565b505b50565b6000546001600160a01b03163314156101b85733ff5b565b60018054604080516020601f600260001961010087891615020190951694909404938401819004810282018101909252828152606093909290918301828280156102455780601f1061021a57610100808354040283529160200191610245565b820191906000526020600020905b81548152906001019060200180831161022857829003601f168201915b505050505090505b90565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061029157805160ff19168380011785556102be565b828001600101855582156102be579182015b828111156102be5782518255916020019190600101906102a3565b506102ca9291506102ce565b5090565b61024d91905b808211156102ca57600081556001016102d456fea264697066735822122084964d4c3f6bc912a9d20e14e449721012d625aa3c8a12de41ae5519752fc89064736f6c63430006000033"; public static void main(String[] args) throws Exception { - System.out.println("Mirror node contract query Example Start!"); + System.out.println("Mirror Node contract queries Example Start!"); /* * Step 0: @@ -150,7 +150,7 @@ public static void main(String[] args) throws Exception { .execute(client); /* - * Step 5: + * Step 6: * Simulate the transaction for free, using the mirror node */ var simulationResult = new MirrorNodeContractCallQuery() From caa584804a6556edae7fa4eafd3406dd95cb2a3d Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 3 Dec 2024 14:14:26 +0200 Subject: [PATCH 14/16] chore: improve example Signed-off-by: Ivan Ivanov --- .../MirrorNodeContractQueriesExample.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java index e7b44074c..c601ca056 100644 --- a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java @@ -131,6 +131,9 @@ public static void main(String[] args) throws Exception { */ var gas = new MirrorNodeContractEstimateGasQuery() .setContractId(contractId) + .setSender(client.getOperatorAccountId()) + .setGasLimit(30_000) + .setGasPrice(1234) .setFunction("getMessage") .execute(client); @@ -155,22 +158,36 @@ public static void main(String[] args) throws Exception { */ var simulationResult = new MirrorNodeContractCallQuery() .setContractId(contractId) + .setSender(client.getOperatorAccountId()) + .setGasLimit(30_000) + .setBlockNumber(10000) + .setGasPrice(1234) .setFunction("getMessage") .execute(client); - System.out.println("Simulation result: " + decodeABIHex(simulationResult.substring(2))); + System.out.println("Simulation result: " + decodeABIHexString(simulationResult)); System.out.println("Contract call result: " + result.getString(0)); } - private static String decodeABIHex(String hex) { + private static String decodeABIHexString(String hex) { + // Trim 0x at the beginning + if (hex.startsWith("0x")) { + hex = hex.substring(2); + } + + // Extract the length of the data by parsing the substring from position 64 to 128 as a hexadecimal integer + // This section represents the length of the dynamic data, specifically the number of bytes in the string or array int length = Integer.parseInt(hex.substring(64, 128), 16); + // Using the extracted length, the code calculates the substring containing the actual data starting from position 128. String hexStringData = hex.substring(128, 128 + length * 2); byte[] bytes = new byte[length]; + // Iterate through the extracted hex data, two characters at a time, converting each pair to a byte and storing it in a byte array. for (int i = 0; i < length; i++) { bytes[i] = (byte) Integer.parseInt(hexStringData.substring(i * 2, i * 2 + 2), 16); } + // Convert to UTF 8 return new String(bytes, StandardCharsets.UTF_8); } } From 7a5c88b7dd63a2907c59814ce9eabb6ec925e438 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 10 Dec 2024 21:53:56 +0200 Subject: [PATCH 15/16] chore: address PR feedback Signed-off-by: Ivan Ivanov --- examples/build.gradle.kts | 5 ++ .../MirrorNodeContractQueriesExample.java | 74 +++++++++---------- examples/src/main/java/module-info.java | 1 + 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index e8a43b94e..53b542199 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -27,6 +27,11 @@ mainModuleInfo { runtimeOnly("org.slf4j.simple") } +dependencies { + implementation("org.bouncycastle:bcpkix-jdk18on:1.78.1") + implementation("org.bouncycastle:bcprov-jdk18on:1.78.1") +} + dependencies.constraints { implementation("com.hedera.hashgraph:sdk:2.45.0") implementation("com.hedera.hashgraph:sdk-full:2.45.0") diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java index c601ca056..a27c41d4e 100644 --- a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java @@ -25,21 +25,16 @@ import com.hedera.hashgraph.sdk.ContractCallQuery; import com.hedera.hashgraph.sdk.ContractCreateTransaction; import com.hedera.hashgraph.sdk.ContractFunctionParameters; -import com.hedera.hashgraph.sdk.FileCreateTransaction; -import com.hedera.hashgraph.sdk.FileId; import com.hedera.hashgraph.sdk.Hbar; import com.hedera.hashgraph.sdk.MirrorNodeContractCallQuery; import com.hedera.hashgraph.sdk.MirrorNodeContractEstimateGasQuery; -import com.hedera.hashgraph.sdk.MirrorNodeContractQuery; import com.hedera.hashgraph.sdk.PrivateKey; -import com.hedera.hashgraph.sdk.PublicKey; -import com.hedera.hashgraph.sdk.TransactionReceipt; -import com.hedera.hashgraph.sdk.TransactionResponse; import com.hedera.hashgraph.sdk.logger.LogLevel; import com.hedera.hashgraph.sdk.logger.Logger; import io.github.cdimascio.dotenv.Dotenv; import java.nio.charset.StandardCharsets; import java.util.Objects; +import org.bouncycastle.util.encoders.Hex; public class MirrorNodeContractQueriesExample { /* @@ -48,32 +43,33 @@ public class MirrorNodeContractQueriesExample { */ /** - * Operator's account ID. - * Used to sign and pay for operations on Hedera. + * Operator's account ID. Used to sign and pay for operations on Hedera. */ - private static final AccountId OPERATOR_ID = AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID"))); + private static final AccountId OPERATOR_ID = AccountId.fromString( + Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID"))); /** * Operator's private key. */ - private static final PrivateKey OPERATOR_KEY = PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY"))); + private static final PrivateKey OPERATOR_KEY = PrivateKey.fromString( + Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY"))); /** - * HEDERA_NETWORK defaults to testnet if not specified in dotenv file. - * Network can be: localhost, testnet, previewnet or mainnet. + * HEDERA_NETWORK defaults to testnet if not specified in dotenv file. Network can be: localhost, testnet, + * previewnet or mainnet. */ private static final String HEDERA_NETWORK = Dotenv.load().get("HEDERA_NETWORK", "testnet"); /** - * SDK_LOG_LEVEL defaults to SILENT if not specified in dotenv file. - * Log levels can be: TRACE, DEBUG, INFO, WARN, ERROR, SILENT. + * SDK_LOG_LEVEL defaults to SILENT if not specified in dotenv file. Log levels can be: TRACE, DEBUG, INFO, WARN, + * ERROR, SILENT. *

- * Important pre-requisite: set simple logger log level to same level as the SDK_LOG_LEVEL, - * for example via VM options: -Dorg.slf4j.simpleLogger.log.com.hedera.hashgraph=trace + * Important pre-requisite: set simple logger log level to same level as the SDK_LOG_LEVEL, for example via VM + * options: -Dorg.slf4j.simpleLogger.log.com.hedera.hashgraph=trace */ private static final String SDK_LOG_LEVEL = Dotenv.load().get("SDK_LOG_LEVEL", "SILENT"); - private static final String SMART_CONTRACT_BYTECODE = "608060405234801561001057600080fd5b506040516104d73803806104d78339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b506040525050600080546001600160a01b0319163317905550805161010890600190602084019061010f565b50506101aa565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061015057805160ff191683800117855561017d565b8280016001018555821561017d579182015b8281111561017d578251825591602001919060010190610162565b5061018992915061018d565b5090565b6101a791905b808211156101895760008155600101610193565b90565b61031e806101b96000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063368b87721461004657806341c0e1b5146100ee578063ce6d41de146100f6575b600080fd5b6100ec6004803603602081101561005c57600080fd5b81019060208101813564010000000081111561007757600080fd5b82018360208201111561008957600080fd5b803590602001918460018302840111640100000000831117156100ab57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610173945050505050565b005b6100ec6101a2565b6100fe6101ba565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610138578181015183820152602001610120565b50505050905090810190601f1680156101655780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6000546001600160a01b0316331461018a5761019f565b805161019d906001906020840190610250565b505b50565b6000546001600160a01b03163314156101b85733ff5b565b60018054604080516020601f600260001961010087891615020190951694909404938401819004810282018101909252828152606093909290918301828280156102455780601f1061021a57610100808354040283529160200191610245565b820191906000526020600020905b81548152906001019060200180831161022857829003601f168201915b505050505090505b90565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061029157805160ff19168380011785556102be565b828001600101855582156102be579182015b828111156102be5782518255916020019190600101906102a3565b506102ca9291506102ce565b5090565b61024d91905b808211156102ca57600081556001016102d456fea264697066735822122084964d4c3f6bc912a9d20e14e449721012d625aa3c8a12de41ae5519752fc89064736f6c63430006000033"; + private static final String SMART_CONTRACT_BYTECODE = "6080604052348015600e575f80fd5b5061014e8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063ce6d41de1461002d575b5f80fd5b61003561004b565b60405161004291906100f8565b60405180910390f35b60606040518060400160405280600581526020017f68656c6c6f000000000000000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6100ca82610088565b6100d48185610092565b93506100e48185602086016100a2565b6100ed816100b0565b840191505092915050565b5f6020820190508181035f83015261011081846100c0565b90509291505056fea264697066735822122073f43039f9146f50acc1b1f6e211c2588bf825fa9fef2178482dd5c63009edcc64736f6c634300081a0033"; public static void main(String[] args) throws Exception { System.out.println("Mirror Node contract queries Example Start!"); @@ -88,36 +84,18 @@ public static void main(String[] args) throws Exception { // Attach logger to the SDK Client. client.setLogger(new Logger(LogLevel.valueOf(SDK_LOG_LEVEL))); - var operatorPublicKey = OPERATOR_KEY.getPublicKey(); - /* * Step 1: * Create the contract */ - System.out.println("Creating new file..."); - TransactionResponse fileCreateTxResponse = new FileCreateTransaction() - // Use the same key as the operator to "own" this file. - .setKeys(operatorPublicKey) - .setContents(SMART_CONTRACT_BYTECODE) - // The default max fee of 1 Hbar is not enough to create a file (starts around ~1.1 Hbar). - .setMaxTransactionFee(Hbar.from(2)) - .execute(client); - - TransactionReceipt fileCreateTxReceipt = fileCreateTxResponse.getReceipt(client); - FileId newFileId = fileCreateTxReceipt.fileId; - - Objects.requireNonNull(newFileId); - System.out.println("Created new file with ID: " + newFileId); - var response = new ContractCreateTransaction() - .setAdminKey(client.getOperatorPublicKey()) - .setGas(200000) - .setConstructorParameters(new ContractFunctionParameters().addString("Hello from Hedera.")) - .setBytecodeFileId(newFileId) + .setGas(200_000) + .setBytecode(Hex.decode(SMART_CONTRACT_BYTECODE)) .setContractMemo("Simple contract with string field") .execute(client); var contractId = Objects.requireNonNull(response.getReceipt(client).contractId); + System.out.println("Created new contract with ID: " + contractId); /* * Step 3: @@ -165,9 +143,27 @@ public static void main(String[] args) throws Exception { .setFunction("getMessage") .execute(client); - System.out.println("Simulation result: " + decodeABIHexString(simulationResult)); + // Decode the result since it's coming in ABI Hex format from the Mirror Node + var decodedResult = decodeABIHexString(simulationResult); + System.out.println("Simulation result: " + decodedResult); System.out.println("Contract call result: " + result.getString(0)); } + + /** + * Decodes a hex-encoded ABI (Application Binary Interface) string into a UTF-8 string. + *

+ * The function assumes the input follows the ABI encoding standard for dynamic data. Specifically, it parses the + * length of the dynamic data and extracts the corresponding substring. + *

+ * The structure of the input hex string is as follows: - The first 64 characters represent metadata, such as + * offsets and other header information. - Characters from index 64 to 128 encode the length of the dynamic data in + * bytes. - Characters from index 128 onward represent the actual dynamic data. + *

+ * This method removes the `0x` prefix if present, parses the length, and decodes the dynamic data into UTF-8. + * + * @param hex the hex string to decode, which follows the ABI encoding standard + * @return the decoded UTF-8 string + */ private static String decodeABIHexString(String hex) { // Trim 0x at the beginning if (hex.startsWith("0x")) { diff --git a/examples/src/main/java/module-info.java b/examples/src/main/java/module-info.java index dba574aac..b94bba8ae 100644 --- a/examples/src/main/java/module-info.java +++ b/examples/src/main/java/module-info.java @@ -27,4 +27,5 @@ requires static java.annotation; requires com.google.protobuf; + requires org.bouncycastle.provider; } From 708ff81557c7c9b55eeabaabafa8294543a38b16 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 11 Dec 2024 11:26:52 +0200 Subject: [PATCH 16/16] chore: refactor Signed-off-by: Ivan Ivanov --- .../sdk/examples/MirrorNodeContractQueriesExample.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java index a27c41d4e..b90eab231 100644 --- a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/MirrorNodeContractQueriesExample.java @@ -24,7 +24,6 @@ import com.hedera.hashgraph.sdk.Client; import com.hedera.hashgraph.sdk.ContractCallQuery; import com.hedera.hashgraph.sdk.ContractCreateTransaction; -import com.hedera.hashgraph.sdk.ContractFunctionParameters; import com.hedera.hashgraph.sdk.Hbar; import com.hedera.hashgraph.sdk.MirrorNodeContractCallQuery; import com.hedera.hashgraph.sdk.MirrorNodeContractEstimateGasQuery; @@ -69,11 +68,10 @@ public class MirrorNodeContractQueriesExample { */ private static final String SDK_LOG_LEVEL = Dotenv.load().get("SDK_LOG_LEVEL", "SILENT"); - private static final String SMART_CONTRACT_BYTECODE = "6080604052348015600e575f80fd5b5061014e8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063ce6d41de1461002d575b5f80fd5b61003561004b565b60405161004291906100f8565b60405180910390f35b60606040518060400160405280600581526020017f68656c6c6f000000000000000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6100ca82610088565b6100d48185610092565b93506100e48185602086016100a2565b6100ed816100b0565b840191505092915050565b5f6020820190508181035f83015261011081846100c0565b90509291505056fea264697066735822122073f43039f9146f50acc1b1f6e211c2588bf825fa9fef2178482dd5c63009edcc64736f6c634300081a0033"; + private static final String SMART_CONTRACT_BYTECODE = "60806040526040518060400160405280600581526020017f68656c6c6f0000000000000000000000000000000000000000000000000000008152505f90816100479190610293565b50348015610053575f80fd5b50610362565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806100d457607f821691505b6020821081036100e7576100e6610090565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026101497fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8261010e565b610153868361010e565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f61019761019261018d8461016b565b610174565b61016b565b9050919050565b5f819050919050565b6101b08361017d565b6101c46101bc8261019e565b84845461011a565b825550505050565b5f90565b6101d86101cc565b6101e38184846101a7565b505050565b5b81811015610206576101fb5f826101d0565b6001810190506101e9565b5050565b601f82111561024b5761021c816100ed565b610225846100ff565b81016020851015610234578190505b610248610240856100ff565b8301826101e8565b50505b505050565b5f82821c905092915050565b5f61026b5f1984600802610250565b1980831691505092915050565b5f610283838361025c565b9150826002028217905092915050565b61029c82610059565b67ffffffffffffffff8111156102b5576102b4610063565b5b6102bf82546100bd565b6102ca82828561020a565b5f60209050601f8311600181146102fb575f84156102e9578287015190505b6102f38582610278565b86555061035a565b601f198416610309866100ed565b5f5b828110156103305784890151825560018201915060208501945060208101905061030b565b8683101561034d5784890151610349601f89168261025c565b8355505b6001600288020188555050505b505050505050565b6102178061036f5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063ce6d41de1461002d575b5f80fd5b61003561004b565b6040516100429190610164565b60405180910390f35b60605f8054610059906101b1565b80601f0160208091040260200160405190810160405280929190818152602001828054610085906101b1565b80156100d05780601f106100a7576101008083540402835291602001916100d0565b820191905f5260205f20905b8154815290600101906020018083116100b357829003601f168201915b5050505050905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156101115780820151818401526020810190506100f6565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610136826100da565b61014081856100e4565b93506101508185602086016100f4565b6101598161011c565b840191505092915050565b5f6020820190508181035f83015261017c818461012c565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806101c857607f821691505b6020821081036101db576101da610184565b5b5091905056fea26469706673582212202a86c27939bfab6d4a2c61ebbf096d8424e17e22dfdd42320f6e2654863581e964736f6c634300081a0033"; public static void main(String[] args) throws Exception { System.out.println("Mirror Node contract queries Example Start!"); - /* * Step 0: * Create and configure the SDK Client. @@ -101,7 +99,7 @@ public static void main(String[] args) throws Exception { * Step 3: * Wait for mirror node to import data */ - Thread.sleep(4000); + Thread.sleep(5000); /* * Step 4: