diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000..1ea816c33 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,20 @@ +changelog: + exclude: + labels: + - Skip-Release-Notes + categories: + - title: Bugfixes + labels: + - Bug-Fix + - title: New Features + labels: + - New Feature + - title: Enhancements + labels: + - Enhancement + - title: Not Yet Enabled + labels: + - Not-Yet-Enabled + - title: Other + labels: + - "*" diff --git a/.github/workflows/pr-type-category.yml b/.github/workflows/pr-type-category.yml new file mode 100644 index 000000000..0ae6369ca --- /dev/null +++ b/.github/workflows/pr-type-category.yml @@ -0,0 +1,24 @@ +name: Check PR category and type +on: + pull_request: + branches: + - develop + types: [opened, synchronize, reopened, labeled, unlabeled, edited] +jobs: + check_label: + runs-on: ubuntu-latest + name: Check PR Category and Type + steps: + - name: Checking for correct number of required github pr labels + uses: mheap/github-action-required-labels@v2 + with: + mode: exactly + count: 1 + labels: "New Feature, Enhancement, Bug-Fix, Not-Yet-Enabled, Skip-Release-Notes" + + - name: "Checking for PR Category in PR title. Should be like ': '." + run: | + if [[ ! "${{ github.event.pull_request.title }}" =~ ^.{2,}\:.{2,} ]]; then + echo "## PR Category is missing from PR title. Please add it like ': '." >> GITHUB_STEP_SUMMARY + exit 1 + fi diff --git a/Makefile b/Makefile index 726f8c30b..d1f6cdf21 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ unit: - mvn test -Dcucumber.filter.tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.indexer.rekey or @unit.transactions or @unit.transactions.keyreg or @unit.responses or @unit.applications or @unit.applications.boxes or @unit.dryrun or @unit.tealsign or @unit.responses.messagepack or @unit.responses.231 or @unit.responses.messagepack.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.algod.ledger_refactoring or @unit.indexer.ledger_refactoring or @unit.dryrun.trace.application" + mvn test -Dcucumber.filter.tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.indexer.rekey or @unit.transactions or @unit.transactions.keyreg or @unit.responses or @unit.applications or @unit.applications.boxes or @unit.dryrun or @unit.tealsign or @unit.responses.messagepack or @unit.responses.231 or @unit.responses.messagepack.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.algod.ledger_refactoring or @unit.indexer.ledger_refactoring or @unit.dryrun.trace.application or @unit.sourcemap" integration: - mvn test -Dcucumber.filter.tags="@algod or @assets or @auction or @kmd or @send or @send.keyregtxn or @indexer or @rekey or @applications.verified or @applications or @applications.boxes or @compile or @dryrun or @indexer.applications or @indexer.231 or @abi or @c2c" + mvn test \ + -Dtest=com.algorand.algosdk.integration.RunCucumberIntegrationTest \ + -Dcucumber.filter.tags="@algod or @assets or @auction or @kmd or @send or @send.keyregtxn or @indexer or @rekey_v1 or @applications.verified or @applications or @applications.boxes or @compile or @dryrun or @indexer.applications or @indexer.231 or @abi or @c2c or @compile.sourcemap" docker-test: ./run_integration_tests.sh diff --git a/src/main/java/com/algorand/algosdk/logic/Logic.java b/src/main/java/com/algorand/algosdk/logic/Logic.java index b8547b0f1..ef4202e08 100644 --- a/src/main/java/com/algorand/algosdk/logic/Logic.java +++ b/src/main/java/com/algorand/algosdk/logic/Logic.java @@ -250,14 +250,14 @@ public static ProgramData readProgram(byte[] program, List args) throws } // costs calculated dynamically starting in v4 if (version < 4 && cost > MAX_COST) { - throw new IllegalArgumentException("program too costly for Teal version < 4. consider using v4."); + throw new IllegalArgumentException("program too costly for version < 4. consider using v4."); } return new ProgramData(true, ints, bytes); } /** - * Retrieves TEAL supported version + * Retrieves supported program version * @return int * @throws IOException */ @@ -269,7 +269,7 @@ public static int getLogicSigVersion() throws IOException { } /** - * Retrieves max supported version of TEAL evaluator + * Retrieves max supported program version of evaluator * @return int * @throws IOException */ diff --git a/src/main/java/com/algorand/algosdk/logic/SourceMap.java b/src/main/java/com/algorand/algosdk/logic/SourceMap.java new file mode 100644 index 000000000..5883b5fa9 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/logic/SourceMap.java @@ -0,0 +1,121 @@ +package com.algorand.algosdk.logic; + +import java.lang.Integer; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * SourceMap class provides parser for source map from + * algod compile endpoint + */ +public class SourceMap { + + public int version; + public String file; + public String[] sources; + public String[] names; + public String mappings; + + public HashMap pcToLine; + public HashMap> lineToPc; + + public SourceMap(HashMap sourceMap) { + int version = (int) sourceMap.get("version"); + if(version != 3){ + throw new IllegalArgumentException("Only source map version 3 is supported"); + } + this.version = version; + + this.file = (String) sourceMap.get("file"); + this.mappings = (String) sourceMap.get("mappings"); + + this.lineToPc = new HashMap<>(); + this.pcToLine = new HashMap<>(); + + Integer lastLine = 0; + String[] vlqs = this.mappings.split(";"); + for(int i=0; i vals = VLQDecoder.decodeSourceMapLine(vlqs[i]); + + // If the vals length >= 3 the lineDelta + if(vals.size() >= 3){ + lastLine = lastLine + vals.get(2); + } + + if(!this.lineToPc.containsKey(lastLine)){ + this.lineToPc.put(lastLine, new ArrayList()); + } + + ArrayList currList = this.lineToPc.get(lastLine); + currList.add(i); + this.lineToPc.put(lastLine, currList); + + this.pcToLine.put(i, lastLine); + } + + } + + /** + * Returns the Integer line number for the passed PC or null if not found + * @param pc the pc (program counter) of the assembled file + * @return the line number or null if not found + */ + public Integer getLineForPc(Integer pc) { + return this.pcToLine.get(pc); + } + + /** + * Returns the List of PCs for the passed line number + * @param line the line number of the source file + * @return the list of PCs that line generated or empty array if not found + */ + public ArrayList getPcsForLine(Integer line) { + if(!this.pcToLine.containsKey(line)){ + return new ArrayList(); + } + return this.lineToPc.get(line); + } + + private static class VLQDecoder { + // VLQDecoder for decoding the VLQ values returned for source map + // based on the decoder implementation here: https://github.com/algorand/go-algorand-sdk/blob/develop/logic/source_map.go + + private static final String b64table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + private static final int vlqShiftSize = 5; + private static final int vlqFlag = 1 << vlqShiftSize; + private static final int vlqMask = vlqFlag - 1; + + public static ArrayList decodeSourceMapLine(String vlq) { + + ArrayList results = new ArrayList<>(); + int value = 0; + int shift = 0; + + for(int i=0; i 0) { + shift += vlqShiftSize; + continue; + } + + if((value&1)>0){ + value = (value >> 1) * -1; + }else{ + value = value >> 1; + } + + results.add(value); + + // Reset + value = 0; + shift = 0; + } + + return results; + } + } + +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/Enums.java b/src/main/java/com/algorand/algosdk/v2/client/model/Enums.java index 04ff6e685..8fc2fec9e 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/Enums.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/Enums.java @@ -1,5 +1,6 @@ package com.algorand.algosdk.v2.client.model; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; public class Enums { @@ -10,7 +11,8 @@ public class Enums { public enum AddressRole { @JsonProperty("sender") SENDER("sender"), @JsonProperty("receiver") RECEIVER("receiver"), - @JsonProperty("freeze-target") FREEZETARGET("freeze-target"); + @JsonProperty("freeze-target") FREEZETARGET("freeze-target"), + @JsonProperty("") UNKNOWN(""); final String serializedName; AddressRole(String name) { @@ -21,6 +23,17 @@ public enum AddressRole { public String toString() { return this.serializedName; } + + @JsonCreator + public static AddressRole forValue(String value) { + for (AddressRole t : values()) { + if (t.serializedName.equalsIgnoreCase(value)) { + return t; + } + } + return UNKNOWN; + } + } /** @@ -34,7 +47,8 @@ public enum Exclude { @JsonProperty("created-assets") CREATEDASSETS("created-assets"), @JsonProperty("apps-local-state") APPSLOCALSTATE("apps-local-state"), @JsonProperty("created-apps") CREATEDAPPS("created-apps"), - @JsonProperty("none") NONE("none"); + @JsonProperty("none") NONE("none"), + @JsonProperty("") UNKNOWN(""); final String serializedName; Exclude(String name) { @@ -45,6 +59,17 @@ public enum Exclude { public String toString() { return this.serializedName; } + + @JsonCreator + public static Exclude forValue(String value) { + for (Exclude t : values()) { + if (t.serializedName.equalsIgnoreCase(value)) { + return t; + } + } + return UNKNOWN; + } + } /** @@ -54,7 +79,8 @@ public String toString() { */ public enum Hashtype { @JsonProperty("sha512_256") SHA512_256("sha512_256"), - @JsonProperty("sha256") SHA256("sha256"); + @JsonProperty("sha256") SHA256("sha256"), + @JsonProperty("") UNKNOWN(""); final String serializedName; Hashtype(String name) { @@ -65,6 +91,17 @@ public enum Hashtype { public String toString() { return this.serializedName; } + + @JsonCreator + public static Hashtype forValue(String value) { + for (Hashtype t : values()) { + if (t.serializedName.equalsIgnoreCase(value)) { + return t; + } + } + return UNKNOWN; + } + } /** @@ -84,7 +121,8 @@ public enum OnCompletion { @JsonProperty("closeout") CLOSEOUT("closeout"), @JsonProperty("clear") CLEAR("clear"), @JsonProperty("update") UPDATE("update"), - @JsonProperty("delete") DELETE("delete"); + @JsonProperty("delete") DELETE("delete"), + @JsonProperty("") UNKNOWN(""); final String serializedName; OnCompletion(String name) { @@ -95,6 +133,17 @@ public enum OnCompletion { public String toString() { return this.serializedName; } + + @JsonCreator + public static OnCompletion forValue(String value) { + for (OnCompletion t : values()) { + if (t.serializedName.equalsIgnoreCase(value)) { + return t; + } + } + return UNKNOWN; + } + } /** @@ -107,7 +156,8 @@ public String toString() { public enum SigType { @JsonProperty("sig") SIG("sig"), @JsonProperty("msig") MSIG("msig"), - @JsonProperty("lsig") LSIG("lsig"); + @JsonProperty("lsig") LSIG("lsig"), + @JsonProperty("") UNKNOWN(""); final String serializedName; SigType(String name) { @@ -118,6 +168,17 @@ public enum SigType { public String toString() { return this.serializedName; } + + @JsonCreator + public static SigType forValue(String value) { + for (SigType t : values()) { + if (t.serializedName.equalsIgnoreCase(value)) { + return t; + } + } + return UNKNOWN; + } + } /** @@ -137,7 +198,8 @@ public enum TxType { @JsonProperty("acfg") ACFG("acfg"), @JsonProperty("axfer") AXFER("axfer"), @JsonProperty("afrz") AFRZ("afrz"), - @JsonProperty("appl") APPL("appl"); + @JsonProperty("appl") APPL("appl"), + @JsonProperty("") UNKNOWN(""); final String serializedName; TxType(String name) { @@ -148,6 +210,17 @@ public enum TxType { public String toString() { return this.serializedName; } + + @JsonCreator + public static TxType forValue(String value) { + for (TxType t : values()) { + if (t.serializedName.equalsIgnoreCase(value)) { + return t; + } + } + return UNKNOWN; + } + } } diff --git a/src/test/java/com/algorand/algosdk/integration/Applications.java b/src/test/java/com/algorand/algosdk/integration/Applications.java index 9e176c0f1..1970e3af9 100644 --- a/src/test/java/com/algorand/algosdk/integration/Applications.java +++ b/src/test/java/com/algorand/algosdk/integration/Applications.java @@ -143,7 +143,7 @@ public void sendTransactionWithTransientAccountAndCheckForError(String error) th @Given("I wait for the transaction to be confirmed.") public void waitForTransactionToBeConfirmed() throws Exception { - Utils.waitForConfirmation(clients.v2Client, txId, 5); + Utils.waitForConfirmation(clients.v2Client, txId, 1); } // TODO: Use V2 Pending Transaction endpoint when it is available. @@ -173,7 +173,7 @@ public void fundAppAccount(Integer amount) throws Exception { SignedTransaction stx = base.signWithAddress(tx, sender); Response rPost = clients.v2Client.RawTransaction().rawtxn(Encoder.encodeToMsgPack(stx)).execute(); - Utils.waitForConfirmation(clients.v2Client, rPost.body().txId, 5); + Utils.waitForConfirmation(clients.v2Client, rPost.body().txId, 1); } @Then("I get the account address for the current application and see that it matches the app id's hash") diff --git a/src/test/java/com/algorand/algosdk/integration/Stepdefs.java b/src/test/java/com/algorand/algosdk/integration/Stepdefs.java index ad00827f5..278878e03 100644 --- a/src/test/java/com/algorand/algosdk/integration/Stepdefs.java +++ b/src/test/java/com/algorand/algosdk/integration/Stepdefs.java @@ -17,6 +17,7 @@ import com.algorand.algosdk.transaction.Transaction; import com.algorand.algosdk.util.AlgoConverter; import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.util.ResourceUtils; import com.algorand.algosdk.v2.client.common.Response; import com.algorand.algosdk.v2.client.model.CompileResponse; import com.algorand.algosdk.v2.client.model.DryrunResponse; @@ -24,6 +25,7 @@ import com.algorand.algosdk.v2.client.model.DryrunSource; import com.fasterxml.jackson.core.JsonProcessingException; + import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; @@ -32,6 +34,7 @@ import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.security.GeneralSecurityException; @@ -40,8 +43,11 @@ import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; import static com.algorand.algosdk.util.ResourceUtils.loadResource; import static org.assertj.core.api.Assertions.assertThat; @@ -118,6 +124,31 @@ public class Stepdefs { Response compileResponse; Response dryrunResponse; + private static class DevModeState { + static final long ACCOUNT_FUNDING_MICROALGOS = 100_000_000; + private Account advanceRounds; + + /** + * randomAmount minimizes the chance `advanceRounds` issues duplicate transactions by randomizing the payment amount. + */ + private long randomAmount() { + return ThreadLocalRandom.current().nextLong(1, (long) (ACCOUNT_FUNDING_MICROALGOS * .01)); + } + + public SignedTransaction selfPay(TransactionParams tp) throws Exception { + Transaction tx = + Transaction.PaymentTransactionBuilder() + .sender(advanceRounds.getAddress()) + .suggestedParams(tp) + .amount(randomAmount()) + .receiver(advanceRounds.getAddress()) + .build(); + return advanceRounds.signTransaction(tx); + } + } + + private final DevModeState dms = new DevModeState(); + protected Address getAddress(int i) { if (addresses == null) { throw new RuntimeException("Addresses not initialized, must use given 'wallet information'"); @@ -158,6 +189,48 @@ public SignedTransaction signWithAddress(Transaction tx, Address addr) throws co return acct.signTransaction(tx); } + /** + * advanceRound is a convenience method intended for testing with DevMode networks. + *

