diff --git a/.circleci/config.yml b/.circleci/config.yml index 511cac25f..968f1a0d7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,7 +24,7 @@ jobs: - run: mvn test integration-test: machine: - image: "ubuntu-2204:2022.04.2" + image: default resource_class: medium steps: - checkout diff --git a/examples/pom.xml b/examples/pom.xml index 3b06f5d04..11ec6c410 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -95,7 +95,7 @@ com.algorand algosdk - 2.0.0 + 2.5.0 @@ -114,6 +114,14 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + diff --git a/examples/src/main/java/com/algorand/examples/ExampleUtils.java b/examples/src/main/java/com/algorand/examples/ExampleUtils.java index 07fc8c1e3..79cd594c9 100644 --- a/examples/src/main/java/com/algorand/examples/ExampleUtils.java +++ b/examples/src/main/java/com/algorand/examples/ExampleUtils.java @@ -36,10 +36,18 @@ public class ExampleUtils { private static int indexer_port = 8980; private static String indexer_token = "a".repeat(64); + private static String testnet_algod_host = "https://testnet-api.algonode.cloud"; + private static int testnet_algod_port = 443; + private static String testnet_algod_token = ""; + public static AlgodClient getAlgodClient() { return new AlgodClient(algod_host, algod_port, algod_token); } + public static AlgodClient getAlgodTestnetClient() { + return new AlgodClient(testnet_algod_host, testnet_algod_port, testnet_algod_token); + } + public static IndexerClient getIndexerClient() { return new IndexerClient(indexer_host, indexer_port, indexer_token); } diff --git a/src/main/java/com/algorand/algosdk/transaction/AtomicTransactionComposer.java b/src/main/java/com/algorand/algosdk/transaction/AtomicTransactionComposer.java index 0ea9e3aee..039213f72 100644 --- a/src/main/java/com/algorand/algosdk/transaction/AtomicTransactionComposer.java +++ b/src/main/java/com/algorand/algosdk/transaction/AtomicTransactionComposer.java @@ -5,20 +5,18 @@ import com.algorand.algosdk.crypto.Digest; import com.algorand.algosdk.util.Encoder; import com.algorand.algosdk.v2.client.Utils; +import com.algorand.algosdk.v2.client.algod.PendingTransactionInformation; +import com.algorand.algosdk.v2.client.algod.SimulateTransaction; import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.common.Client; import com.algorand.algosdk.v2.client.common.Response; -import com.algorand.algosdk.v2.client.model.PendingTransactionResponse; -import com.algorand.algosdk.v2.client.model.PostTransactionsResponse; +import com.algorand.algosdk.v2.client.model.*; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.ArrayUtils; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.HashMap; +import java.util.*; public class AtomicTransactionComposer { public enum Status { @@ -126,7 +124,7 @@ public List buildGroup() throws IOException { if (this.status.compareTo(Status.BUILT) >= 0) return this.transactionList; - if (this.transactionList.size() == 0) + if (this.transactionList.isEmpty()) throw new IllegalArgumentException("should not build transaction group with 0 transaction in composer"); else if (this.transactionList.size() > 1) { List groupTxns = new ArrayList<>(); @@ -228,6 +226,57 @@ public List submit(AlgodClient client) throws Exception { return this.getTxIDs(); } + /** + * Simulate simulates the transaction group against the network. + *

+ * The composer's status must be SUBMITTED or lower before calling this method. Simulation will not + * advance the status of the composer beyond SIGNED. + *

+ * The `request` argument can be used to customize the characteristics of the simulation. + *

+ * Returns a models.SimulateResponse and an ABIResult for each method call in this group. + */ + public SimulateResult simulate(AlgodClient client, SimulateRequest request) throws Exception { + if (this.status.ordinal() > Status.SUBMITTED.ordinal()) { + throw new Exception("Status must be SUBMITTED or lower in order to call Simulate()"); + } + + List stxs = gatherSignatures(); + if (stxs == null) { + throw new Exception("Error gathering signatures"); + } + + SimulateRequestTransactionGroup txnGroups = new SimulateRequestTransactionGroup(); + txnGroups.txns = stxs; + request.txnGroups = new ArrayList<>(); + request.txnGroups.add(txnGroups); + + SimulateTransaction st = new SimulateTransaction(client); + SimulateResponse simulateResponse = st.request(request).execute().body(); + + if (simulateResponse == null) { + throw new Exception("Error in simulation response"); + } + + List methodResults = new ArrayList<>(); + for (int i = 0; i < stxs.size(); i++) { + SignedTransaction stx = stxs.get(i); + PendingTransactionResponse pendingTransactionResponse = simulateResponse.txnGroups.get(0).txnResults.get(i).txnResult; + + if (!this.methodMap.containsKey(i)) + continue; + + ReturnValue returnValue = parseMethodResponse(this.methodMap.get(i), stx, pendingTransactionResponse); + methodResults.add(returnValue); + } + + SimulateResult result = new SimulateResult(); + result.setMethodResults(methodResults); + result.setSimulateResponse(simulateResponse); + + return result; + } + /** * Send the transaction group to the network and wait until it's committed to a block. An error * will be thrown if submission or execution fails. @@ -326,6 +375,78 @@ public ExecuteResult execute(AlgodClient client, int waitRounds) throws Exceptio return new ExecuteResult(txInfo.confirmedRound, this.getTxIDs(), retList); } + public static class SimulateResult { + private SimulateResponse simulateResponse; + private List methodResults; + + public SimulateResponse getSimulateResponse() { + return simulateResponse; + } + + public void setSimulateResponse(SimulateResponse simulateResponse) { + this.simulateResponse = simulateResponse; + } + + public List getMethodResults() { + return methodResults; + } + + public void setMethodResults(List methodResults) { + this.methodResults = methodResults; + } + } + + /** + * Parses a single ABI Method transaction log into a ABI result object. + * + * @param method + * @param stx + * @param pendingTransactionResponse + * @return An ReturnValue object + */ + public ReturnValue parseMethodResponse(Method method, SignedTransaction stx, PendingTransactionResponse pendingTransactionResponse) { + ReturnValue returnValue = new ReturnValue( + stx.transactionID, + new byte[0], + null, + method, + null, + pendingTransactionResponse + ); + try { + if (!method.returns.type.equals(Method.Returns.VoidRetType)) { + List logs = pendingTransactionResponse.logs; + if (logs == null || logs.isEmpty()) { + throw new Exception("App call transaction did not log a return value"); + } + + byte[] lastLog = logs.get(logs.size() - 1); + if (lastLog.length < 4 || !hasPrefix(lastLog, ABI_RET_HASH)) { + throw new Exception("App call transaction did not log a return value"); + } + + returnValue.rawValue = Arrays.copyOfRange(lastLog, ABI_RET_HASH.length, lastLog.length); + returnValue.value = method.returns.parsedType.decode(returnValue.rawValue); + } + } catch (Exception e) { + returnValue.parseError = e; + } + + return returnValue; + } + + private static boolean hasPrefix(byte[] array, byte[] prefix) { + if (array.length < prefix.length) { + return false; + } + for (int i = 0; i < prefix.length; i++) { + if (array[i] != prefix[i]) { + return false; + } + } + return true; + } + private static boolean checkLogRet(byte[] logLine) { if (logLine.length < ABI_RET_HASH.length) return false; diff --git a/src/main/java/com/algorand/algosdk/transaction/EmptyTransactionSigner.java b/src/main/java/com/algorand/algosdk/transaction/EmptyTransactionSigner.java new file mode 100644 index 000000000..597807990 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/transaction/EmptyTransactionSigner.java @@ -0,0 +1,60 @@ +package com.algorand.algosdk.transaction; + +import com.algorand.algosdk.crypto.Address; +import java.security.NoSuchAlgorithmException; +import java.util.Objects; + +public class EmptyTransactionSigner implements TxnSigner { + + private String authAddr; + /** + * EmptyTransactionSigner is a TransactionSigner that produces signed transaction objects without + * signatures. This is useful for simulating transactions, but it won't work for actual submission. + */ + public EmptyTransactionSigner(String authAddr) { + super(); + this.authAddr = authAddr; + } + + /** + * SignTransactions returns SignedTxn bytes but does not sign them. + * + * @param txnGroup The group of transactions to be signed. + * @param indicesToSign The indexes of the transactions to sign. + * @return A list of signed transaction bytes. + */ + @Override + public SignedTransaction[] signTxnGroup(Transaction[] txnGroup, int[] indicesToSign) throws NoSuchAlgorithmException { + SignedTransaction[] stxs = new SignedTransaction[indicesToSign.length]; + + for (int pos : indicesToSign) { + SignedTransaction stx = new SignedTransaction(txnGroup[pos]); + + try { + if (authAddr != null) { + Address address = new Address(authAddr); + stx.authAddr(address.getBytes()); + } + } catch (IllegalArgumentException ignored) { } + + stxs[pos] = stx; + } + return stxs; + } + + /** + * Equals returns true if the other TransactionSigner equals this one. + * + * @param other The other TransactionSigner to compare. + * @return true if equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + return other instanceof EmptyTransactionSigner; + } + + @Override + public int hashCode() { + return Objects.hash(EmptyTransactionSigner.class); + } +} diff --git a/src/main/java/com/algorand/algosdk/transaction/SignedTransaction.java b/src/main/java/com/algorand/algosdk/transaction/SignedTransaction.java index bed22aed6..6a374d850 100644 --- a/src/main/java/com/algorand/algosdk/transaction/SignedTransaction.java +++ b/src/main/java/com/algorand/algosdk/transaction/SignedTransaction.java @@ -71,6 +71,10 @@ public SignedTransaction(Transaction tx, LogicsigSignature lSig, String txId) { private SignedTransaction() { } + public SignedTransaction(Transaction transaction) { + this.tx = transaction; + } + public SignedTransaction authAddr(Address authAddr) { this.authAddr = authAddr; return this; diff --git a/src/test/integration.tags b/src/test/integration.tags index f8b49f9fc..d73c5c749 100644 --- a/src/test/integration.tags +++ b/src/test/integration.tags @@ -13,3 +13,4 @@ @rekey_v1 @send @send.keyregtxn +@simulate diff --git a/src/test/java/com/algorand/algosdk/integration/AtomicTxnComposer.java b/src/test/java/com/algorand/algosdk/integration/AtomicTxnComposer.java index fc4cf71be..93a269976 100644 --- a/src/test/java/com/algorand/algosdk/integration/AtomicTxnComposer.java +++ b/src/test/java/com/algorand/algosdk/integration/AtomicTxnComposer.java @@ -8,11 +8,21 @@ import com.algorand.algosdk.crypto.TEALProgram; import com.algorand.algosdk.cucumber.shared.TransactionSteps; import com.algorand.algosdk.logic.StateSchema; -import com.algorand.algosdk.transaction.*; -import com.algorand.algosdk.util.*; +import com.algorand.algosdk.transaction.AtomicTransactionComposer; +import com.algorand.algosdk.transaction.EmptyTransactionSigner; +import com.algorand.algosdk.transaction.MethodCallParams; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.transaction.TransactionWithSigner; +import com.algorand.algosdk.transaction.TxnSigner; +import com.algorand.algosdk.util.Digester; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.util.GenericObjToArray; +import com.algorand.algosdk.util.ResourceUtils; +import com.algorand.algosdk.util.SplitAndProcessMethodArgs; import com.algorand.algosdk.v2.client.model.PendingTransactionResponse; +import com.algorand.algosdk.v2.client.model.SimulateRequest; import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; - +import io.cucumber.java.en.And; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; @@ -27,7 +37,9 @@ import java.util.Map; import java.util.regex.Pattern; +import static com.algorand.algosdk.util.ConversionUtils.convertBoxes; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNull; public class AtomicTxnComposer { @@ -43,6 +55,7 @@ public class AtomicTxnComposer { SplitAndProcessMethodArgs abiArgProcessor; Long appID; String nonce; + AtomicTransactionComposer.SimulateResult simulateResult; public AtomicTxnComposer(Stepdefs stepdefs, Applications apps, TransactionSteps steps) { base = stepdefs; @@ -425,6 +438,28 @@ public void i_add_a_method_call_with_the_transient_account_the_current_applicati atc.addMethodCall(optionBuild); } + @And("I add a method call with the transient account, the current application, suggested params, on complete {string}, current transaction signer, current method arguments, boxes {string}.") + public void iAddAMethodCallWithTheTransientAccountTheCurrentApplicationSuggestedParamsOnCompleteCurrentTransactionSignerCurrentMethodArgumentsBoxes(String onCompleteString, String boxesString) throws Exception { + Address senderAddress = applications.transientAccount.transientAccount.getAddress(); + + optionBuilder + .onComplete(Transaction.OnCompletion.String(onCompleteString)) + .sender(senderAddress) + .signer(transSigner) + .applicationId(applications.appId) + .method(method) + .note(("I should be unique thanks to this nonce: " + nonce).getBytes(StandardCharsets.UTF_8)) + .firstValid(transSteps.fv) + .lastValid(transSteps.lv) + .genesisHash(transSteps.genesisHash) + .genesisID(transSteps.genesisID) + .fee(transSteps.fee) + .flatFee(transSteps.flatFee) + .boxReferences(convertBoxes(boxesString)); + MethodCallParams optionBuild = optionBuilder.build(); + atc.addMethodCall(optionBuild); + } + @When("I build the transaction group with the composer. If there is an error it is {string}.") public void i_build_the_transaction_group_with_the_composer_if_there_is_an_error_it_is(String errStr) { String inferredError = ""; @@ -437,4 +472,92 @@ public void i_build_the_transaction_group_with_the_composer_if_there_is_an_error } assertThat(inferredError).isEqualTo(errStr); } + + @And("I simulate the current transaction group with the composer") + public void iSimulateTheCurrentTransactionGroupWithTheComposer() throws Exception { + if (base.simulateRequest == null) { + base.simulateRequest = new SimulateRequest(); + } + + simulateResult = atc.simulate(base.aclv2, base.simulateRequest); + execRes = new AtomicTransactionComposer.ExecuteResult(0L, null, simulateResult.getMethodResults()); + } + + @Then("the simulation should succeed without any failure message") + public void theSimulationShouldSucceedWithoutAnyFailureMessage() { + if (simulateResult != null) { + assertNull(simulateResult.getSimulateResponse().txnGroups.get(0).failureMessage); + } else { + assertNull(base.simulateResponse.body().txnGroups.get(0).failureMessage); + } + } + + @And("the simulation should report a failure at group {string}, path {string} with message {string}") + public void theSimulationShouldReportAFailureAtGroupPathWithMessage(String txnGroupIndex, String failAt, String expectedFailureMsg) throws Exception { + int groupIndex; + try { + groupIndex = Integer.parseInt(txnGroupIndex); + } catch (NumberFormatException e) { + throw new Exception("Invalid transaction group index", e); + } + + String[] path = failAt.split(","); + List expectedPath = new ArrayList<>(); + for (String pathStr : path) { + try { + expectedPath.add(Long.parseLong(pathStr)); + } catch (NumberFormatException e) { + throw new Exception("Invalid path number", e); + } + } + + String actualFailureMsg = null; + if (base.simulateResponse != null) { + actualFailureMsg = base.simulateResponse.body().txnGroups.get(groupIndex).failureMessage; + } else if (simulateResult != null) { + actualFailureMsg = simulateResult.getSimulateResponse().txnGroups.get(groupIndex).failureMessage; + } + + if (expectedFailureMsg.isEmpty() && actualFailureMsg != null && !actualFailureMsg.isEmpty()) { + throw new Exception("Expected no failure message, but got: '" + actualFailureMsg + "'"); + } else if (!expectedFailureMsg.isEmpty() && actualFailureMsg != null && !actualFailureMsg.contains(expectedFailureMsg)) { + throw new Exception("Expected failure message '" + expectedFailureMsg + "', but got: '" + actualFailureMsg + "'"); + } + + List actualPath = null; + if (base.simulateResponse != null) { + actualPath = base.simulateResponse.body().txnGroups.get(groupIndex).failedAt; + } else if (simulateResult != null) { + actualPath = simulateResult.getSimulateResponse().txnGroups.get(groupIndex).failedAt; + } + if (expectedPath.size() != actualPath.size()) { + throw new Exception("Expected failure path " + expectedPath + ", but got: " + actualPath); + } + + for (int i = 0; i < expectedPath.size(); i++) { + if (!expectedPath.get(i).equals(actualPath.get(i))) { + throw new Exception("Expected failure path " + expectedPath + ", but got: " + actualPath); + } + } + } + + @And("I create a transaction with an empty signer with the current transaction.") + public void iCreateATransactionWithAnEmptySignerWithTheCurrentTransaction() { + String address = ""; + if (base.address != null) { + address = base.address; + } else if (base.account != null) { + address = base.account.getAddress().toString(); + } + + base.txn = transSteps.builtTransaction; + transWithSigner = new TransactionWithSigner(base.txn, + new EmptyTransactionSigner(address) + ); + } + + @Then("the current application initial {string} state should contain {string} with value {string}.") + public void theCurrentApplicationInitialStateShouldContainWithValue(String arg0, String arg1, String arg2) { + + } } diff --git a/src/test/java/com/algorand/algosdk/integration/Stepdefs.java b/src/test/java/com/algorand/algosdk/integration/Stepdefs.java index 95548d66c..d2c3ddd8f 100644 --- a/src/test/java/com/algorand/algosdk/integration/Stepdefs.java +++ b/src/test/java/com/algorand/algosdk/integration/Stepdefs.java @@ -4,35 +4,72 @@ import com.algorand.algosdk.auction.Bid; import com.algorand.algosdk.auction.SignedBid; import com.algorand.algosdk.builder.transaction.TransactionBuilder; -import com.algorand.algosdk.crypto.*; +import com.algorand.algosdk.crypto.Address; +import com.algorand.algosdk.crypto.Digest; +import com.algorand.algosdk.crypto.Ed25519PublicKey; +import com.algorand.algosdk.crypto.LogicsigSignature; +import com.algorand.algosdk.crypto.MultisigAddress; +import com.algorand.algosdk.crypto.ParticipationPublicKey; +import com.algorand.algosdk.crypto.Signature; +import com.algorand.algosdk.crypto.VRFPublicKey; import com.algorand.algosdk.kmd.client.KmdClient; import com.algorand.algosdk.kmd.client.api.KmdApi; -import com.algorand.algosdk.kmd.client.model.*; +import com.algorand.algosdk.kmd.client.model.APIV1GETWalletsResponse; +import com.algorand.algosdk.kmd.client.model.APIV1Wallet; +import com.algorand.algosdk.kmd.client.model.CreateWalletRequest; +import com.algorand.algosdk.kmd.client.model.DeleteKeyRequest; +import com.algorand.algosdk.kmd.client.model.DeleteMultisigRequest; +import com.algorand.algosdk.kmd.client.model.ExportKeyRequest; +import com.algorand.algosdk.kmd.client.model.ExportMasterKeyRequest; +import com.algorand.algosdk.kmd.client.model.ExportMultisigRequest; +import com.algorand.algosdk.kmd.client.model.GenerateKeyRequest; +import com.algorand.algosdk.kmd.client.model.ImportKeyRequest; +import com.algorand.algosdk.kmd.client.model.ImportMultisigRequest; +import com.algorand.algosdk.kmd.client.model.InitWalletHandleTokenRequest; +import com.algorand.algosdk.kmd.client.model.ListKeysRequest; +import com.algorand.algosdk.kmd.client.model.ListMultisigRequest; +import com.algorand.algosdk.kmd.client.model.ReleaseWalletHandleTokenRequest; +import com.algorand.algosdk.kmd.client.model.RenameWalletRequest; +import com.algorand.algosdk.kmd.client.model.RenewWalletHandleTokenRequest; +import com.algorand.algosdk.kmd.client.model.SignMultisigRequest; +import com.algorand.algosdk.kmd.client.model.SignTransactionRequest; +import com.algorand.algosdk.kmd.client.model.WalletInfoRequest; import com.algorand.algosdk.mnemonic.Mnemonic; import com.algorand.algosdk.transaction.SignedTransaction; import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.transaction.TransactionWithSigner; import com.algorand.algosdk.util.AlgoConverter; import com.algorand.algosdk.util.Encoder; import com.algorand.algosdk.util.ResourceUtils; +import com.algorand.algosdk.v2.client.algod.SimulateTransaction; import com.algorand.algosdk.v2.client.common.AlgodClient; import com.algorand.algosdk.v2.client.common.IndexerClient; import com.algorand.algosdk.v2.client.common.Response; -import com.algorand.algosdk.v2.client.model.*; - +import com.algorand.algosdk.v2.client.model.AccountAssetResponse; +import com.algorand.algosdk.v2.client.model.Asset; +import com.algorand.algosdk.v2.client.model.CompileResponse; +import com.algorand.algosdk.v2.client.model.DryrunRequest; +import com.algorand.algosdk.v2.client.model.DryrunResponse; +import com.algorand.algosdk.v2.client.model.DryrunSource; +import com.algorand.algosdk.v2.client.model.SimulateRequest; +import com.algorand.algosdk.v2.client.model.SimulateRequestTransactionGroup; +import com.algorand.algosdk.v2.client.model.SimulateResponse; +import com.algorand.algosdk.v2.client.model.SimulateTraceConfig; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; import com.fasterxml.jackson.core.JsonProcessingException; - +import io.cucumber.java.en.And; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import java.io.*; +import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -43,7 +80,8 @@ import static org.assertj.core.api.Assertions.fail; public class Stepdefs { - public static String token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + public static String algodHost = "http://localhost"; + public static String algodToken = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; public static Integer algodPort = 60000; public static Integer kmdPort = 60001; @@ -95,6 +133,11 @@ public class Stepdefs { BigInteger votelst; BigInteger votekd; + TransactionWithSigner accountTxAndSigner; + SimulateRequest simulateRequest; + Response simulateResponse; + SimulateTransaction simulateTransaction; + /* Assets */ String creator = ""; BigInteger assetID = BigInteger.valueOf(1); @@ -111,7 +154,7 @@ protected Address getAddress(int i) { if (addresses == null) { throw new RuntimeException("Addresses not initialized, must use given 'wallet information'"); } - if (addresses.size() < i || addresses.size() == 0) { + if (addresses.size() < i || addresses.isEmpty()) { throw new RuntimeException("Not enough addresses, you may need to update the network template."); } try { @@ -558,7 +601,7 @@ public void kClient() { kmdClient.setConnectTimeout(30000); kmdClient.setReadTimeout(30000); kmdClient.setWriteTimeout(30000); - kmdClient.setApiKey(token); + kmdClient.setApiKey(algodToken); kmdClient.setBasePath("http://localhost:" + kmdPort); kcl = new KmdApi(kmdClient); } @@ -566,7 +609,7 @@ public void kClient() { @Given("an algod v2 client") public void aClientv2() { aclv2 = new com.algorand.algosdk.v2.client.common.AlgodClient( - "http://localhost", algodPort, token + algodHost, algodPort, algodToken ); } @@ -1191,4 +1234,94 @@ public void disassemblyMatches(String bytecodeFilename, String sourceFilename) t assertThat(disassembledSource).isEqualTo(expectedSource); } + + @And("I simulate the transaction") + public void iSimulateTheTransaction() throws Exception { + SimulateRequestTransactionGroup group = new SimulateRequestTransactionGroup(); + group.txns = Collections.singletonList(stx); + + if (simulateRequest == null) { + simulateRequest = new SimulateRequest(); + } + + simulateRequest.txnGroups = Collections.singletonList(group); + simulateResponse = aclv2.SimulateTransaction().request(simulateRequest).execute(); + } + + @When("I make a new simulate request.") + public void iMakeANewSimulateRequest() { + if (simulateRequest == null) { + simulateRequest = new SimulateRequest(); + } + } + + @Then("I allow {int} more budget on that simulate request.") + public void iAllowMoreBudgetOnThatSimulateRequest(int extraOpcodeBudget) { + simulateRequest.extraOpcodeBudget = Long.parseLong(extraOpcodeBudget + ""); + } + + @Then("I simulate the transaction group with the simulate request.") + public void iSimulateTheTransactionGroupWithTheSimulateRequest() { + + } + + @Then("I check the simulation result has power packs extra-opcode-budget with extra budget {int}.") + public void iCheckTheSimulationResultHasPowerPacksExtraOpcodeBudgetWithExtraBudget(int budget) { + + } + + @Then("I check the simulation result has power packs allow-more-logging.") + public void iCheckTheSimulationResultHasPowerPacksAllowMoreLogging() { + + } + + @Then("I allow more logs on that simulate request.") + public void iAllowMoreLogsOnThatSimulateRequest() { + simulateRequest.allowMoreLogging = true; + } + + @Then("I allow exec trace options {string} on that simulate request.") + public void iAllowExecTraceOptionsOnThatSimulateRequest(String stcString) { + SimulateTraceConfig stc = new SimulateTraceConfig(); + String[] stcArray = stcString.split(","); + for (String stcValue : stcArray) { + switch (stcValue) { + case "stack": stc.stackChange = true; + break; + case "scratch": stc.scratchChange = true; + break; + case "state": stc.stateChange = true; + break; + default: + break; + } + } + stc.enable = true; + simulateRequest.execTraceConfig = stc; + } + + @Then("{int}th unit in the {string} trace at txn-groups path {string} should add value {string} to stack, pop {int} values from stack, write value {string} to scratch slot {string}.") + public void thUnitInTheTraceAtTxnGroupsPathShouldAddValueToStackPopValuesFromStackWriteValueToScratchSlot(int arg0, String arg1, String arg2, String arg3, int arg4, String arg5, String arg6) { + + } + + @Then("the current application initial {string} state should be empty.") + public void theCurrentApplicationInitialStateShouldBeEmpty(String arg0) { + + } + + @Then("{string} hash at txn-groups path {string} should be {string}.") + public void hashAtTxnGroupsPathShouldBe(String arg0, String arg1, String arg2) { + + } + + @And("th unit in the {string} trace at txn-groups path {string} should write to {string} state {string} with new value {string}.") + public void indexThUnitInTheTraceAtTxnGroupsPathShouldWriteToStateWithNewValue(int arg0, String arg1, String arg2, String arg3, String arg4, String arg5) { + + } + + @When("I prepare the transaction without signatures for simulation") + public void iPrepareTheTransactionWithoutSignaturesForSimulation() { + stx = new SignedTransaction(txn); + } }