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);
+ }
}