+ * Since DevMode block generation requires a transaction rather than time passing, test assertions may require advancing rounds. advanceRound issues advanceCount payments to advance rounds. + */ + private void advanceRoundsV1(int advanceCount) { + initializeDevModeAccount(); + for (int i = 0; i < advanceCount; i++) { + try { + acl.rawTransaction(Encoder.encodeToMsgPack(dms.selfPay(acl.transactionParams()))); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * initializeDevModeAccount performs a one-time account initialization per inclusion in a scenario outline. No attempt is made to delete the account. + */ + public void initializeDevModeAccount() { + if (dms.advanceRounds != null) { + return; + } + + try { + getParams(); + dms.advanceRounds = new Account(); + Address sender = getAddress(0); + Transaction tx = + Transaction.PaymentTransactionBuilder() + .sender(sender) + .suggestedParams(acl.transactionParams()) + .amount(DevModeState.ACCOUNT_FUNDING_MICROALGOS) + .receiver(dms.advanceRounds.getAddress()) + .build(); + SignedTransaction st = signWithAddress(tx, sender); + acl.rawTransaction(Encoder.encodeToMsgPack(st)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /** * Convenience method to export a key and initialize an account to use for signing. */ @@ -447,8 +520,8 @@ public void status() throws ApiException{ } @When("I get status after this block") - public void statusBlock() throws ApiException, InterruptedException { - Thread.sleep(4000); + public void statusBlock() throws Exception { + advanceRoundsV1(1); statusAfter = acl.waitForBlock(status.getLastRound()); } @@ -526,21 +599,48 @@ public void msigNotInWallet()throws com.algorand.algosdk.kmd.client.ApiException } @When("I generate a key using kmd") - public void genKeyKmd() throws com.algorand.algosdk.kmd.client.ApiException, NoSuchAlgorithmException{ + public void genKeyKmd() throws com.algorand.algosdk.kmd.client.ApiException, NoSuchAlgorithmException { GenerateKeyRequest req = new GenerateKeyRequest(); req.setDisplayMnemonic(false); req.setWalletHandleToken(handle); pk = new Address(kcl.generateKey(req).getAddress()); } + @When("I generate a key using kmd for rekeying and fund it") + public void genKeyKmdRekey() throws com.algorand.algosdk.kmd.client.ApiException, NoSuchAlgorithmException { + GenerateKeyRequest req = new GenerateKeyRequest(); + req.setDisplayMnemonic(false); + req.setWalletHandleToken(handle); + rekey = new Address(kcl.generateKey(req).getAddress()); + + // Fund rekey address + try { + getParams(); + Address sender = getAddress(0); + Transaction tx = + Transaction.PaymentTransactionBuilder() + .sender(sender) + .suggestedParams(acl.transactionParams()) + .amount(100_000_000) + .receiver(rekey) + .build(); + SignedTransaction st = signWithAddress(tx, sender); + acl.rawTransaction(Encoder.encodeToMsgPack(st)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + Address rekey; + @Then("the key should be in the wallet") public void keyInWallet() throws com.algorand.algosdk.kmd.client.ApiException { ListKeysRequest req = new ListKeysRequest(); req.setWalletHandleToken(handle); List keys = kcl.listKeysInWallet(req).getAddresses(); boolean exists = false; - for (String k : keys){ - if (k.equals(pk.toString())){ + for (String k : keys) { + if (k.equals(pk.toString())) { exists = true; } } @@ -632,6 +732,7 @@ public void aClient() throws FileNotFoundException, IOException{ algodClient.setBasePath("http://localhost:" + algodPort); acl = new AlgodApi(algodClient); } + @Given("an algod v2 client") public void aClientv2() throws FileNotFoundException, IOException{ aclv2 = new com.algorand.algosdk.v2.client.common.AlgodClient( @@ -660,21 +761,30 @@ public void walletInfo() throws com.algorand.algosdk.kmd.client.ApiException, No } @Given("default transaction with parameters {int} {string}") - public void defaultTxn(int amt, String note) throws ApiException, NoSuchAlgorithmException{ + public void defaultTxn(int amt, String note) throws ApiException, NoSuchAlgorithmException { + defaultTxnWithAddress(amt, note, getAddress(0)); + } + + @Given("default transaction with parameters {int} {string} and rekeying key") + public void defaultTxnForRekeying(int amt, String note) { + defaultTxnWithAddress(amt, note, rekey); + } + + private void defaultTxnWithAddress(int amt, String note, Address sender) { getParams(); - if (note.equals("none")){ + if (note.equals("none")) { this.note = null; - } else{ + } else { this.note = Encoder.decodeFromBase64(note); } txnBuilder = Transaction.PaymentTransactionBuilder() - .sender(getAddress(0)) + .sender(sender) .suggestedParams(params) .note(this.note) .amount(amt) .receiver(getAddress(1)); txn = txnBuilder.build(); - pk = getAddress(0); + pk = sender; } @Given("default multisig transaction with parameters {int} {string}") @@ -715,23 +825,32 @@ public void sendMsigTxn() throws JsonProcessingException, ApiException{ } @Then("the transaction should go through") - public void checkTxn() throws ApiException, InterruptedException{ + public void checkTxn() throws Exception { String ans = acl.pendingTransactionInformation(txid).getFrom(); assertThat(this.txn.sender.toString()).isEqualTo(ans); - acl.waitForBlock(lastRound.add(BigInteger.valueOf(2))); + waitForAlgodTransactionProcessingToComplete(); String senderFromResponse = acl.transactionInformation(txn.sender.toString(), txid).getFrom(); assertThat(senderFromResponse).isEqualTo(txn.sender.toString()); assertThat(acl.transaction(txid).getFrom()).isEqualTo(senderFromResponse); } + /** + * waitForAlgodTransactionProcessingToComplete is a Dev mode helper method that's a rough analog to `acl.waitForBlock(lastRound.add(BigInteger.valueOf(2)));`. + *

+ * Since Dev mode produces blocks on a per transaction basis, it's possible algod generates a block _before_ the corresponding SDK call to wait for a block. Without _any_ wait, it's possible the SDK looks for the transaction before algod completes processing. So, the method performs a local sleep to simulate waiting for a block. + */ + private static void waitForAlgodTransactionProcessingToComplete() throws Exception { + Thread.sleep(500); + } + @Then("I can get the transaction by ID") - public void txnbyID() throws ApiException, InterruptedException{ - acl.waitForBlock(lastRound.add(BigInteger.valueOf(2))); + public void txnByID() throws Exception { + waitForAlgodTransactionProcessingToComplete(); assertThat(acl.transaction(txid).getFrom()).isEqualTo(pk.toString()); } @Then("the transaction should not go through") - public void txnFail(){ + public void txnFail() { assertThat(err).isTrue(); } @@ -1361,4 +1480,26 @@ public void i_get_execution_result(String result) throws Exception { assertThat(msgs.size()).isGreaterThan(0); assertThat(msgs.get(msgs.size() - 1)).isEqualTo(result); } + + + @When("I compile a teal program {string} with mapping enabled") + public void i_compile_a_teal_program_with_mapping_enabled(String tealPath) throws Exception { + byte[] tealProgram = ResourceUtils.loadResource(tealPath); + this.compileResponse = aclv2.TealCompile().source(tealProgram).sourcemap(true).execute(); + } + + + @Then("the resulting source map is the same as the json {string}") + public void the_resulting_source_map_is_the_same_as_the_json(String jsonPath) throws Exception { + String[] fields = {"version", "sources", "names", "mapping", "mappings"}; + String srcMapStr = new String(ResourceUtils.readResource(jsonPath), StandardCharsets.UTF_8); + + HashMap expectedMap = new HashMap<>(Encoder.decodeFromJson(srcMapStr, Map.class)); + HashMap actualMap = this.compileResponse.body().sourcemap; + + for(String field: fields){ + assertThat(actualMap.get(field)).isEqualTo(expectedMap.get(field)); + } + } + } diff --git a/src/test/java/com/algorand/algosdk/integration/TransientAccount.java b/src/test/java/com/algorand/algosdk/integration/TransientAccount.java index dfc3e48fa..a571e617b 100644 --- a/src/test/java/com/algorand/algosdk/integration/TransientAccount.java +++ b/src/test/java/com/algorand/algosdk/integration/TransientAccount.java @@ -38,7 +38,7 @@ public void createAndFundTransientAccount(Long amount) throws Exception { SignedTransaction stx = base.signWithAddress(tx, sender); Response rPost = clients.v2Client.RawTransaction().rawtxn(Encoder.encodeToMsgPack(stx)).execute(); - Utils.waitForConfirmation(clients.v2Client, rPost.body().txId, 5); + Utils.waitForConfirmation(clients.v2Client, rPost.body().txId, 1); } } diff --git a/src/test/java/com/algorand/algosdk/logic/TestLogic.java b/src/test/java/com/algorand/algosdk/logic/TestLogic.java index bcf3ca9e7..64abd28fc 100644 --- a/src/test/java/com/algorand/algosdk/logic/TestLogic.java +++ b/src/test/java/com/algorand/algosdk/logic/TestLogic.java @@ -213,7 +213,7 @@ public void testCheckProgramCostly() throws Exception { program2[0] = v; assertThatThrownBy(() -> readProgram(program2, args)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("program too costly for Teal version < 4. consider using v4."); + .hasMessage("program too costly for version < 4. consider using v4."); } for (byte v : versions) { @@ -236,7 +236,7 @@ public void testCheckProgramInvalidOpcode() throws Exception { } @Test - public void testCheckProgramTealV2() throws Exception { + public void testCheckProgramV2() throws Exception { assertThat(getEvalMaxVersion()).isGreaterThanOrEqualTo(2); assertThat(getLogicSigVersion()).isGreaterThanOrEqualTo(2); @@ -269,7 +269,7 @@ public void testCheckProgramTealV2() throws Exception { } @Test - public void testCheckProgramTealV3() throws Exception { + public void testCheckProgramV3() throws Exception { assertThat(getEvalMaxVersion()).isGreaterThanOrEqualTo(3); assertThat(getLogicSigVersion()).isGreaterThanOrEqualTo(3); @@ -311,7 +311,7 @@ public void testCheckProgramTealV3() throws Exception { } @Test - public void testCheckProgramTealV4() throws Exception { + public void testCheckProgramV4() throws Exception { assertThat(getEvalMaxVersion()).isGreaterThanOrEqualTo(4); { @@ -379,7 +379,7 @@ public void testCheckProgramTealV4() throws Exception { } @Test - public void testCheckProgramTealV5() throws Exception { + public void testCheckProgramV5() throws Exception { assertThat(getEvalMaxVersion()).isGreaterThanOrEqualTo(5); { @@ -414,7 +414,7 @@ public void testCheckProgramTealV5() throws Exception { } @Test - public void testCheckProgramTealV6() throws Exception { + public void testCheckProgramV6() throws Exception { assertThat(getEvalMaxVersion()).isGreaterThanOrEqualTo(6); { diff --git a/src/test/java/com/algorand/algosdk/unit/TestSourceMap.java b/src/test/java/com/algorand/algosdk/unit/TestSourceMap.java new file mode 100644 index 000000000..7c51e9862 --- /dev/null +++ b/src/test/java/com/algorand/algosdk/unit/TestSourceMap.java @@ -0,0 +1,60 @@ +package com.algorand.algosdk.unit; + +import com.algorand.algosdk.logic.SourceMap; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.util.ResourceUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; + +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import static org.assertj.core.api.Assertions.assertThat; + + +public class TestSourceMap { + SourceMap srcMap; + + @Given("a source map json file {string}") + public void a_source_map_json_file(String srcMapPath) throws IOException { + String srcMapStr = new String(ResourceUtils.readResource(srcMapPath), StandardCharsets.UTF_8); + HashMap map = new HashMap<>(Encoder.decodeFromJson(srcMapStr, Map.class)); + this.srcMap = new SourceMap(map); + } + + @Then("the string composed of pc:line number equals {string}") + public void the_string_composed_of_pc_line_number_equals(String expected) { + List strs = new ArrayList<>(); + for(Map.Entry entry: this.srcMap.pcToLine.entrySet()){ + strs.add(String.format("%d:%d", entry.getKey(), entry.getValue())); + } + + String actual = StringUtils.join(strs, ";"); + assertThat(actual).isEqualTo(expected); + } + + @Then("getting the last pc associated with a line {string} equals {string}") + public void getting_the_last_pc_associated_with_a_line_equals(String lineStr, String pcStr) { + Integer line = Integer.valueOf(lineStr); + Integer pc = Integer.valueOf(pcStr); + + ArrayList actualPcs = this.srcMap.getPcsForLine(line); + assertThat(actualPcs.get(actualPcs.size() - 1)).isEqualTo(pc); + } + + + @Then("getting the line associated with a pc {string} equals {string}") + public void getting_the_line_associated_with_a_pc_equals(String pcStr, String lineStr) { + Integer line = Integer.valueOf(lineStr); + Integer pc = Integer.valueOf(pcStr); + + Integer actualLine = this.srcMap.getLineForPc(pc); + assertThat(actualLine).isEqualTo(line); + } + +} diff --git a/src/test/java/com/algorand/algosdk/util/TestEncoder.java b/src/test/java/com/algorand/algosdk/util/TestEncoder.java index 5bfb0e95c..15e4f726f 100644 --- a/src/test/java/com/algorand/algosdk/util/TestEncoder.java +++ b/src/test/java/com/algorand/algosdk/util/TestEncoder.java @@ -1,11 +1,17 @@ package com.algorand.algosdk.util; +import com.algorand.algosdk.algod.client.model.PaymentTransactionType; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.v2.client.model.Account; +import com.algorand.algosdk.v2.client.model.Enums; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; +import java.io.IOException; import java.math.BigInteger; import static org.assertj.core.api.Assertions.*; @@ -131,4 +137,42 @@ public void testDecodeUint64() { .isInstanceOf(IllegalArgumentException.class) .hasMessage("Length of byte array is invalid"); } + + @ParameterizedTest + @CsvSource({ + "pay, pay", + "blah, ", + "blah, ''" + }) + public void testTransactionEnum(String type, String expected) throws IOException { + String txnString = "{\"type\": \"" + type + "\"}"; + Transaction txn = Encoder.decodeFromJson(txnString, Transaction.class); + if (expected != null) { + Transaction.Type enumValue = Transaction.Type.forValue(expected); + assertThat(enumValue).isEqualTo(txn.type); + } else { + assertThat(Transaction.Type.Default).isEqualTo(txn.type); + } + + TestUtil.serializeDeserializeCheck(txn); + } + + @ParameterizedTest + @CsvSource({ + "sig, sig", + "blah, ", + "blah, ''" + }) + public void testHttpModelEnum(String type, String expected) throws IOException { + String acctString = "{\"sig-type\": \"" + type + "\"}"; + Account acct = Encoder.decodeFromJson(acctString, Account.class); + if (expected != null) { + Enums.SigType enumValue = Enums.SigType.forValue(expected); + assertThat(enumValue).isEqualTo(acct.sigType); + } else { + assertThat(Enums.SigType.UNKNOWN).isEqualTo(acct.sigType); + } + + TestUtil.serializeDeserializeCheck(acct); + } }