From b101fbc2b33b9b0c5aa1aab418d34c6397c305c9 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Tue, 24 May 2022 12:19:04 -0700 Subject: [PATCH 1/8] Rerun code generation based on new models (#321) --- .../algosdk/v2/client/algod/GetProof.java | 11 +++ .../algosdk/v2/client/algod/TealCompile.java | 9 +++ .../v2/client/algod/TealDisassemble.java | 68 +++++++++++++++++++ .../algosdk/v2/client/common/AlgodClient.java | 11 +++ .../v2/client/common/IndexerClient.java | 7 +- .../indexer/LookupAccountTransactions.java | 2 +- .../indexer/LookupAssetTransactions.java | 2 +- .../client/indexer/SearchForTransactions.java | 3 +- .../v2/client/model/CompileResponse.java | 8 +++ .../v2/client/model/DisassembleResponse.java | 30 ++++++++ .../algosdk/v2/client/model/Enums.java | 38 ++++++----- .../v2/client/model/ProofResponse.java | 2 +- 12 files changed, 166 insertions(+), 25 deletions(-) create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/TealDisassemble.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/DisassembleResponse.java diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/GetProof.java b/src/main/java/com/algorand/algosdk/v2/client/algod/GetProof.java index d07043feb..6824d0f4c 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/algod/GetProof.java +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/GetProof.java @@ -5,6 +5,7 @@ import com.algorand.algosdk.v2.client.common.Query; import com.algorand.algosdk.v2.client.common.QueryData; import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.Enums; import com.algorand.algosdk.v2.client.model.ProofResponse; @@ -28,6 +29,16 @@ public GetProof(Client client, Long round, String txid) { this.txid = txid; } + /** + * The type of hash function used to create the proof, must be one of: + * sha512_256 + * sha256 + */ + public GetProof hashtype(Enums.Hashtype hashtype) { + addQuery("hashtype", String.valueOf(hashtype)); + return this; + } + /** * Execute the query. * @return the query response object. diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/TealCompile.java b/src/main/java/com/algorand/algosdk/v2/client/algod/TealCompile.java index aadd2b545..5b0d1008f 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/algod/TealCompile.java +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/TealCompile.java @@ -28,6 +28,15 @@ public TealCompile source(byte[] source) { return this; } + /** + * When set to `true`, returns the source map of the program as a JSON. Defaults to + * `false`. + */ + public TealCompile sourcemap(Boolean sourcemap) { + addQuery("sourcemap", String.valueOf(sourcemap)); + return this; + } + /** * Execute the query. * @return the query response object. diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/TealDisassemble.java b/src/main/java/com/algorand/algosdk/v2/client/algod/TealDisassemble.java new file mode 100644 index 000000000..ef6a4f5f8 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/TealDisassemble.java @@ -0,0 +1,68 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.DisassembleResponse; + + +/** + * Given the program bytes, return the TEAL source code in plain text. This + * endpoint is only enabled when a node's configuration file sets + * EnableDeveloperAPI to true. + * /v2/teal/disassemble + */ +public class TealDisassemble extends Query { + + public TealDisassemble(Client client) { + super(client, new HttpMethod("post")); + } + + /** + * TEAL program binary to be disassembled + */ + public TealDisassemble source(byte[] source) { + addToBody(source); + return this; + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(DisassembleResponse.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(DisassembleResponse.class); + return resp; + } + + protected QueryData getRequestString() { + if (qd.bodySegments.isEmpty()) { + throw new RuntimeException("source is not set. It is a required parameter."); + } + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("teal")); + addPathSegment(String.valueOf("disassemble")); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java index e6632efa6..4d2909782 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java @@ -21,6 +21,7 @@ import com.algorand.algosdk.v2.client.algod.GetApplicationByID; import com.algorand.algosdk.v2.client.algod.GetAssetByID; import com.algorand.algosdk.v2.client.algod.TealCompile; +import com.algorand.algosdk.v2.client.algod.TealDisassemble; import com.algorand.algosdk.v2.client.algod.TealDryrun; import com.algorand.algosdk.crypto.Address; @@ -240,6 +241,16 @@ public TealCompile TealCompile() { return new TealCompile((Client) this); } + /** + * Given the program bytes, return the TEAL source code in plain text. This + * endpoint is only enabled when a node's configuration file sets + * EnableDeveloperAPI to true. + * /v2/teal/disassemble + */ + public TealDisassemble TealDisassemble() { + return new TealDisassemble((Client) this); + } + /** * Executes TEAL program(s) in context and returns debugging information about the * execution. This endpoint is only enabled when a node's configuration file sets diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/IndexerClient.java b/src/main/java/com/algorand/algosdk/v2/client/common/IndexerClient.java index b2486a905..6661c80d5 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/IndexerClient.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/IndexerClient.java @@ -110,7 +110,7 @@ public LookupAccountCreatedApplications lookupAccountCreatedApplications(Address } /** - * Lookup account transactions. + * Lookup account transactions. Transactions are returned newest to oldest. * /v2/accounts/{account-id}/transactions */ public LookupAccountTransactions lookupAccountTransactions(Address accountId) { @@ -166,7 +166,7 @@ public LookupAssetBalances lookupAssetBalances(Long assetId) { } /** - * Lookup transactions for an asset. + * Lookup transactions for an asset. Transactions are returned oldest to newest. * /v2/assets/{asset-id}/transactions */ public LookupAssetTransactions lookupAssetTransactions(Long assetId) { @@ -190,7 +190,8 @@ public LookupTransaction lookupTransaction(String txid) { } /** - * Search for transactions. + * Search for transactions. Transactions are returned oldest to newest unless the + * address parameter is used, in which case results are returned newest to oldest. * /v2/transactions */ public SearchForTransactions searchForTransactions() { diff --git a/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupAccountTransactions.java b/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupAccountTransactions.java index 0aadb6213..d99ec5b91 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupAccountTransactions.java +++ b/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupAccountTransactions.java @@ -15,7 +15,7 @@ /** - * Lookup account transactions. + * Lookup account transactions. Transactions are returned newest to oldest. * /v2/accounts/{account-id}/transactions */ public class LookupAccountTransactions extends Query { diff --git a/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupAssetTransactions.java b/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupAssetTransactions.java index ea0cebef2..ede4367b1 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupAssetTransactions.java +++ b/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupAssetTransactions.java @@ -15,7 +15,7 @@ /** - * Lookup transactions for an asset. + * Lookup transactions for an asset. Transactions are returned oldest to newest. * /v2/assets/{asset-id}/transactions */ public class LookupAssetTransactions extends Query { diff --git a/src/main/java/com/algorand/algosdk/v2/client/indexer/SearchForTransactions.java b/src/main/java/com/algorand/algosdk/v2/client/indexer/SearchForTransactions.java index 22c7dace2..3197b89b0 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/indexer/SearchForTransactions.java +++ b/src/main/java/com/algorand/algosdk/v2/client/indexer/SearchForTransactions.java @@ -15,7 +15,8 @@ /** - * Search for transactions. + * Search for transactions. Transactions are returned oldest to newest unless the + * address parameter is used, in which case results are returned newest to oldest. * /v2/transactions */ public class SearchForTransactions extends Query { diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/CompileResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/CompileResponse.java index f0ed7b74f..35f245637 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/CompileResponse.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/CompileResponse.java @@ -1,5 +1,6 @@ package com.algorand.algosdk.v2.client.model; +import java.util.HashMap; import java.util.Objects; import com.algorand.algosdk.v2.client.common.PathResponse; @@ -22,6 +23,12 @@ public class CompileResponse extends PathResponse { @JsonProperty("result") public String result; + /** + * JSON of the source map + */ + @JsonProperty("sourcemap") + public HashMap sourcemap; + @Override public boolean equals(Object o) { @@ -31,6 +38,7 @@ public boolean equals(Object o) { CompileResponse other = (CompileResponse) o; if (!Objects.deepEquals(this.hash, other.hash)) return false; if (!Objects.deepEquals(this.result, other.result)) return false; + if (!Objects.deepEquals(this.sourcemap, other.sourcemap)) return false; return true; } diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/DisassembleResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/DisassembleResponse.java new file mode 100644 index 000000000..468a36a07 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/DisassembleResponse.java @@ -0,0 +1,30 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Teal disassembly Result + */ +public class DisassembleResponse extends PathResponse { + + /** + * disassembled Teal code + */ + @JsonProperty("result") + public String result; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + DisassembleResponse other = (DisassembleResponse) o; + if (!Objects.deepEquals(this.result, other.result)) return false; + + return true; + } +} 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 b2515301c..04ff6e685 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 @@ -47,6 +47,26 @@ public String toString() { } } + /** + * The type of hash function used to create the proof, must be one of: + * sha512_256 + * sha256 + */ + public enum Hashtype { + @JsonProperty("sha512_256") SHA512_256("sha512_256"), + @JsonProperty("sha256") SHA256("sha256"); + + final String serializedName; + Hashtype(String name) { + this.serializedName = name; + } + + @Override + public String toString() { + return this.serializedName; + } + } + /** * (apan) defines the what additional actions occur with the transaction. * Valid types: @@ -130,22 +150,4 @@ public String toString() { } } - /** - * Combine with the address parameter to define what type of address to search for. - */ - public enum Hashtype { - @JsonProperty("sumhash") SUMHASH("sumhash"), - @JsonProperty("sha512_256") SHA512_256("sha512_256"); - - final String serializedName; - Hashtype(String name) { - this.serializedName = name; - } - - @Override - public String toString() { - return this.serializedName; - } - } - } diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/ProofResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/ProofResponse.java index 71b4e8c89..6318e77a3 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/ProofResponse.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/ProofResponse.java @@ -13,8 +13,8 @@ public class ProofResponse extends PathResponse { /** * The type of hash function used to create the proof, must be one of: - * sumhash * sha512_256 + * sha256 */ @JsonProperty("hashtype") public Enums.Hashtype hashtype; From f7f8adb07e2cac66d25985b54c5094d1c99dbe62 Mon Sep 17 00:00:00 2001 From: Jack <87339414+algojack@users.noreply.github.com> Date: Wed, 25 May 2022 16:22:45 -0400 Subject: [PATCH 2/8] Excluding gh-pages branch from cicd (#325) --- .circleci/config.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 941be9ffe..57fee085b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,8 +4,16 @@ workflows: version: 2 test: jobs: - - unit-test - - integration-test + - unit-test: + filters: + branches: + ignore: + - gh-pages + - integration-test: + filters: + branches: + ignore: + - gh-pages jobs: unit-test: From 878843c7a76e22b747bebf2328f09404a38c09e9 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Fri, 27 May 2022 14:01:34 -0700 Subject: [PATCH 3/8] Add codegen workflow (#327) --- .github/workflows/codegen.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/codegen.yml diff --git a/.github/workflows/codegen.yml b/.github/workflows/codegen.yml new file mode 100644 index 000000000..db258a1ea --- /dev/null +++ b/.github/workflows/codegen.yml @@ -0,0 +1,19 @@ +name: "SDK Code Generation" +on: + schedule: + - cron: '20 23 * * *' +permissions: + contents: write + pull-requests: write +jobs: + generate_and_pr: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v3 + - name: Generate and PR + uses: algorand/generator/.github/actions/sdk-codegen/ + with: + args: "-k JAVA" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b1c83d99aead2a09d766a37305eb91b444d37329 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Fri, 27 May 2022 14:46:18 -0700 Subject: [PATCH 4/8] Add branch info to sdk gen action (#330) --- .github/workflows/codegen.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codegen.yml b/.github/workflows/codegen.yml index db258a1ea..28e2e330e 100644 --- a/.github/workflows/codegen.yml +++ b/.github/workflows/codegen.yml @@ -12,7 +12,7 @@ jobs: - name: Check out repository uses: actions/checkout@v3 - name: Generate and PR - uses: algorand/generator/.github/actions/sdk-codegen/ + uses: algorand/generator/.github/actions/sdk-codegen/@master with: args: "-k JAVA" env: From 0ed5109f59800a1cbaf2dcd90f28fcb21da767cb Mon Sep 17 00:00:00 2001 From: algoidurovic <91566643+algoidurovic@users.noreply.github.com> Date: Thu, 2 Jun 2022 13:19:50 -0400 Subject: [PATCH 5/8] Generate updated client API code (#318) * generate api client code * noop * remove generated files * remove generated api code for teal disassembly * regen * regen --- .../v2/client/model/DryrunTxnResult.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/DryrunTxnResult.java b/src/main/java/com/algorand/algosdk/v2/client/model/DryrunTxnResult.java index 1ca53a139..2aeda9a07 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/DryrunTxnResult.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/DryrunTxnResult.java @@ -22,7 +22,20 @@ public class DryrunTxnResult extends PathResponse { public List appCallTrace = new ArrayList(); /** - * Execution cost of app call transaction + * Budget added during execution of app call transaction. + */ + @JsonProperty("budget-added") + public Long budgetAdded; + + /** + * Budget consumed during execution of app call transaction. + */ + @JsonProperty("budget-consumed") + public Long budgetConsumed; + + /** + * Net cost of app execution. Field is DEPRECATED and is subject for removal. + * Instead, use `budget-added` and `budget-consumed. */ @JsonProperty("cost") public Long cost; @@ -81,6 +94,8 @@ public boolean equals(Object o) { DryrunTxnResult other = (DryrunTxnResult) o; if (!Objects.deepEquals(this.appCallMessages, other.appCallMessages)) return false; if (!Objects.deepEquals(this.appCallTrace, other.appCallTrace)) return false; + if (!Objects.deepEquals(this.budgetAdded, other.budgetAdded)) return false; + if (!Objects.deepEquals(this.budgetConsumed, other.budgetConsumed)) return false; if (!Objects.deepEquals(this.cost, other.cost)) return false; if (!Objects.deepEquals(this.disassembly, other.disassembly)) return false; if (!Objects.deepEquals(this.globalDelta, other.globalDelta)) return false; From 613ca2c287dcce9ba8471f507f3fa764a28a02c6 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 2 Jun 2022 10:56:57 -0700 Subject: [PATCH 6/8] Introduce unified `MethodCallTransactionBuilder` & deprecate `MethodCallParams.Builder` (#324) * Unify builders * Remove unnecessary checks * Fix compile errors * Fix unit test error * Move method call txn creation from ATC to MethodCallParams --- .../ApplicationBaseTransactionBuilder.java | 20 +- .../ApplicationCallReferencesSetter.java | 30 ++ .../ApplicationCreateTransactionBuilder.java | 19 +- .../ApplicationUpdateTransactionBuilder.java | 11 +- .../MethodCallTransactionBuilder.java | 171 +++++++ .../transaction/StateSchemaSetter.java | 26 ++ .../transaction/TEALProgramSetter.java | 17 + .../transaction/TransactionBuilder.java | 419 +---------------- .../TransactionParametersBuilder.java | 431 ++++++++++++++++++ .../AtomicTransactionComposer.java | 163 +------ .../algosdk/transaction/MethodCallParams.java | 383 +++++++++++----- .../cucumber/shared/TransactionSteps.java | 22 +- .../integration/AtomicTxnComposer.java | 147 +++--- .../algosdk/unit/AtomicTxnComposer.java | 85 ++-- .../com/algorand/algosdk/unit/Rekeying.java | 2 +- 15 files changed, 1085 insertions(+), 861 deletions(-) create mode 100644 src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java create mode 100644 src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java create mode 100644 src/main/java/com/algorand/algosdk/builder/transaction/StateSchemaSetter.java create mode 100644 src/main/java/com/algorand/algosdk/builder/transaction/TEALProgramSetter.java create mode 100644 src/main/java/com/algorand/algosdk/builder/transaction/TransactionParametersBuilder.java diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java index 124423696..e231ac4ec 100644 --- a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java +++ b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java @@ -9,7 +9,7 @@ import java.util.Objects; @SuppressWarnings("unchecked") -public abstract class ApplicationBaseTransactionBuilder> extends TransactionBuilder { +public abstract class ApplicationBaseTransactionBuilder> extends TransactionBuilder implements ApplicationCallReferencesSetter { private Transaction.OnCompletion onCompletion; private List applicationArgs; private List
accounts; @@ -38,9 +38,7 @@ protected void applyTo(Transaction txn) { if (foreignAssets != null) txn.foreignAssets = foreignAssets; } - /** - * ApplicationID is the application being interacted with, or 0 if creating a new application. - */ + @Override public T applicationId(Long applicationId) { this.applicationId = applicationId; return (T) this; @@ -75,27 +73,19 @@ public T argsBase64Encoded(List args) { return this.args(decodedArgs); } - /** - * Accounts lists the accounts (in addition to the sender) that may be accessed from the application logic. - */ + @Override public T accounts(List
accounts) { this.accounts = accounts; return (T) this; } - /** - * ForeignApps lists the applications (in addition to txn.ApplicationID) whose global states may be accessed by this - * application. The access is read-only. - */ + @Override public T foreignApps(List foreignApps) { this.foreignApps = foreignApps; return (T) this; } - /** - * ForeignAssets lists the assets whose global states may be accessed by this - * application. The access is read-only. - */ + @Override public T foreignAssets(List foreignAssets) { this.foreignAssets = foreignAssets; return (T) this; diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java new file mode 100644 index 000000000..ddf5b84a2 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java @@ -0,0 +1,30 @@ +package com.algorand.algosdk.builder.transaction; + +import java.util.List; + +import com.algorand.algosdk.crypto.Address; + +public interface ApplicationCallReferencesSetter> { + + /** + * ApplicationID is the application being interacted with, or 0 if creating a new application. + */ + public T applicationId(Long applicationId); + + /** + * Accounts lists the accounts (in addition to the sender) that may be accessed from the application logic. + */ + public T accounts(List
accounts); + + /** + * ForeignApps lists the applications (in addition to txn.ApplicationID) whose global states may be accessed by this + * application. The access is read-only. + */ + public T foreignApps(List foreignApps); + + /** + * ForeignAssets lists the assets whose global states may be accessed by this + * application. The access is read-only. + */ + public T foreignAssets(List foreignAssets); +} diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCreateTransactionBuilder.java b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCreateTransactionBuilder.java index 2c9b05fec..b0827f746 100644 --- a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCreateTransactionBuilder.java +++ b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCreateTransactionBuilder.java @@ -4,7 +4,7 @@ import com.algorand.algosdk.transaction.Transaction; @SuppressWarnings("unchecked") -public class ApplicationCreateTransactionBuilder> extends ApplicationUpdateTransactionBuilder { +public class ApplicationCreateTransactionBuilder> extends ApplicationUpdateTransactionBuilder implements StateSchemaSetter { private StateSchema localStateSchema; private StateSchema globalStateSchema; private Long extraPages = 0L; @@ -54,30 +54,19 @@ public T optIn(boolean optIn) { return (T) this; } - /** - * LocalStateSchema sets limits on the number of strings and integers that may be stored in an account's LocalState. - * for this application. The larger these limits are, the larger minimum balance must be maintained inside the - * account of any users who opt into this application. The LocalStateSchema is immutable. - */ + @Override public T localStateSchema(StateSchema localStateSchema) { this.localStateSchema = localStateSchema; return (T) this; } - /** - * GlobalStateSchema sets limits on the number of strings and integers that may be stored in the GlobalState. The - * larger these limits are, the larger minimum balance must be maintained inside the creator's account (in order to - * 'pay' for the state that can be used). The GlobalStateSchema is immutable. - */ + @Override public T globalStateSchema(StateSchema globalStateSchema) { this.globalStateSchema = globalStateSchema; return (T) this; } - /** - * extraPages allows you to rent extra pages of memory for the application. Each page is 2048 bytes of shared - * memory between approval and clear state programs. extraPages parameter must be an integer between 0 and 3 inclusive. - */ + @Override public T extraPages(Long extraPages) { if (extraPages == null || extraPages < 0 || extraPages > 3) { throw new IllegalArgumentException("extraPages must be an integer between 0 and 3 inclusive"); diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationUpdateTransactionBuilder.java b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationUpdateTransactionBuilder.java index 82b474187..cdd409c71 100644 --- a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationUpdateTransactionBuilder.java +++ b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationUpdateTransactionBuilder.java @@ -4,7 +4,7 @@ import com.algorand.algosdk.transaction.Transaction; @SuppressWarnings("unchecked") -public class ApplicationUpdateTransactionBuilder> extends ApplicationBaseTransactionBuilder { +public class ApplicationUpdateTransactionBuilder> extends ApplicationBaseTransactionBuilder implements TEALProgramSetter { private TEALProgram approvalProgram; private TEALProgram clearStateProgram; @@ -27,18 +27,13 @@ protected void applyTo(Transaction txn) { super.applyTo(txn); } - /** - * ApprovalProgram determines whether or not this ApplicationCall transaction will be approved or not. - */ + @Override public T approvalProgram(TEALProgram approvalProgram) { this.approvalProgram = approvalProgram; return (T) this; } - /** - * ClearStateProgram executes when a clear state ApplicationCall transaction is executed. This program may not - * reject the transaction, only update state. - */ + @Override public T clearStateProgram(TEALProgram clearStateProgram) { this.clearStateProgram = clearStateProgram; return (T) this; diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java b/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java new file mode 100644 index 000000000..012a6837a --- /dev/null +++ b/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java @@ -0,0 +1,171 @@ +package com.algorand.algosdk.builder.transaction; + +import com.algorand.algosdk.abi.Method; +import com.algorand.algosdk.crypto.Address; +import com.algorand.algosdk.crypto.TEALProgram; +import com.algorand.algosdk.logic.StateSchema; +import com.algorand.algosdk.transaction.MethodCallParams; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.transaction.TxnSigner; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +@SuppressWarnings("unchecked") +public class MethodCallTransactionBuilder> extends TransactionParametersBuilder implements StateSchemaSetter, TEALProgramSetter, ApplicationCallReferencesSetter { + protected Long appID; + protected Transaction.OnCompletion onCompletion; + protected Method method; + protected List methodArgs = new ArrayList<>(); + + protected List
foreignAccounts = new ArrayList<>(); + protected List foreignAssets = new ArrayList<>(); + protected List foreignApps = new ArrayList<>(); + + protected TEALProgram approvalProgram, clearStateProgram; + protected StateSchema localStateSchema; + protected StateSchema globalStateSchema; + protected Long extraPages; + + protected TxnSigner signer; + + /** + * Initialize a {@link MethodCallTransactionBuilder}. + */ + public static MethodCallTransactionBuilder Builder() { + return new MethodCallTransactionBuilder<>(); + } + + @Override + public T applicationId(Long applicationId) { + this.appID = applicationId; + return (T) this; + } + + /** + * This is the faux application type used to distinguish different application actions. Specifically, OnCompletion + * specifies what side effects this transaction will have if it successfully makes it into a block. + */ + public T onComplete(Transaction.OnCompletion op) { + this.onCompletion = op; + return (T) this; + } + + /** + * Specify the ABI method that this method call transaction will invoke. + */ + public T method(Method method) { + this.method = method; + return (T) this; + } + + /** + * Specify arguments for the ABI method invocation. + * + * This will reset the arguments list to what is passed in by the caller. + */ + public T methodArguments(List arguments) { + this.methodArgs = new ArrayList<>(arguments); + return (T) this; + } + + /** + * Specify arguments for the ABI method invocation. + * + * This will add the arguments passed in by the caller to the existing list of arguments. + */ + public T addMethodArguments(List arguments) { + this.methodArgs.addAll(arguments); + return (T) this; + } + + /** + * Specify arguments for the ABI method invocation. + * + * This will add the argument passed in by the caller to the existing list of arguments. + */ + public T addMethodArgument(Object argument) { + this.methodArgs.add(argument); + return (T) this; + } + + /** + * Specify the signer for this method call transaction. + */ + public T signer(TxnSigner signer) { + this.signer = signer; + return (T) this; + } + + @Override + public T accounts(List
accounts) { + if (accounts != null) + this.foreignAccounts = new ArrayList<>(new HashSet<>(accounts)); + else + this.foreignAccounts.clear(); + return (T) this; + } + + @Override + public T foreignApps(List foreignApps) { + if (foreignApps != null) + this.foreignApps = new ArrayList<>(new HashSet<>(foreignApps)); + else + this.foreignApps.clear(); + return (T) this; + } + + @Override + public T foreignAssets(List foreignAssets) { + if (foreignAssets != null) + this.foreignAssets = new ArrayList<>(new HashSet<>(foreignAssets)); + else + this.foreignAssets.clear(); + return (T) this; + } + + @Override + public T approvalProgram(TEALProgram approvalProgram) { + this.approvalProgram = approvalProgram; + return (T) this; + } + + @Override + public T clearStateProgram(TEALProgram clearStateProgram) { + this.clearStateProgram = clearStateProgram; + return (T) this; + } + + @Override + public T localStateSchema(StateSchema localStateSchema) { + this.localStateSchema = localStateSchema; + return (T) this; + } + + @Override + public T globalStateSchema(StateSchema globalStateSchema) { + this.globalStateSchema = globalStateSchema; + return (T) this; + } + + @Override + public T extraPages(Long extraPages) { + if (extraPages == null || extraPages < 0 || extraPages > 3) { + throw new IllegalArgumentException("extraPages must be an integer between 0 and 3 inclusive"); + } + this.extraPages = extraPages; + return (T) this; + } + + /** + * Build a MethodCallParams object. + */ + public MethodCallParams build() { + return new MethodCallParams( + appID, method, methodArgs, sender, onCompletion, note, lease, genesisID, genesisHash, + firstValid, lastValid, fee, flatFee, rekeyTo, signer, foreignAccounts, foreignAssets, foreignApps, + approvalProgram, clearStateProgram, globalStateSchema, localStateSchema, extraPages + ); + } +} diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/StateSchemaSetter.java b/src/main/java/com/algorand/algosdk/builder/transaction/StateSchemaSetter.java new file mode 100644 index 000000000..e7fa70575 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/builder/transaction/StateSchemaSetter.java @@ -0,0 +1,26 @@ +package com.algorand.algosdk.builder.transaction; + +import com.algorand.algosdk.logic.StateSchema; + +public interface StateSchemaSetter> { + + /** + * LocalStateSchema sets limits on the number of strings and integers that may be stored in an account's LocalState. + * for this application. The larger these limits are, the larger minimum balance must be maintained inside the + * account of any users who opt into this application. The LocalStateSchema is immutable. + */ + public T localStateSchema(StateSchema localStateSchema); + + /** + * GlobalStateSchema sets limits on the number of strings and integers that may be stored in the GlobalState. The + * larger these limits are, the larger minimum balance must be maintained inside the creator's account (in order to + * 'pay' for the state that can be used). The GlobalStateSchema is immutable. + */ + public T globalStateSchema(StateSchema globalStateSchema); + + /** + * extraPages allows you to rent extra pages of memory for the application. Each page is 2048 bytes of shared + * memory between approval and clear state programs. extraPages parameter must be an integer between 0 and 3 inclusive. + */ + public T extraPages(Long extraPages); +} diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/TEALProgramSetter.java b/src/main/java/com/algorand/algosdk/builder/transaction/TEALProgramSetter.java new file mode 100644 index 000000000..382aceeb7 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/builder/transaction/TEALProgramSetter.java @@ -0,0 +1,17 @@ +package com.algorand.algosdk.builder.transaction; + +import com.algorand.algosdk.crypto.TEALProgram; + +public interface TEALProgramSetter> { + + /** + * ApprovalProgram determines whether or not this ApplicationCall transaction will be approved or not. + */ + public T approvalProgram(TEALProgram approvalProgram); + + /** + * ClearStateProgram executes when a clear state ApplicationCall transaction is executed. This program may not + * reject the transaction, only update state. + */ + public T clearStateProgram(TEALProgram clearStateProgram); +} diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/TransactionBuilder.java b/src/main/java/com/algorand/algosdk/builder/transaction/TransactionBuilder.java index ffc53ebd1..8209f0e98 100644 --- a/src/main/java/com/algorand/algosdk/builder/transaction/TransactionBuilder.java +++ b/src/main/java/com/algorand/algosdk/builder/transaction/TransactionBuilder.java @@ -1,16 +1,10 @@ package com.algorand.algosdk.builder.transaction; import com.algorand.algosdk.account.Account; -import com.algorand.algosdk.algod.client.ApiException; -import com.algorand.algosdk.algod.client.api.AlgodApi; -import com.algorand.algosdk.algod.client.model.TransactionParams; -import com.algorand.algosdk.crypto.Address; import com.algorand.algosdk.crypto.Digest; import com.algorand.algosdk.transaction.Lease; import com.algorand.algosdk.transaction.Transaction; import com.algorand.algosdk.util.Encoder; -import com.algorand.algosdk.v2.client.common.Response; -import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; import java.math.BigInteger; import java.nio.charset.StandardCharsets; @@ -20,19 +14,9 @@ * TransactionBuilder has parameters common to all transactions types. */ @SuppressWarnings("unchecked") -public abstract class TransactionBuilder> { +public abstract class TransactionBuilder> extends TransactionParametersBuilder { protected final Transaction.Type type; - protected Address sender = null; - protected BigInteger fee = null; - protected BigInteger flatFee = null; - protected BigInteger firstValid = null; - protected BigInteger lastValid = null; - protected byte[] note = null; - protected byte[] lease = null; - protected Address rekeyTo = null; - protected String genesisID = null; - protected Digest genesisHash = null; protected Digest group = null; protected TransactionBuilder(Transaction.Type type) { @@ -93,407 +77,6 @@ final public Transaction build() { return txn; } - /** - * Query the V1 REST API with {@link AlgodApi} for Transaction Parameters: - * Initialize fee, genesisID, genesisHash, firstValid, lastValid by querying algod if not already set. - * @param client The backend client connection. - * @return This builder. - * @throws ApiException When the client fails to retrieve {@link TransactionParams} from the backend. - */ - public T lookupParams(AlgodApi client) throws ApiException { - TransactionParams params = client.transactionParams(); - return suggestedParams(params); - } - - /** - * Initialize fee, genesisID, genesisHash, firstValid, lastValid using {@link TransactionParams} if not already set. - * @param params The suggested transaction parameters. - * @return This builder. - */ - public T suggestedParams(TransactionParams params) { - if (this.fee == null) { - fee(params.getFee()); - } - if (this.genesisID == null) { - genesisID(params.getGenesisID()); - } - if (this.genesisHash == null) { - genesisHash(params.getGenesishashb64()); - } - if (this.firstValid == null) { - firstValid(params.getLastRound()); - } - if (this.lastValid == null) { - lastValid(params.getLastRound().add(BigInteger.valueOf(1000L))); - } - return (T) this; - } - - /** - * Query the V2 REST API with {@link com.algorand.algosdk.v2.client.common.AlgodClient} for Transaction Parameters: - * Initialize fee, genesisID, genesisHash, firstValid, lastValid using {@link TransactionParametersResponse} if not already set. - * @param client The backend client connection. - * @return This builder. - * @throws ApiException When the client fails to retrieve {@link TransactionParametersResponse} from the backend. - */ - public T lookupParams(com.algorand.algosdk.v2.client.common.AlgodClient client) throws Exception { - Response params = client.TransactionParams().execute(); - return suggestedParams(params.body()); - } - - /** - * Initialize fee, genesisID, genesisHash, firstValid and lastValid using {@link TransactionParametersResponse}. - * @param params The suggested transaction parameters. - * @return This builder. - */ - public T suggestedParams(TransactionParametersResponse params) { - fee(params.fee); - genesisID(params.genesisId); - genesisHash(params.genesisHash); - firstValid(params.lastRound); - lastValid(params.lastRound + 1000); - return (T) this; - } - - /** - * Set the transaction sender account. - * @param sender The sender account. - * @return This builder. - */ - public T sender(Address sender) { - this.sender = sender; - return (T) this; - } - - /** - * Set the transaction sender account in the human-readable address format. - * @param sender The sender account. - * @return This builder. - */ - public T sender(String sender) { - try { - this.sender = new Address(sender); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException(e); - } - return (T) this; - } - - /** - * Set the transaction sender account in the raw 32 byte format. - * @param sender The sender account. - * @return This builder. - */ - public T sender(byte[] sender) { - this.sender = new Address(sender); - return (T) this; - } - - /** - * Set the fee per bytes value. This value is multiplied by the estimated size of the transaction to reach a final transaction fee, or 1000, whichever is higher. - * This field cannot be combined with flatFee. - * @param fee The fee per byte. - * @return This builder. - */ - public T fee(BigInteger fee) { - this.fee = fee; - return (T) this; - } - - /** - * Set the fee per bytes value. This value is multiplied by the estimated size of the transaction to reach a final transaction fee, or 1000, whichever is higher. - * This field cannot be combined with flatFee. - * @param fee The fee per byte. - * @return This builder. - */ - public T fee(Integer fee) { - if (fee < 0) throw new IllegalArgumentException("fee cannot be a negative value"); - this.fee = BigInteger.valueOf(fee); - return (T) this; - } - - /** - * Set the fee per bytes value. This value is multiplied by the estimated size of the transaction to reach a final transaction fee, or 1000, whichever is higher. - * This field cannot be combined with flatFee. - * @param fee The fee per byte. - * @return This builder. - */ - public T fee(Long fee) { - if (fee < 0) throw new IllegalArgumentException("fee cannot be a negative value"); - this.fee = BigInteger.valueOf(fee); - return (T) this; - } - - /** - * Set the flatFee. This value will be used for the transaction fee. - * This fee may fall to zero but a group of N atomic transactions must - * still have a fee of at least N*MinTxnFee. - * This field cannot be combined with fee. - * @param flatFee The flatFee to use for the transaction. - * @return This builder. - */ - public T flatFee(BigInteger flatFee) { - this.flatFee = flatFee; - return (T) this; - } - - /** - * Set the flatFee. This value will be used for the transaction fee. - * This fee may fall to zero but a group of N atomic transactions must - * still have a fee of at least N*MinTxnFee. - * This field cannot be combined with fee. - * @param flatFee The flatFee to use for the transaction. - * @return This builder. - */ - public T flatFee(Integer flatFee) { - if (flatFee < 0) throw new IllegalArgumentException("flatFee cannot be a negative value"); - this.flatFee = BigInteger.valueOf(flatFee); - return (T) this; - } - - /** - * Set the flatFee. This value will be used for the transaction fee. - * This fee may fall to zero but a group of N atomic transactions must - * still have a fee of at least N*MinTxnFee. - * This field cannot be combined with fee. - * @param flatFee The flatFee to use for the transaction. - * @return This builder. - */ - public T flatFee(Long flatFee) { - if (flatFee < 0) throw new IllegalArgumentException("flatFee cannot be a negative value"); - this.flatFee = BigInteger.valueOf(flatFee); - return (T) this; - } - - /** - * Set the firstValid value, which is the first round that this transaction is valid for. - * @param firstValid The firstValid round. - * @return This builder. - */ - public T firstValid(BigInteger firstValid) { - this.firstValid = firstValid; - return (T) this; - } - - /** - * Set the firstValid value, which is the first round that this transaction is valid for. - * @param firstValid The firstValid round. - * @return This builder. - */ - public T firstValid(Integer firstValid) { - if (firstValid < 0) throw new IllegalArgumentException("firstValid cannot be a negative value"); - this.firstValid = BigInteger.valueOf(firstValid); - return (T) this; - } - - /** - * Set the firstValid value, which is the first round that this transaction is valid for. - * @param firstValid The firstValid round. - * @return This builder. - */ - public T firstValid(Long firstValid) { - if (firstValid < 0) throw new IllegalArgumentException("firstValid cannot be a negative value"); - this.firstValid = BigInteger.valueOf(firstValid); - return (T) this; - } - - /** - * Set the lastValid value, which is the last round that this transaction is valid for. - * @param lastValid The lastValid round. - * @return This builder. - */ - public T lastValid(BigInteger lastValid) { - this.lastValid = lastValid; - return (T) this; - } - - /** - * Set the lastValid value, which is the last round that this transaction is valid for. - * @param lastValid The lastValid round. - * @return This builder. - */ - public T lastValid(Integer lastValid) { - if (lastValid < 0) throw new IllegalArgumentException("lastValid cannot be a negative value"); - this.lastValid = BigInteger.valueOf(lastValid); - return (T) this; - } - - /** - * Set the lastValid value, which is the last round that this transaction is valid for. - * @param lastValid The lastValid round. - * @return This builder. - */ - public T lastValid(Long lastValid) { - if (lastValid < 0) throw new IllegalArgumentException("lastValid cannot be a negative value"); - this.lastValid = BigInteger.valueOf(lastValid); - return (T) this; - } - - /** - * Set the note field. It may containe 1024 bytes of free form data. - * @param note The note field. - * @return This builder. - */ - public T note(byte[] note) { - this.note = note; - return (T) this; - } - /** - * Set the note field using a UTF-8 encoded string. It may containe 1024 bytes of free form data. - * @param note The note field. - * @return This builder. - */ - public T noteUTF8(String note) { - this.note = note.getBytes(StandardCharsets.UTF_8); - return (T) this; - } - - /** - * Set the note field using a Base64 encoded string. It may containe 1024 bytes of free form data. - * @param note The note field. - * @return This builder. - */ - public T noteB64(String note) { - this.note = Encoder.decodeFromBase64(note); - return (T) this; - } - - /** - * Set the optional lease field. Lease enforces mutual exclusion of transactions. If this field is nonzero, then once the - * transaction is confirmed, it acquires the lease identified by the (Sender, Lease) pair of the transaction until - * the LastValid round passes. While this transaction possesses the lease, no other transaction specifying this - * lease can be confirmed. - * @param lease The lease. - * @return This builder. - */ - public T lease(Lease lease) { - this.lease = lease.getBytes(); - return (T) this; - } - - /** - * Set the optional lease field in its raw 32 byte form. Lease enforces mutual exclusion of transactions. If this field is nonzero, then once the - * transaction is confirmed, it acquires the lease identified by the (Sender, Lease) pair of the transaction until - * the LastValid round passes. While this transaction possesses the lease, no other transaction specifying this - * lease can be confirmed. - * @param lease The lease. - * @return This builder. - */ - public T lease(byte[] lease) { - this.lease = lease; - return (T) this; - } - - /** - * Set the optional lease field using a UTF-8 encoded string. Lease enforces mutual exclusion of transactions. If this field is nonzero, then once the - * transaction is confirmed, it acquires the lease identified by the (Sender, Lease) pair of the transaction until - * the LastValid round passes. While this transaction possesses the lease, no other transaction specifying this - * lease can be confirmed. - * @param lease The lease. - * @return This builder. - */ - public T leaseUTF8(String lease) { - this.lease = lease.getBytes(StandardCharsets.UTF_8); - return (T) this; - } - - /** - * Set the optional lease field using a base 64 encoded string. Lease enforces mutual exclusion of transactions. If this field is nonzero, then once the - * transaction is confirmed, it acquires the lease identified by the (Sender, Lease) pair of the transaction until - * the LastValid round passes. While this transaction possesses the lease, no other transaction specifying this - * lease can be confirmed. - * @param lease The lease. - * @return This builder. - */ - public T leaseB64(String lease) { - this.lease = Encoder.decodeFromBase64(lease); - return (T) this; - } - - /** - * Rekey to the sender account. - * @return This builder. - */ - public T rekey(Address rekeyTo) { - this.rekeyTo = rekeyTo; - return (T) this; - } - - /** - * Rekey to the account in the human-readable address format. - * @return This builder. - */ - public T rekey(String rekeyTo) { - try { - this.rekeyTo = new Address(rekeyTo); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException(e); - } - return (T) this; - } - - /** - * Rekey to the account in the raw 32 byte format. - * @return This builder. - */ - public T rekey(byte[] rekeyTo) { - this.rekeyTo = new Address(rekeyTo); - return (T) this; - } - - /** - * Set the optional genesisID. If set it is used to verify that the transaction is being submitted to the correct blockchain. - * @param genesisID The genesisID. - * @return This builder. - */ - public T genesisID(String genesisID) { - this.genesisID = genesisID; - return (T) this; - } - - /** - * Set the genesisHash field. It must match the hash of the genesis block and is used to verify that that the - * transaction is being submitted to the correct blockchain. - * @param genesisHash The genesisHash. - * @return This builder. - */ - public T genesisHash(Digest genesisHash) { - this.genesisHash = genesisHash; - return (T) this; - } - - /** - * Set the genesisHash field in its raw byte array format. It must match the hash of the genesis block and is used - * to verify that that the transaction is being submitted to the correct blockchain. - * @param genesisHash The genesisHash. - * @return This builder. - */ - public T genesisHash(byte[] genesisHash) { - this.genesisHash = new Digest(genesisHash); - return (T) this; - } - /** - * Set the genesisHash field in a UTF8 encoded format. It must match the hash of the genesis block and is used to - * verify that that the transaction is being submitted to the correct blockchain. - * @param genesisHash The genesisHash. - * @return This builder. - */ - public T genesisHashUTF8(String genesisHash) { - this.genesisHash = new Digest(genesisHash.getBytes(StandardCharsets.UTF_8)); - return (T) this; - } - - /** - * Set the genesisHash field in a base64 encoded format. It must match the hash of the genesis block and is used to - * verify that that the transaction is being submitted to the correct blockchain. - * @param genesisHash The genesisHash. - * @return This builder. - */ - public T genesisHashB64(String genesisHash) { - this.genesisHash = new Digest(Encoder.decodeFromBase64(genesisHash)); - return (T) this; - } - /** * Set the group field. When present indicates that this transaction is part of a transaction group and the value is * the sha512/256 hash of the transactions in that group. diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/TransactionParametersBuilder.java b/src/main/java/com/algorand/algosdk/builder/transaction/TransactionParametersBuilder.java new file mode 100644 index 000000000..0b3d2b9ba --- /dev/null +++ b/src/main/java/com/algorand/algosdk/builder/transaction/TransactionParametersBuilder.java @@ -0,0 +1,431 @@ +package com.algorand.algosdk.builder.transaction; + +import com.algorand.algosdk.algod.client.ApiException; +import com.algorand.algosdk.algod.client.api.AlgodApi; +import com.algorand.algosdk.algod.client.model.TransactionParams; +import com.algorand.algosdk.crypto.Address; +import com.algorand.algosdk.crypto.Digest; +import com.algorand.algosdk.transaction.Lease; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; + +@SuppressWarnings("unchecked") +public abstract class TransactionParametersBuilder> { + + protected Address sender = null; + protected BigInteger fee = null; + protected BigInteger flatFee = null; + protected BigInteger firstValid = null; + protected BigInteger lastValid = null; + protected byte[] note = null; + protected byte[] lease = null; + protected Address rekeyTo = null; + protected String genesisID = null; + protected Digest genesisHash = null; + + /** + * Query the V1 REST API with {@link AlgodApi} for Transaction Parameters: + * Initialize fee, genesisID, genesisHash, firstValid, lastValid by querying algod if not already set. + * @param client The backend client connection. + * @return This builder. + * @throws ApiException When the client fails to retrieve {@link TransactionParams} from the backend. + */ + public T lookupParams(AlgodApi client) throws ApiException { + TransactionParams params = client.transactionParams(); + return suggestedParams(params); + } + + /** + * Initialize fee, genesisID, genesisHash, firstValid, lastValid using {@link TransactionParams} if not already set. + * @param params The suggested transaction parameters. + * @return This builder. + */ + public T suggestedParams(TransactionParams params) { + if (this.fee == null) { + fee(params.getFee()); + } + if (this.genesisID == null) { + genesisID(params.getGenesisID()); + } + if (this.genesisHash == null) { + genesisHash(params.getGenesishashb64()); + } + if (this.firstValid == null) { + firstValid(params.getLastRound()); + } + if (this.lastValid == null) { + lastValid(params.getLastRound().add(BigInteger.valueOf(1000L))); + } + return (T) this; + } + + /** + * Query the V2 REST API with {@link com.algorand.algosdk.v2.client.common.AlgodClient} for Transaction Parameters: + * Initialize fee, genesisID, genesisHash, firstValid, lastValid using {@link TransactionParametersResponse} if not already set. + * @param client The backend client connection. + * @return This builder. + * @throws ApiException When the client fails to retrieve {@link TransactionParametersResponse} from the backend. + */ + public T lookupParams(com.algorand.algosdk.v2.client.common.AlgodClient client) throws Exception { + Response params = client.TransactionParams().execute(); + return suggestedParams(params.body()); + } + + /** + * Initialize fee, genesisID, genesisHash, firstValid and lastValid using {@link TransactionParametersResponse}. + * @param params The suggested transaction parameters. + * @return This builder. + */ + public T suggestedParams(TransactionParametersResponse params) { + fee(params.fee); + genesisID(params.genesisId); + genesisHash(params.genesisHash); + firstValid(params.lastRound); + lastValid(params.lastRound + 1000); + return (T) this; + } + + /** + * Set the transaction sender account. + * @param sender The sender account. + * @return This builder. + */ + public T sender(Address sender) { + this.sender = sender; + return (T) this; + } + + /** + * Set the transaction sender account in the human-readable address format. + * @param sender The sender account. + * @return This builder. + */ + public T sender(String sender) { + try { + this.sender = new Address(sender); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + return (T) this; + } + + /** + * Set the transaction sender account in the raw 32 byte format. + * @param sender The sender account. + * @return This builder. + */ + public T sender(byte[] sender) { + this.sender = new Address(sender); + return (T) this; + } + + /** + * Set the fee per bytes value. This value is multiplied by the estimated size of the transaction to reach a final transaction fee, or 1000, whichever is higher. + * This field cannot be combined with flatFee. + * @param fee The fee per byte. + * @return This builder. + */ + public T fee(BigInteger fee) { + this.fee = fee; + return (T) this; + } + + /** + * Set the fee per bytes value. This value is multiplied by the estimated size of the transaction to reach a final transaction fee, or 1000, whichever is higher. + * This field cannot be combined with flatFee. + * @param fee The fee per byte. + * @return This builder. + */ + public T fee(Integer fee) { + if (fee < 0) throw new IllegalArgumentException("fee cannot be a negative value"); + this.fee = BigInteger.valueOf(fee); + return (T) this; + } + + /** + * Set the fee per bytes value. This value is multiplied by the estimated size of the transaction to reach a final transaction fee, or 1000, whichever is higher. + * This field cannot be combined with flatFee. + * @param fee The fee per byte. + * @return This builder. + */ + public T fee(Long fee) { + if (fee < 0) throw new IllegalArgumentException("fee cannot be a negative value"); + this.fee = BigInteger.valueOf(fee); + return (T) this; + } + + /** + * Set the flatFee. This value will be used for the transaction fee. + * This fee may fall to zero but a group of N atomic transactions must + * still have a fee of at least N*MinTxnFee. + * This field cannot be combined with fee. + * @param flatFee The flatFee to use for the transaction. + * @return This builder. + */ + public T flatFee(BigInteger flatFee) { + this.flatFee = flatFee; + return (T) this; + } + + /** + * Set the flatFee. This value will be used for the transaction fee. + * This fee may fall to zero but a group of N atomic transactions must + * still have a fee of at least N*MinTxnFee. + * This field cannot be combined with fee. + * @param flatFee The flatFee to use for the transaction. + * @return This builder. + */ + public T flatFee(Integer flatFee) { + if (flatFee < 0) throw new IllegalArgumentException("flatFee cannot be a negative value"); + this.flatFee = BigInteger.valueOf(flatFee); + return (T) this; + } + + /** + * Set the flatFee. This value will be used for the transaction fee. + * This fee may fall to zero but a group of N atomic transactions must + * still have a fee of at least N*MinTxnFee. + * This field cannot be combined with fee. + * @param flatFee The flatFee to use for the transaction. + * @return This builder. + */ + public T flatFee(Long flatFee) { + if (flatFee < 0) throw new IllegalArgumentException("flatFee cannot be a negative value"); + this.flatFee = BigInteger.valueOf(flatFee); + return (T) this; + } + + /** + * Set the firstValid value, which is the first round that this transaction is valid for. + * @param firstValid The firstValid round. + * @return This builder. + */ + public T firstValid(BigInteger firstValid) { + this.firstValid = firstValid; + return (T) this; + } + + /** + * Set the firstValid value, which is the first round that this transaction is valid for. + * @param firstValid The firstValid round. + * @return This builder. + */ + public T firstValid(Integer firstValid) { + if (firstValid < 0) throw new IllegalArgumentException("firstValid cannot be a negative value"); + this.firstValid = BigInteger.valueOf(firstValid); + return (T) this; + } + + /** + * Set the firstValid value, which is the first round that this transaction is valid for. + * @param firstValid The firstValid round. + * @return This builder. + */ + public T firstValid(Long firstValid) { + if (firstValid < 0) throw new IllegalArgumentException("firstValid cannot be a negative value"); + this.firstValid = BigInteger.valueOf(firstValid); + return (T) this; + } + + /** + * Set the lastValid value, which is the last round that this transaction is valid for. + * @param lastValid The lastValid round. + * @return This builder. + */ + public T lastValid(BigInteger lastValid) { + this.lastValid = lastValid; + return (T) this; + } + + /** + * Set the lastValid value, which is the last round that this transaction is valid for. + * @param lastValid The lastValid round. + * @return This builder. + */ + public T lastValid(Integer lastValid) { + if (lastValid < 0) throw new IllegalArgumentException("lastValid cannot be a negative value"); + this.lastValid = BigInteger.valueOf(lastValid); + return (T) this; + } + + /** + * Set the lastValid value, which is the last round that this transaction is valid for. + * @param lastValid The lastValid round. + * @return This builder. + */ + public T lastValid(Long lastValid) { + if (lastValid < 0) throw new IllegalArgumentException("lastValid cannot be a negative value"); + this.lastValid = BigInteger.valueOf(lastValid); + return (T) this; + } + + /** + * Set the note field. It may containe 1024 bytes of free form data. + * @param note The note field. + * @return This builder. + */ + public T note(byte[] note) { + this.note = note; + return (T) this; + } + /** + * Set the note field using a UTF-8 encoded string. It may containe 1024 bytes of free form data. + * @param note The note field. + * @return This builder. + */ + public T noteUTF8(String note) { + this.note = note.getBytes(StandardCharsets.UTF_8); + return (T) this; + } + + /** + * Set the note field using a Base64 encoded string. It may containe 1024 bytes of free form data. + * @param note The note field. + * @return This builder. + */ + public T noteB64(String note) { + this.note = Encoder.decodeFromBase64(note); + return (T) this; + } + + /** + * Set the optional lease field. Lease enforces mutual exclusion of transactions. If this field is nonzero, then once the + * transaction is confirmed, it acquires the lease identified by the (Sender, Lease) pair of the transaction until + * the LastValid round passes. While this transaction possesses the lease, no other transaction specifying this + * lease can be confirmed. + * @param lease The lease. + * @return This builder. + */ + public T lease(Lease lease) { + this.lease = lease.getBytes(); + return (T) this; + } + + /** + * Set the optional lease field in its raw 32 byte form. Lease enforces mutual exclusion of transactions. If this field is nonzero, then once the + * transaction is confirmed, it acquires the lease identified by the (Sender, Lease) pair of the transaction until + * the LastValid round passes. While this transaction possesses the lease, no other transaction specifying this + * lease can be confirmed. + * @param lease The lease. + * @return This builder. + */ + public T lease(byte[] lease) { + this.lease = lease; + return (T) this; + } + + /** + * Set the optional lease field using a UTF-8 encoded string. Lease enforces mutual exclusion of transactions. If this field is nonzero, then once the + * transaction is confirmed, it acquires the lease identified by the (Sender, Lease) pair of the transaction until + * the LastValid round passes. While this transaction possesses the lease, no other transaction specifying this + * lease can be confirmed. + * @param lease The lease. + * @return This builder. + */ + public T leaseUTF8(String lease) { + this.lease = lease.getBytes(StandardCharsets.UTF_8); + return (T) this; + } + + /** + * Set the optional lease field using a base 64 encoded string. Lease enforces mutual exclusion of transactions. If this field is nonzero, then once the + * transaction is confirmed, it acquires the lease identified by the (Sender, Lease) pair of the transaction until + * the LastValid round passes. While this transaction possesses the lease, no other transaction specifying this + * lease can be confirmed. + * @param lease The lease. + * @return This builder. + */ + public T leaseB64(String lease) { + this.lease = Encoder.decodeFromBase64(lease); + return (T) this; + } + + /** + * Rekey to the sender account. + * @return This builder. + */ + public T rekey(Address rekeyTo) { + this.rekeyTo = rekeyTo; + return (T) this; + } + + /** + * Rekey to the account in the human-readable address format. + * @return This builder. + */ + public T rekey(String rekeyTo) { + try { + this.rekeyTo = new Address(rekeyTo); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + return (T) this; + } + + /** + * Rekey to the account in the raw 32 byte format. + * @return This builder. + */ + public T rekey(byte[] rekeyTo) { + this.rekeyTo = new Address(rekeyTo); + return (T) this; + } + + /** + * Set the optional genesisID. If set it is used to verify that the transaction is being submitted to the correct blockchain. + * @param genesisID The genesisID. + * @return This builder. + */ + public T genesisID(String genesisID) { + this.genesisID = genesisID; + return (T) this; + } + + /** + * Set the genesisHash field. It must match the hash of the genesis block and is used to verify that that the + * transaction is being submitted to the correct blockchain. + * @param genesisHash The genesisHash. + * @return This builder. + */ + public T genesisHash(Digest genesisHash) { + this.genesisHash = genesisHash; + return (T) this; + } + + /** + * Set the genesisHash field in its raw byte array format. It must match the hash of the genesis block and is used + * to verify that that the transaction is being submitted to the correct blockchain. + * @param genesisHash The genesisHash. + * @return This builder. + */ + public T genesisHash(byte[] genesisHash) { + this.genesisHash = new Digest(genesisHash); + return (T) this; + } + /** + * Set the genesisHash field in a UTF8 encoded format. It must match the hash of the genesis block and is used to + * verify that that the transaction is being submitted to the correct blockchain. + * @param genesisHash The genesisHash. + * @return This builder. + */ + public T genesisHashUTF8(String genesisHash) { + this.genesisHash = new Digest(genesisHash.getBytes(StandardCharsets.UTF_8)); + return (T) this; + } + + /** + * Set the genesisHash field in a base64 encoded format. It must match the hash of the genesis block and is used to + * verify that that the transaction is being submitted to the correct blockchain. + * @param genesisHash The genesisHash. + * @return This builder. + */ + public T genesisHashB64(String genesisHash) { + this.genesisHash = new Digest(Encoder.decodeFromBase64(genesisHash)); + return (T) this; + } +} diff --git a/src/main/java/com/algorand/algosdk/transaction/AtomicTransactionComposer.java b/src/main/java/com/algorand/algosdk/transaction/AtomicTransactionComposer.java index 9640e7872..c1f8ee901 100644 --- a/src/main/java/com/algorand/algosdk/transaction/AtomicTransactionComposer.java +++ b/src/main/java/com/algorand/algosdk/transaction/AtomicTransactionComposer.java @@ -1,14 +1,8 @@ package com.algorand.algosdk.transaction; import com.algorand.algosdk.abi.Method; -import com.algorand.algosdk.abi.TypeAddress; -import com.algorand.algosdk.abi.TypeUint; -import com.algorand.algosdk.abi.TypeTuple; import com.algorand.algosdk.abi.ABIType; -import com.algorand.algosdk.builder.transaction.ApplicationCallTransactionBuilder; -import com.algorand.algosdk.crypto.Address; import com.algorand.algosdk.crypto.Digest; -import com.algorand.algosdk.logic.StateSchema; import com.algorand.algosdk.util.Encoder; import com.algorand.algosdk.v2.client.Utils; import com.algorand.algosdk.v2.client.common.AlgodClient; @@ -19,9 +13,7 @@ import org.apache.commons.lang3.ArrayUtils; import java.io.IOException; -import java.math.BigInteger; import java.nio.ByteBuffer; -import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.ArrayList; import java.util.List; @@ -38,10 +30,6 @@ public enum Status { } public static final int MAX_GROUP_SIZE = 16; - // if the abi type argument number > 15, then the abi types after 14th should be wrapped in a tuple - private static final int MAX_ABI_ARG_TYPE_LEN = 15; - - private static final int FOREIGN_OBJ_ABI_UINT_SIZE = 8; private static final byte[] ABI_RET_HASH = new byte[]{0x15, 0x1f, 0x7c, 0x75}; @@ -105,164 +93,27 @@ public void addTransaction(TransactionWithSigner txnAndSigner) { this.transactionList.add(txnAndSigner); } - /** - * Add a value to an application call's foreign array. The addition will be as compact as possible, - * and this function will return an index that can be used to reference `objectToBeAdded` in `objectArray`. - * - * @param objectToBeAdded - The value to add to the array. If this value is already present in the array, - * it will not be added again. Instead, the existing index will be returned. - * @param objectArray - The existing foreign array. This input may be modified to append `valueToAdd`. - * @param zerothObject - If provided, this value indicated two things: the 0 value is special for this - * array, so all indexes into `objectArray` must start at 1; additionally, if `objectToBeAdded` equals - * `zerothValue`, then `objectToBeAdded` will not be added to the array, and instead the 0 indexes will - * be returned. - * @return An index that can be used to reference `valueToAdd` in `array`. - */ - private static int populateForeignArrayIndex(T objectToBeAdded, List objectArray, T zerothObject) { - if (objectToBeAdded.equals(zerothObject)) - return 0; - int startFrom = zerothObject == null ? 0 : 1; - int searchInListIndex = objectArray.indexOf(objectToBeAdded); - if (searchInListIndex != -1) - return startFrom + searchInListIndex; - objectArray.add(objectToBeAdded); - return objectArray.size() - 1 + startFrom; - } - - private static boolean checkTransactionType(TransactionWithSigner tws, String txnType) { - if (txnType.equals(Method.TxAnyType)) return true; - return tws.txn.type.toValue().equals(txnType); - } - /** * Add a smart contract method call to this atomic group. *

* An error will be thrown if the composer's status is not BUILDING, if adding this transaction * causes the current group to exceed MAX_GROUP_SIZE, or if the provided arguments are invalid * for the given method. + *

+ * For help creating a MethodCallParams object, see {@link com.algorand.algosdk.builder.transaction.MethodCallTransactionBuilder} */ public void addMethodCall(MethodCallParams methodCall) { if (!this.status.equals(Status.BUILDING)) throw new IllegalArgumentException("Atomic Transaction Composer must be in BUILDING stage"); - if (this.transactionList.size() + methodCall.method.getTxnCallCount() > MAX_GROUP_SIZE) + + List txns = methodCall.createTransactions(); + + if (this.transactionList.size() + txns.size() > MAX_GROUP_SIZE) throw new IllegalArgumentException( "Atomic Transaction Composer cannot exceed MAX_GROUP_SIZE = " + MAX_GROUP_SIZE + " transactions" ); - List encodedABIArgs = new ArrayList<>(); - encodedABIArgs.add(methodCall.method.getSelector()); - - List methodArgs = new ArrayList<>(); - List methodABIts = new ArrayList<>(); - - List tempTransWithSigner = new ArrayList<>(); - List
foreignAccounts = methodCall.foreignAccounts; - List foreignAssets = methodCall.foreignAssets; - List foreignApps = methodCall.foreignApps; - - Address senderAddress; - try { - senderAddress = new Address(methodCall.sender); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException(e); - } - - for (int i = 0; i < methodCall.method.args.size(); i++) { - Method.Arg argT = methodCall.method.args.get(i); - Object methodArg = methodCall.methodArgs.get(i); - if (argT.parsedType == null && methodArg instanceof TransactionWithSigner) { - TransactionWithSigner twsConverted = (TransactionWithSigner) methodArg; - if (!checkTransactionType(twsConverted, argT.type)) - throw new IllegalArgumentException( - "expected transaction type " + argT.type - + " not match with given " + twsConverted.txn.type.toValue() - ); - tempTransWithSigner.add((TransactionWithSigner) methodArg); - } else if (Method.RefArgTypes.contains(argT.type)) { - int index; - if (argT.type.equals(Method.RefTypeAccount)) { - TypeAddress abiAddressT = new TypeAddress(); - Address accountAddress = (Address) abiAddressT.decode(abiAddressT.encode(methodArg)); - index = populateForeignArrayIndex(accountAddress, foreignAccounts, senderAddress); - } else if (argT.type.equals(Method.RefTypeAsset) && methodArg instanceof BigInteger) { - TypeUint abiUintT = new TypeUint(64); - BigInteger assetID = (BigInteger) abiUintT.decode(abiUintT.encode(methodArg)); - index = populateForeignArrayIndex(assetID.longValue(), foreignAssets, null); - } else if (argT.type.equals(Method.RefTypeApplication) && methodArg instanceof BigInteger) { - TypeUint abiUintT = new TypeUint(64); - BigInteger appID = (BigInteger) abiUintT.decode(abiUintT.encode(methodArg)); - index = populateForeignArrayIndex(appID.longValue(), foreignApps, methodCall.appID); - } else - throw new IllegalArgumentException( - "cannot add method call in AtomicTransactionComposer: ForeignArray arg type not matching" - ); - methodArgs.add(index); - methodABIts.add(new TypeUint(FOREIGN_OBJ_ABI_UINT_SIZE)); - } else if (argT.parsedType != null) { - methodArgs.add(methodArg); - methodABIts.add(argT.parsedType); - } else - throw new IllegalArgumentException( - "error: the type of method argument is a transaction-type, but no transaction-with-signer provided" - ); - } - - if (methodArgs.size() > MAX_ABI_ARG_TYPE_LEN) { - List wrappedABITypeList = new ArrayList<>(); - List wrappedValueList = new ArrayList<>(); - - for (int i = MAX_ABI_ARG_TYPE_LEN - 1; i < methodArgs.size(); i++) { - wrappedABITypeList.add(methodABIts.get(i)); - wrappedValueList.add(methodArgs.get(i)); - } - - TypeTuple tupleT = new TypeTuple(wrappedABITypeList); - methodABIts = methodABIts.subList(0, MAX_ABI_ARG_TYPE_LEN - 1); - methodABIts.add(tupleT); - methodArgs = methodArgs.subList(0, MAX_ABI_ARG_TYPE_LEN - 1); - methodArgs.add(wrappedValueList); - } - - for (int i = 0; i < methodArgs.size(); i++) - encodedABIArgs.add(methodABIts.get(i).encode(methodArgs.get(i))); - - ApplicationCallTransactionBuilder txBuilder = ApplicationCallTransactionBuilder.Builder(); - - txBuilder - .suggestedParams(methodCall.suggestedParams) - .firstValid(methodCall.fv) - .lastValid(methodCall.lv) - .fee(methodCall.fee) - .sender(methodCall.sender) - .flatFee(methodCall.flatFee) - .applicationId(methodCall.appID) - .args(encodedABIArgs) - .note(methodCall.note) - .lease(methodCall.lease) - .accounts(foreignAccounts) - .foreignApps(foreignApps) - .foreignAssets(foreignAssets); - - if (methodCall.rekeyTo != null) - txBuilder.rekey(methodCall.rekeyTo); - - Transaction tx = txBuilder.build(); - - tx.onCompletion = methodCall.onCompletion; - tx.approvalProgram = methodCall.approvalProgram; - tx.clearStateProgram = methodCall.clearProgram; - - if (methodCall.globalInts != null && methodCall.globalBytes != null) - tx.globalStateSchema = new StateSchema(methodCall.globalInts, methodCall.globalBytes); - - if (methodCall.localInts != null && methodCall.globalBytes != null) - tx.localStateSchema = new StateSchema(methodCall.localInts, methodCall.localBytes); - - if (methodCall.extraPages != null) - tx.extraPages = methodCall.extraPages; - - this.transactionList.addAll(tempTransWithSigner); - this.transactionList.add(new TransactionWithSigner(tx, methodCall.signer)); + this.transactionList.addAll(txns); this.methodMap.put(this.transactionList.size() - 1, methodCall.method); } diff --git a/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java b/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java index 27bab6cd4..fda05a1e9 100644 --- a/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java +++ b/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java @@ -1,250 +1,389 @@ package com.algorand.algosdk.transaction; +import com.algorand.algosdk.abi.ABIType; import com.algorand.algosdk.abi.Method; +import com.algorand.algosdk.abi.TypeAddress; +import com.algorand.algosdk.abi.TypeTuple; +import com.algorand.algosdk.abi.TypeUint; import com.algorand.algosdk.algod.client.model.TransactionParams; +import com.algorand.algosdk.builder.transaction.ApplicationCallTransactionBuilder; +import com.algorand.algosdk.builder.transaction.MethodCallTransactionBuilder; import com.algorand.algosdk.crypto.Address; +import com.algorand.algosdk.crypto.Digest; import com.algorand.algosdk.crypto.TEALProgram; +import com.algorand.algosdk.logic.StateSchema; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; import java.math.BigInteger; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; +/** + * MethodCallParams is an object that holds all parameters necessary to invoke {@link AtomicTransactionComposer#addMethodCall(MethodCallParams)} + */ public class MethodCallParams { - public Long appID; - public Method method; - public List methodArgs; - public String sender, rekeyTo; - public Transaction.OnCompletion onCompletion; - public byte[] note, lease; - public BigInteger fv, lv, fee, flatFee; - TxnSigner signer; - public TransactionParams suggestedParams; - public List
foreignAccounts; - public List foreignAssets; - public List foreignApps; - - public TEALProgram approvalProgram, clearProgram; - public Long globalInts, globalBytes; - public Long localInts, localBytes; - public Long extraPages; - - public MethodCallParams(Long appID, Method method, List methodArgs, String sender, - TransactionParams sp, Transaction.OnCompletion onCompletion, byte[] note, byte[] lease, - BigInteger fv, BigInteger lv, BigInteger fee, BigInteger flatFee, - String rekeyTo, TxnSigner signer, + // if the abi type argument number > 15, then the abi types after 14th should be wrapped in a tuple + private static final int MAX_ABI_ARG_TYPE_LEN = 15; + + private static final int FOREIGN_OBJ_ABI_UINT_SIZE = 8; + + public final Long appID; + public final Transaction.OnCompletion onCompletion; + public final Method method; + public final List methodArgs; + + public final List
foreignAccounts; + public final List foreignAssets; + public final List foreignApps; + + public final TEALProgram approvalProgram, clearProgram; + public final StateSchema globalStateSchema, localStateSchema; + public final Long extraPages; + + public final TxnSigner signer; + + // from com.algorand.algosdk.builder.transaction.TransactionParametersBuilder + public final Address sender; + public final BigInteger fee; + public final BigInteger flatFee; + public final BigInteger firstValid; + public final BigInteger lastValid; + public final byte[] note; + public final byte[] lease; + public final Address rekeyTo; + public final String genesisID; + public final Digest genesisHash; + + /** + * NOTE: it's strongly suggested to use {@link com.algorand.algosdk.builder.transaction.MethodCallTransactionBuilder} + * instead of this constructor to create a new MethodCallParams object. + */ + public MethodCallParams(Long appID, Method method, List methodArgs, Address sender, + Transaction.OnCompletion onCompletion, byte[] note, byte[] lease, String genesisID, Digest genesisHash, + BigInteger firstValid, BigInteger lastValid, BigInteger fee, BigInteger flatFee, + Address rekeyTo, TxnSigner signer, List
fAccounts, List fAssets, List fApps, - TEALProgram approvalProgram, TEALProgram clearProgram, Long globalInts, Long globalBytes, - Long localInts, Long localBytes, Long extraPages) { - if (appID == null || method == null || sender == null || onCompletion == null || signer == null || sp == null) + TEALProgram approvalProgram, TEALProgram clearProgram, + StateSchema globalStateSchema, StateSchema localStateSchema, Long extraPages) { + if (appID == null || method == null || sender == null || onCompletion == null || signer == null || genesisID == null || genesisHash == null || firstValid == null || lastValid == null || (fee == null && flatFee == null)) throw new IllegalArgumentException("Method call builder error: some required field not added"); + if (fee != null && flatFee != null) + throw new IllegalArgumentException("Cannot set both fee and flatFee"); if (method.args.size() != methodArgs.size()) throw new IllegalArgumentException("Method call error: incorrect method arg number provided"); if (appID == 0) { - if (approvalProgram == null || clearProgram == null || globalInts == null || localInts == null || globalBytes == null || localBytes == null) + if (approvalProgram == null || clearProgram == null || globalStateSchema == null || localStateSchema == null) throw new IllegalArgumentException( "One of the following required parameters for application creation is missing: " + - "approvalProgram, clearProgram, numGlobalInts, numGlobalByteSlices, numLocalInts, numLocalByteSlice" + "approvalProgram, clearProgram, globalStateSchema, localStateSchema" ); } else if (onCompletion == Transaction.OnCompletion.UpdateApplicationOC) { if (approvalProgram == null || clearProgram == null) throw new IllegalArgumentException( "One of the following required parameters for OnApplicationComplete.UpdateApplicationOC is missing: approvalProgram, clearProgram" ); - if (globalBytes != null || globalInts != null || localBytes != null || localInts != null || extraPages != null) + if (globalStateSchema != null || localStateSchema != null || extraPages != null) throw new IllegalArgumentException( "One of the following application creation parameters were set on a non-creation call: " + - "numGlobalInts, numGlobalByteSlices, numLocalInts, numLocalByteSlices, extraPages" + "globalStateSchema, localStateSchema, extraPages" ); } else { - if (approvalProgram != null || clearProgram != null || globalInts != null || localInts != null || globalBytes != null || localBytes != null || extraPages != null) { + if (approvalProgram != null || clearProgram != null || globalStateSchema != null || localStateSchema != null || extraPages != null) { throw new IllegalArgumentException( "One of the following application creation parameters were set on a non-creation call: " + - "approvalProgram, clearProgram, numGlobalInts, numGlobalByteSlices, numLocalInts, numLocalByteSlices, extraPages" + "approvalProgram, clearProgram, globalStateSchema, localStateSchema, extraPages" ); } } this.appID = appID; this.method = method; - this.methodArgs = methodArgs; + this.methodArgs = new ArrayList<>(methodArgs); this.sender = sender; - this.suggestedParams = sp; this.onCompletion = onCompletion; this.note = note; this.lease = lease; - this.fv = fv; - this.lv = lv; + this.genesisID = genesisID; + this.genesisHash = genesisHash; + this.firstValid = firstValid; + this.lastValid = lastValid; this.fee = fee; this.flatFee = flatFee; this.rekeyTo = rekeyTo; this.signer = signer; - this.foreignAccounts = fAccounts; - this.foreignAssets = fAssets; - this.foreignApps = fApps; + this.foreignAccounts = new ArrayList<>(fAccounts); + this.foreignAssets = new ArrayList<>(fAssets); + this.foreignApps = new ArrayList<>(fApps); this.approvalProgram = approvalProgram; this.clearProgram = clearProgram; - this.globalBytes = globalBytes; - this.globalInts = globalInts; - this.localBytes = localBytes; - this.localInts = localInts; + this.globalStateSchema = globalStateSchema; + this.localStateSchema = localStateSchema; this.extraPages = extraPages; } - public static class Builder { - public Long appID; - public Method method; - public List methodArgs; - public String sender, rekeyTo; - public Transaction.OnCompletion onCompletion; - public byte[] note, lease; - public BigInteger fv, lv, fee, flatFee; - TxnSigner signer; - public TransactionParams sp; - public List
foreignAccounts = new ArrayList<>(); - public List foreignAssets = new ArrayList<>(); - public List foreignApps = new ArrayList<>(); - - public TEALProgram approvalProgram, clearProgram; - public Long globalInts, globalBytes; - public Long localInts, localBytes; - public Long extraPages; - - public Builder() { - this.onCompletion = Transaction.OnCompletion.NoOpOC; - this.methodArgs = new ArrayList<>(); + /** + * Create the transactions which will carry out the specified method call. + * + * The list of transactions returned by this function will have the same length as method.getTxnCallCount(). + */ + public List createTransactions() { + List encodedABIArgs = new ArrayList<>(); + encodedABIArgs.add(this.method.getSelector()); + + List methodArgs = new ArrayList<>(); + List methodABIts = new ArrayList<>(); + + List transactionArgs = new ArrayList<>(); + List
foreignAccounts = new ArrayList<>(this.foreignAccounts); + List foreignAssets = new ArrayList<>(this.foreignAssets); + List foreignApps = new ArrayList<>(this.foreignApps); + + for (int i = 0; i < this.method.args.size(); i++) { + Method.Arg argT = this.method.args.get(i); + Object methodArg = this.methodArgs.get(i); + if (argT.parsedType == null && methodArg instanceof TransactionWithSigner) { + TransactionWithSigner twsConverted = (TransactionWithSigner) methodArg; + if (!checkTransactionType(twsConverted, argT.type)) + throw new IllegalArgumentException( + "expected transaction type " + argT.type + + " not match with given " + twsConverted.txn.type.toValue() + ); + transactionArgs.add((TransactionWithSigner) methodArg); + } else if (Method.RefArgTypes.contains(argT.type)) { + int index; + if (argT.type.equals(Method.RefTypeAccount)) { + TypeAddress abiAddressT = new TypeAddress(); + Address accountAddress = (Address) abiAddressT.decode(abiAddressT.encode(methodArg)); + index = populateForeignArrayIndex(accountAddress, foreignAccounts, this.sender); + } else if (argT.type.equals(Method.RefTypeAsset) && methodArg instanceof BigInteger) { + TypeUint abiUintT = new TypeUint(64); + BigInteger assetID = (BigInteger) abiUintT.decode(abiUintT.encode(methodArg)); + index = populateForeignArrayIndex(assetID.longValue(), foreignAssets, null); + } else if (argT.type.equals(Method.RefTypeApplication) && methodArg instanceof BigInteger) { + TypeUint abiUintT = new TypeUint(64); + BigInteger appID = (BigInteger) abiUintT.decode(abiUintT.encode(methodArg)); + index = populateForeignArrayIndex(appID.longValue(), foreignApps, this.appID); + } else + throw new IllegalArgumentException( + "cannot add method call in AtomicTransactionComposer: ForeignArray arg type not matching" + ); + methodArgs.add(index); + methodABIts.add(new TypeUint(FOREIGN_OBJ_ABI_UINT_SIZE)); + } else if (argT.parsedType != null) { + methodArgs.add(methodArg); + methodABIts.add(argT.parsedType); + } else + throw new IllegalArgumentException( + "error: the type of method argument is a transaction-type, but no transaction-with-signer provided" + ); } + if (methodArgs.size() > MAX_ABI_ARG_TYPE_LEN) { + List wrappedABITypeList = new ArrayList<>(); + List wrappedValueList = new ArrayList<>(); + + for (int i = MAX_ABI_ARG_TYPE_LEN - 1; i < methodArgs.size(); i++) { + wrappedABITypeList.add(methodABIts.get(i)); + wrappedValueList.add(methodArgs.get(i)); + } + + TypeTuple tupleT = new TypeTuple(wrappedABITypeList); + methodABIts = methodABIts.subList(0, MAX_ABI_ARG_TYPE_LEN - 1); + methodABIts.add(tupleT); + methodArgs = methodArgs.subList(0, MAX_ABI_ARG_TYPE_LEN - 1); + methodArgs.add(wrappedValueList); + } + + for (int i = 0; i < methodArgs.size(); i++) + encodedABIArgs.add(methodABIts.get(i).encode(methodArgs.get(i))); + + ApplicationCallTransactionBuilder txBuilder = ApplicationCallTransactionBuilder.Builder(); + + txBuilder + .firstValid(this.firstValid) + .lastValid(this.lastValid) + .genesisHash(this.genesisHash) + .genesisID(this.genesisID) + .fee(this.fee) + .flatFee(this.flatFee) + .note(this.note) + .lease(this.lease) + .rekey(this.rekeyTo) + .sender(this.sender) + .applicationId(this.appID) + .args(encodedABIArgs) + .accounts(foreignAccounts) + .foreignApps(foreignApps) + .foreignAssets(foreignAssets); + + Transaction tx = txBuilder.build(); + + // must apply these fields manually, as they are not exposed in the base ApplicationCallTransactionBuilder + tx.onCompletion = this.onCompletion; + tx.approvalProgram = this.approvalProgram; + tx.clearStateProgram = this.clearProgram; + + if (this.globalStateSchema != null) + tx.globalStateSchema = this.globalStateSchema; + if (this.localStateSchema != null) + tx.localStateSchema = this.localStateSchema; + if (this.extraPages != null) + tx.extraPages = this.extraPages; + + TransactionWithSigner methodCall = new TransactionWithSigner(tx, this.signer); + transactionArgs.add(methodCall); + + return transactionArgs; + } + + private static boolean checkTransactionType(TransactionWithSigner tws, String txnType) { + if (txnType.equals(Method.TxAnyType)) return true; + return tws.txn.type.toValue().equals(txnType); + } + + /** + * Add a value to an application call's foreign array. The addition will be as compact as possible, + * and this function will return an index that can be used to reference `objectToBeAdded` in `objectArray`. + * + * @param objectToBeAdded - The value to add to the array. If this value is already present in the array, + * it will not be added again. Instead, the existing index will be returned. + * @param objectArray - The existing foreign array. This input may be modified to append `valueToAdd`. + * @param zerothObject - If provided, this value indicated two things: the 0 value is special for this + * array, so all indexes into `objectArray` must start at 1; additionally, if `objectToBeAdded` equals + * `zerothValue`, then `objectToBeAdded` will not be added to the array, and instead the 0 indexes will + * be returned. + * @return An index that can be used to reference `valueToAdd` in `array`. + */ + private static int populateForeignArrayIndex(T objectToBeAdded, List objectArray, T zerothObject) { + if (objectToBeAdded.equals(zerothObject)) + return 0; + int startFrom = zerothObject == null ? 0 : 1; + int searchInListIndex = objectArray.indexOf(objectToBeAdded); + if (searchInListIndex != -1) + return startFrom + searchInListIndex; + objectArray.add(objectToBeAdded); + return objectArray.size() - 1 + startFrom; + } + + /** + * Deprecated, use {@link com.algorand.algosdk.builder.transaction.MethodCallTransactionBuilder#Builder()} instead. + */ + @Deprecated + public static class Builder extends MethodCallTransactionBuilder { + public Builder setAppID(Long appID) { - this.appID = appID; - return this; + return this.applicationId(appID); } public Builder setMethod(Method method) { - this.method = method; - return this; + return this.method(method); } public Builder addMethodArgs(Object ma) { - this.methodArgs.add(ma); - return this; + return this.addMethodArgument(ma); } public Builder setSender(String sender) { - this.sender = sender; - return this; + return this.sender(sender); } public Builder setSuggestedParams(TransactionParams sp) { - this.sp = sp; - return this; + return this.suggestedParams(sp); + } + + public Builder setSuggestedParams(TransactionParametersResponse sp) { + return this.suggestedParams(sp); } public Builder setOnComplete(Transaction.OnCompletion op) { - this.onCompletion = op; - return this; + return this.onComplete(op); } public Builder setNote(byte[] note) { - this.note = note; - return this; + return this.note(note); } public Builder setLease(byte[] lease) { - this.lease = lease; - return this; + return this.lease(lease); } public Builder setRekeyTo(String rekeyTo) { - this.rekeyTo = rekeyTo; - return this; + return this.rekey(rekeyTo); } public Builder setSigner(TxnSigner signer) { - this.signer = signer; - return this; + return this.signer(signer); } public Builder setFirstValid(BigInteger fv) { - this.fv = fv; - return this; + return this.firstValid(fv); } public Builder setLastValid(BigInteger lv) { - this.lv = lv; - return this; + return this.lastValid(lv); } public Builder setFee(BigInteger fee) { - this.fee = fee; - return this; + return this.fee(fee); } public Builder setFlatFee(BigInteger flatFee) { - this.flatFee = flatFee; - return this; + return this.flatFee(flatFee); } public Builder setForeignAccounts(List
fAccounts) { - if (fAccounts == null) return this; - this.foreignAccounts = new ArrayList<>(new HashSet<>(fAccounts)); - return this; + return this.accounts(fAccounts); } public Builder setForeignAssets(List fAssets) { - if (fAssets == null) return this; - this.foreignAssets = new ArrayList<>(new HashSet<>(fAssets)); - return this; + return this.foreignAssets(fAssets); } public Builder setForeignApps(List fApps) { - if (fApps == null) return this; - this.foreignApps = new ArrayList<>(new HashSet<>(fApps)); - return this; + return this.foreignApps(fApps); } public Builder setApprovalProgram(TEALProgram approvalProgram) { - if (approvalProgram == null) return this; - this.approvalProgram = approvalProgram; - return this; + return this.approvalProgram(approvalProgram); } public Builder setClearProgram(TEALProgram clearProgram) { - if (clearProgram == null) return this; - this.clearProgram = clearProgram; - return this; + return this.clearStateProgram(clearProgram); } public Builder setGlobalInts(Long globalInts) { - this.globalInts = globalInts; + if (this.globalStateSchema == null) { + return this.globalStateSchema(new StateSchema(globalInts, 0L)); + } + this.globalStateSchema.numUint = BigInteger.valueOf(globalInts); return this; } public Builder setGlobalBytes(Long globalBytes) { - this.globalBytes = globalBytes; + if (this.globalStateSchema == null) { + return this.globalStateSchema(new StateSchema(0L, globalBytes)); + } + this.globalStateSchema.numByteSlice = BigInteger.valueOf(globalBytes); return this; } public Builder setLocalInts(Long localInts) { - this.localInts = localInts; + if (this.localStateSchema == null) { + return this.localStateSchema(new StateSchema(localInts, 0L)); + } + this.localStateSchema.numUint = BigInteger.valueOf(localInts); return this; } public Builder setLocalBytes(Long localBytes) { - this.localBytes = localBytes; + if (this.localStateSchema == null) { + return this.localStateSchema(new StateSchema(0L, localBytes)); + } + this.localStateSchema.numByteSlice = BigInteger.valueOf(localBytes); return this; } public Builder setExtraPages(Long extraPages) { - this.extraPages = extraPages; - return this; - } - - public MethodCallParams build() { - return new MethodCallParams( - appID, method, methodArgs, sender, sp, onCompletion, note, lease, - fv, lv, fee, flatFee, rekeyTo, signer, foreignAccounts, foreignAssets, foreignApps, - approvalProgram, clearProgram, globalInts, globalBytes, localInts, localBytes, extraPages - ); + return this.extraPages(extraPages); } } } diff --git a/src/test/java/com/algorand/algosdk/cucumber/shared/TransactionSteps.java b/src/test/java/com/algorand/algosdk/cucumber/shared/TransactionSteps.java index 4539ef7c7..e44e68e9a 100644 --- a/src/test/java/com/algorand/algosdk/cucumber/shared/TransactionSteps.java +++ b/src/test/java/com/algorand/algosdk/cucumber/shared/TransactionSteps.java @@ -11,7 +11,6 @@ import java.util.Arrays; import java.util.Map; -import com.algorand.algosdk.algod.client.model.TransactionParams; import com.algorand.algosdk.builder.transaction.ApplicationBaseTransactionBuilder; import com.algorand.algosdk.builder.transaction.KeyRegistrationTransactionBuilder; import com.algorand.algosdk.builder.transaction.PaymentTransactionBuilder; @@ -30,14 +29,9 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import java.io.IOException; -import java.math.BigInteger; -import java.security.NoSuchAlgorithmException; -import java.util.Map; import static com.algorand.algosdk.util.ConversionUtils.*; import static com.algorand.algosdk.util.ResourceUtils.loadTEALProgramFromFile; -import static org.assertj.core.api.Assertions.assertThat; public class TransactionSteps { public Base base; @@ -45,9 +39,7 @@ public class TransactionSteps { public SignedTransaction signedTransaction = null; public TransientAccount transAcc; - public TransactionParams suggestedParams; public BigInteger fee, flatFee, fv, lv; - public boolean isFlatFee; public byte[] genesisHash; public String genesisID; @@ -57,7 +49,7 @@ public TransactionSteps(Base b) { @When("I build an application transaction with operation {string}, application-id {long}, sender {string}, approval-program {string}, clear-program {string}, global-bytes {long}, global-ints {long}, local-bytes {long}, local-ints {long}, app-args {string}, foreign-apps {string}, foreign-assets {string}, app-accounts {string}, fee {long}, first-valid {long}, last-valid {long}, genesis-hash {string}, extra-pages {long}") public void buildApplicationTransactions(String operation, Long applicationId, String sender, String approvalProgramFile, String clearProgramFile, Long globalBytes, Long globalInts, Long localBytes, Long localInts, String appArgs, String foreignApps, String foreignAssets, String appAccounts, Long fee, Long firstValid, Long lastValid, String genesisHash, Long extraPages) throws Exception { - ApplicationBaseTransactionBuilder builder = null; + ApplicationBaseTransactionBuilder builder = null; // Create builder and apply builder-specific parameters switch (operation) { @@ -134,12 +126,6 @@ public void buildKeyregTransaction(String sender, String nonpart, Integer voteFi KeyRegistrationTransactionBuilder builder = Transaction.KeyRegistrationTransactionBuilder(); - if (isFlatFee) { - builder = builder.flatFee(flatFee); - } else { - builder = builder.fee(fee); - } - if (!votePk.isEmpty()) builder = builder.participationPublicKeyBase64(votePk); @@ -154,6 +140,8 @@ public void buildKeyregTransaction(String sender, String nonpart, Integer voteFi .lastValid(lv) .genesisHash(genesisHash) .genesisID(genesisID) + .fee(fee) + .flatFee(flatFee) .sender(sender) .nonparticipation(nonpart.equals("true")) .voteFirst(voteFirst) @@ -164,7 +152,7 @@ public void buildKeyregTransaction(String sender, String nonpart, Integer voteFi @Given("suggested transaction parameters fee {int}, flat-fee {string}, first-valid {int}, last-valid {int}, genesis-hash {string}, genesis-id {string}") public void suggested_transaction_parameters_fee_flat_fee_first_valid_last_valid_genesis_hash_genesis_id(Integer int1, String string, Integer int2, Integer int3, String string2, String string3) { - isFlatFee = Boolean.parseBoolean(string); + boolean isFlatFee = Boolean.parseBoolean(string); fee = isFlatFee ? null : BigInteger.valueOf(int1); flatFee = isFlatFee ? BigInteger.valueOf(int1) : null; @@ -172,8 +160,6 @@ public void suggested_transaction_parameters_fee_flat_fee_first_valid_last_valid genesisID = string3; fv = BigInteger.valueOf(int2); lv = BigInteger.valueOf(int3); - - suggestedParams = new TransactionParams().genesishashb64(genesisHash).genesisID(genesisID).lastRound(fv); } @When("I build a payment transaction with sender {string}, receiver {string}, amount {int}, close remainder to {string}") diff --git a/src/test/java/com/algorand/algosdk/integration/AtomicTxnComposer.java b/src/test/java/com/algorand/algosdk/integration/AtomicTxnComposer.java index 2fa5d5990..bc152a5e9 100644 --- a/src/test/java/com/algorand/algosdk/integration/AtomicTxnComposer.java +++ b/src/test/java/com/algorand/algosdk/integration/AtomicTxnComposer.java @@ -2,12 +2,17 @@ import com.algorand.algosdk.abi.ABIType; import com.algorand.algosdk.abi.Method; +import com.algorand.algosdk.builder.transaction.MethodCallTransactionBuilder; +import com.algorand.algosdk.crypto.Address; import com.algorand.algosdk.crypto.Digest; 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.v2.client.model.PendingTransactionResponse; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; + import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; @@ -33,7 +38,7 @@ public class AtomicTxnComposer { TxnSigner transSigner; TransactionWithSigner transWithSigner; Method method; - MethodCallParams.Builder optionBuilder; + MethodCallTransactionBuilder optionBuilder; AtomicTransactionComposer.ExecuteResult execRes; SplitAndProcessMethodArgs abiArgProcessor; Long appID; @@ -55,15 +60,14 @@ public void addNonce(String nonce) { } @Given("suggested transaction parameters from the algod v2 client") - public void suggested_transaction_parameters_from_the_algod_v2_client() throws IOException { - base.aClient(); - base.getParams(); - - transSteps.suggestedParams = base.params; - transSteps.genesisHash = transSteps.suggestedParams.getGenesishashb64(); - transSteps.genesisID = transSteps.suggestedParams.getGenesisID(); - transSteps.fee = transSteps.suggestedParams.getFee(); - transSteps.fv = transSteps.suggestedParams.getLastRound(); + public void suggested_transaction_parameters_from_the_algod_v2_client() throws Exception { + TransactionParametersResponse response = applications.base.aclv2.TransactionParams().execute().body(); + + transSteps.genesisHash = response.genesisHash; + transSteps.genesisID = response.genesisId; + transSteps.fee = BigInteger.valueOf(response.fee); + transSteps.flatFee = null; + transSteps.fv = BigInteger.valueOf(response.lastRound); transSteps.lv = transSteps.fv.add(BigInteger.valueOf(1000)); } @@ -110,20 +114,20 @@ public void i_create_the_method_object_from_method_signature(String string) { @When("I create a new method arguments array.") public void i_create_a_new_method_arguments_array() { - optionBuilder = new MethodCallParams.Builder(); + optionBuilder = MethodCallTransactionBuilder.Builder(); abiArgProcessor = new SplitAndProcessMethodArgs(method); } @When("I append the current transaction with signer to the method arguments array.") public void i_append_the_current_transaction_with_signer_to_the_method_arguments_array() { - this.optionBuilder.addMethodArgs(this.transWithSigner); + this.optionBuilder.addMethodArgument(this.transWithSigner); } @When("I append the encoded arguments {string} to the method arguments array.") public void i_append_the_encoded_arguments_to_the_method_arguments_array(String string) { List processedABIArgs = abiArgProcessor.splitAndProcessMethodArgs(string, applications.rememberedAppIds); for (Object arg : processedABIArgs) - this.optionBuilder.addMethodArgs(arg); + this.optionBuilder.addMethodArgument(arg); } @Then("I execute the current transaction group with the composer.") @@ -319,74 +323,80 @@ public void the_composer_should_have_a_status_of(String string) { @When("I add a method call with the transient account, the current application, suggested params, on complete {string}, current transaction signer, current method arguments.") public void i_add_a_method_call_with_the_signing_account_the_current_application_suggested_params_on_complete_current_transaction_signer_current_method_arguments(String onComplete) { - String senderAddress = applications.transientAccount.transientAccount.getAddress().toString(); + Address senderAddress = applications.transientAccount.transientAccount.getAddress(); composerMethods.add(method); optionBuilder - .setOnComplete(Transaction.OnCompletion.String(onComplete)) - .setSender(senderAddress) - .setSigner(transSigner) - .setAppID(applications.appId) - .setMethod(method) - .setSuggestedParams(transSteps.suggestedParams) - .setFirstValid(transSteps.fv) - .setLastValid(transSteps.lv) - .setFee(transSteps.fee); + .onComplete(Transaction.OnCompletion.String(onComplete)) + .sender(senderAddress) + .signer(transSigner) + .applicationId(applications.appId) + .method(method) + .firstValid(transSteps.fv) + .lastValid(transSteps.lv) + .genesisHash(transSteps.genesisHash) + .genesisID(transSteps.genesisID) + .fee(transSteps.fee) + .flatFee(transSteps.flatFee); MethodCallParams optionBuild = optionBuilder.build(); atc.addMethodCall(optionBuild); } @When("I add a nonced method call with the transient account, the current application, suggested params, on complete {string}, current transaction signer, current method arguments.") public void i_add_a_nonced_method_call_with_the_transient_account_the_current_application_suggested_params_on_complete_current_transaction_signer_current_method_arguments(String onComplete) { - String senderAddress = applications.transientAccount.transientAccount.getAddress().toString(); + Address senderAddress = applications.transientAccount.transientAccount.getAddress(); composerMethods.add(method); optionBuilder - .setOnComplete(Transaction.OnCompletion.String(onComplete)) - .setSender(senderAddress) - .setSigner(transSigner) - .setAppID(applications.appId) - .setMethod(method) - .setNote(("I should be unique thanks to this nonce: " + nonce).getBytes(StandardCharsets.UTF_8)) - .setSuggestedParams(transSteps.suggestedParams) - .setFirstValid(transSteps.fv) - .setLastValid(transSteps.lv) - .setFee(transSteps.fee); + .onComplete(Transaction.OnCompletion.String(onComplete)) + .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); MethodCallParams optionBuild = optionBuilder.build(); atc.addMethodCall(optionBuild); } @When("I add a method call with the transient account, the current application, suggested params, on complete {string}, current transaction signer, current method arguments, approval-program {string}, clear-program {string}, global-bytes {int}, global-ints {int}, local-bytes {int}, local-ints {int}, extra-pages {int}.") - public void i_add_a_method_call_with_the_transient_account_the_current_application_suggested_params_on_complete_current_transaction_signer_current_method_arguments_approval_program_clear_program_global_bytes_global_ints_local_bytes_local_ints_extra_pages(String string, String string2, String string3, Integer int1, Integer int2, Integer int3, Integer int4, Integer int5) { + public void i_add_a_method_call_with_the_transient_account_the_current_application_suggested_params_on_complete_current_transaction_signer_current_method_arguments_approval_program_clear_program_global_bytes_global_ints_local_bytes_local_ints_extra_pages(String onCompleteString, String approvalProgramPath, String clearProgramPath, Integer globalBytes, Integer globalInts, Integer localBytes, Integer localInts, Integer extraPages) { byte[] tealApproval, tealClear; try { - tealApproval = ResourceUtils.readResource(string2); - tealClear = ResourceUtils.readResource(string3); + tealApproval = ResourceUtils.readResource(approvalProgramPath); + tealClear = ResourceUtils.readResource(clearProgramPath); } catch (Exception e) { throw new IllegalArgumentException("cannot read resource from specified TEAL files"); } composerMethods.add(method); - String senderAddress = applications.transientAccount.transientAccount.getAddress().toString(); + Address senderAddress = applications.transientAccount.transientAccount.getAddress(); + + StateSchema globalSchema = new StateSchema(globalInts, globalBytes); + StateSchema localSchema = new StateSchema(localInts, localBytes); optionBuilder - .setOnComplete(Transaction.OnCompletion.String(string)) - .setSender(senderAddress) - .setSigner(transSigner) - .setAppID(appID) - .setMethod(method) - .setSuggestedParams(this.transSteps.suggestedParams) - .setFirstValid(this.transSteps.fv) - .setLastValid(this.transSteps.lv) - .setFee(this.transSteps.fee) - .setFlatFee(this.transSteps.flatFee) - .setApprovalProgram(new TEALProgram(tealApproval)) - .setClearProgram(new TEALProgram(tealClear)) - .setGlobalBytes(int1.longValue()) - .setGlobalInts(int2.longValue()) - .setLocalBytes(int3.longValue()) - .setLocalInts(int4.longValue()) - .setExtraPages(int5.longValue()); + .onComplete(Transaction.OnCompletion.String(onCompleteString)) + .sender(senderAddress) + .signer(transSigner) + .applicationId(appID) + .method(method) + .firstValid(transSteps.fv) + .lastValid(transSteps.lv) + .genesisHash(transSteps.genesisHash) + .genesisID(transSteps.genesisID) + .fee(transSteps.fee) + .flatFee(transSteps.flatFee) + .approvalProgram(new TEALProgram(tealApproval)) + .clearStateProgram(new TEALProgram(tealClear)) + .globalStateSchema(globalSchema) + .localStateSchema(localSchema) + .extraPages(extraPages.longValue()); MethodCallParams optionBuild = optionBuilder.build(); atc.addMethodCall(optionBuild); } @@ -402,21 +412,22 @@ public void i_add_a_method_call_with_the_transient_account_the_current_applicati } composerMethods.add(method); - String senderAddress = applications.transientAccount.transientAccount.getAddress().toString(); + Address senderAddress = applications.transientAccount.transientAccount.getAddress(); optionBuilder - .setOnComplete(Transaction.OnCompletion.String(string)) - .setSender(senderAddress) - .setSigner(transSigner) - .setAppID(applications.appId) - .setMethod(method) - .setSuggestedParams(this.transSteps.suggestedParams) - .setFirstValid(this.transSteps.fv) - .setLastValid(this.transSteps.lv) - .setFee(this.transSteps.fee) - .setFlatFee(this.transSteps.flatFee) - .setApprovalProgram(new TEALProgram(tealApproval)) - .setClearProgram(new TEALProgram(tealClear)); + .onComplete(Transaction.OnCompletion.String(string)) + .sender(senderAddress) + .signer(transSigner) + .applicationId(applications.appId) + .method(method) + .firstValid(transSteps.fv) + .lastValid(transSteps.lv) + .genesisHash(transSteps.genesisHash) + .genesisID(transSteps.genesisID) + .fee(transSteps.fee) + .flatFee(transSteps.flatFee) + .approvalProgram(new TEALProgram(tealApproval)) + .clearStateProgram(new TEALProgram(tealClear)); MethodCallParams optionBuild = optionBuilder.build(); atc.addMethodCall(optionBuild); } diff --git a/src/test/java/com/algorand/algosdk/unit/AtomicTxnComposer.java b/src/test/java/com/algorand/algosdk/unit/AtomicTxnComposer.java index 0f0b8f56e..2fd8cfb8a 100644 --- a/src/test/java/com/algorand/algosdk/unit/AtomicTxnComposer.java +++ b/src/test/java/com/algorand/algosdk/unit/AtomicTxnComposer.java @@ -3,8 +3,10 @@ import static org.assertj.core.api.Assertions.assertThat; import com.algorand.algosdk.account.Account; +import com.algorand.algosdk.builder.transaction.MethodCallTransactionBuilder; 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.Encoder; import com.algorand.algosdk.util.ResourceUtils; @@ -35,7 +37,7 @@ public class AtomicTxnComposer { List signedTxnsGathered; TransactionWithSigner txnWithSigner; - MethodCallParams.Builder optionBuilder; + MethodCallTransactionBuilder optionBuilder; SplitAndProcessMethodArgs abiArgProcessor = null; public AtomicTxnComposer(Base b, ABIJson methodABI_, TransactionSteps steps) { @@ -74,13 +76,13 @@ public void i_create_a_transaction_with_signer_with_the_current_transaction() { @When("I create a new method arguments array.") public void i_create_a_new_method_arguments_array() { - this.optionBuilder = new MethodCallParams.Builder(); + this.optionBuilder = MethodCallTransactionBuilder.Builder(); abiArgProcessor = new SplitAndProcessMethodArgs(methodABI.method); } @When("I append the current transaction with signer to the method arguments array.") public void i_append_the_current_transaction_with_signer_to_the_method_arguments_array() { - this.optionBuilder.addMethodArgs(this.txnWithSigner); + this.optionBuilder.addMethodArgument(this.txnWithSigner); } @When("I add the current transaction with signer to the composer.") @@ -90,56 +92,59 @@ public void i_add_the_current_transaction_with_signer_to_the_composer() { @When("I append the encoded arguments {string} to the method arguments array.") public void i_append_the_encoded_arguments_to_the_method_arguments_array(String string) { - List processedABIArgs = abiArgProcessor.splitAndProcessMethodArgs(string, new ArrayList<>()); + List processedABIArgs = abiArgProcessor.splitAndProcessMethodArgs(string, new ArrayList()); for (Object arg : processedABIArgs) - this.optionBuilder.addMethodArgs(arg); + this.optionBuilder.addMethodArgument(arg); } @When("I add a method call with the signing account, the current application, suggested params, on complete {string}, current transaction signer, current method arguments.") public void i_add_a_method_call_with_the_signing_account_the_current_application_suggested_params_on_complete_current_transaction_signer_current_method_arguments(String string) { optionBuilder - .setOnComplete(Transaction.OnCompletion.String(string)) - .setSender(signingAccount.getAddress().toString()) - .setSigner(txnSigner) - .setAppID(appID.longValue()) - .setMethod(methodABI.method) - .setSuggestedParams(this.transSteps.suggestedParams) - .setFirstValid(this.transSteps.fv) - .setLastValid(this.transSteps.lv) - .setFee(this.transSteps.fee) - .setFlatFee(this.transSteps.flatFee); + .onComplete(Transaction.OnCompletion.String(string)) + .sender(signingAccount.getAddress()) + .signer(txnSigner) + .applicationId(appID.longValue()) + .method(methodABI.method) + .firstValid(this.transSteps.fv) + .lastValid(this.transSteps.lv) + .fee(this.transSteps.fee) + .flatFee(this.transSteps.flatFee) + .genesisHash(this.transSteps.genesisHash) + .genesisID(this.transSteps.genesisID); MethodCallParams optionBuild = optionBuilder.build(); atc.addMethodCall(optionBuild); } @When("I add a method call with the signing account, the current application, suggested params, on complete {string}, current transaction signer, current method arguments, approval-program {string}, clear-program {string}, global-bytes {int}, global-ints {int}, local-bytes {int}, local-ints {int}, extra-pages {int}.") - public void i_add_a_method_call_with_the_signing_account_the_current_application_suggested_params_on_complete_current_transaction_signer_current_method_arguments_approval_program_clear_program_global_bytes_global_ints_local_bytes_local_ints_extra_pages(String string, String string2, String string3, Integer int1, Integer int2, Integer int3, Integer int4, Integer int5) { + public void i_add_a_method_call_with_the_signing_account_the_current_application_suggested_params_on_complete_current_transaction_signer_current_method_arguments_approval_program_clear_program_global_bytes_global_ints_local_bytes_local_ints_extra_pages(String onCompleteString, String approvalProgramPath, String clearProgramPath, Integer globalBytes, Integer globalInts, Integer localBytes, Integer localInts, Integer extraPages) { byte[] tealApproval, tealClear; try { - tealApproval = ResourceUtils.readResource(string2); - tealClear = ResourceUtils.readResource(string3); + tealApproval = ResourceUtils.readResource(approvalProgramPath); + tealClear = ResourceUtils.readResource(clearProgramPath); } catch (Exception e) { throw new IllegalArgumentException("cannot read resource from specified TEAL files"); } + StateSchema globalSchema = new StateSchema(globalInts, globalBytes); + StateSchema localSchema = new StateSchema(localInts, localBytes); + optionBuilder - .setOnComplete(Transaction.OnCompletion.String(string)) - .setSender(signingAccount.getAddress().toString()) - .setSigner(txnSigner) - .setAppID(appID.longValue()) - .setMethod(methodABI.method) - .setSuggestedParams(this.transSteps.suggestedParams) - .setFirstValid(this.transSteps.fv) - .setLastValid(this.transSteps.lv) - .setFee(this.transSteps.fee) - .setFlatFee(this.transSteps.flatFee) - .setApprovalProgram(new TEALProgram(tealApproval)) - .setClearProgram(new TEALProgram(tealClear)) - .setGlobalBytes(int1.longValue()) - .setGlobalInts(int2.longValue()) - .setLocalBytes(int3.longValue()) - .setLocalInts(int4.longValue()) - .setExtraPages(int5.longValue()); + .onComplete(Transaction.OnCompletion.String(onCompleteString)) + .sender(signingAccount.getAddress()) + .signer(txnSigner) + .applicationId(appID.longValue()) + .method(methodABI.method) + .firstValid(this.transSteps.fv) + .lastValid(this.transSteps.lv) + .genesisHash(this.transSteps.genesisHash) + .genesisID(this.transSteps.genesisID) + .fee(this.transSteps.fee) + .flatFee(this.transSteps.flatFee) + .approvalProgram(new TEALProgram(tealApproval)) + .clearStateProgram(new TEALProgram(tealClear)) + .globalStateSchema(globalSchema) + .localStateSchema(localSchema) + .extraPages(extraPages.longValue()); MethodCallParams optionBuild = optionBuilder.build(); atc.addMethodCall(optionBuild); } @@ -169,12 +174,12 @@ public void i_gather_signatures_with_the_composer() throws Exception { @Then("the base64 encoded signed transactions should equal {string}") public void the_base64_encoded_signed_transactions_should_equal(String string) throws IOException { - String[] splitStr = string.split(","); - for (int i = 0; i < splitStr.length; i++) { - String subStr = splitStr[i]; + List expectedSignTxns = new ArrayList<>(); + for (String subStr : string.split(",")) { SignedTransaction decodedMsgPack = Encoder.decodeFromMsgPack(subStr, SignedTransaction.class); - SignedTransaction actual = signedTxnsGathered.get(i); - assertThat(actual).isEqualTo(decodedMsgPack); + expectedSignTxns.add(decodedMsgPack); } + + assertThat(signedTxnsGathered).isEqualTo(expectedSignTxns); } } diff --git a/src/test/java/com/algorand/algosdk/unit/Rekeying.java b/src/test/java/com/algorand/algosdk/unit/Rekeying.java index be2beded9..69298530b 100644 --- a/src/test/java/com/algorand/algosdk/unit/Rekeying.java +++ b/src/test/java/com/algorand/algosdk/unit/Rekeying.java @@ -24,7 +24,7 @@ public class Rekeying { @SuppressWarnings("rawtypes") - private TransactionBuilder transactionBuilder; + private TransactionBuilder transactionBuilder; private SignedTransaction signedTransaction; public Account account; SignedTransaction stx; From 42ff1565385f7999b630c6341cc8ce9a2a498513 Mon Sep 17 00:00:00 2001 From: John Lee Date: Thu, 2 Jun 2022 15:14:13 -0400 Subject: [PATCH 7/8] Bump version and update CHANGELOG --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ README.md | 2 +- pom.xml | 2 +- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cebd8b68..f3ac0949c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +# 1.15.0 + +## What's Changed +* Better error message on encoding exception. by @winder in https://github.com/algorand/java-algorand-sdk/pull/258 +* Add WaitForConfirmation function by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/274 +* Bug fix for `logs` on `PendingTransactionResponse` by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/275 +* ABI Interaction Support for JAVA SDK by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/268 +* Resolve #229 and #279 on upgrading jackson packages by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/281 +* Support Foreign objects as ABI arguments by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/277 +* Implement circle ci by @bricerisingalgorand in https://github.com/algorand/java-algorand-sdk/pull/293 +* C2C Feature and Testing by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/290 +* add new key reg txn field by @shiqizng in https://github.com/algorand/java-algorand-sdk/pull/266 +* Update README.md by @rotemh in https://github.com/algorand/java-algorand-sdk/pull/297 +* Update langspec for teal6 by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/298 +* Unlimited assets regenerated code by @algonautshant in https://github.com/algorand/java-algorand-sdk/pull/302 +* add error message with atomic transaction composer by @Reguzzoni in https://github.com/algorand/java-algorand-sdk/pull/311 +* ran updated generator against latest json specs by @barnjamin in https://github.com/algorand/java-algorand-sdk/pull/307 +* adding createDryrun method by @barnjamin in https://github.com/algorand/java-algorand-sdk/pull/284 +* Dryrun printer by @barnjamin in https://github.com/algorand/java-algorand-sdk/pull/305 +* adding foreign app address to dryrun creator by @barnjamin in https://github.com/algorand/java-algorand-sdk/pull/315 +* Rerun code generation based on new models by @Eric-Warehime in https://github.com/algorand/java-algorand-sdk/pull/321 +* Excluding gh-pages branch from cicd by @algojack in https://github.com/algorand/java-algorand-sdk/pull/325 +* Build: Add SDK code generation workflow by @Eric-Warehime in https://github.com/algorand/java-algorand-sdk/pull/327 +* Add branch info to sdk gen action by @Eric-Warehime in https://github.com/algorand/java-algorand-sdk/pull/330 +* Generate updated client API code by @algoidurovic in https://github.com/algorand/java-algorand-sdk/pull/318 +* Introduce unified `MethodCallTransactionBuilder` & deprecate `MethodCallParams.Builder` by @jasonpaulos in https://github.com/algorand/java-algorand-sdk/pull/324 + +## New Contributors +* @Reguzzoni made their first contribution in https://github.com/algorand/java-algorand-sdk/pull/311 +* @barnjamin made their first contribution in https://github.com/algorand/java-algorand-sdk/pull/307 +* @Eric-Warehime made their first contribution in https://github.com/algorand/java-algorand-sdk/pull/321 +* @algoidurovic made their first contribution in https://github.com/algorand/java-algorand-sdk/pull/318 + # 1.14.0 ## Added - Add foreign app address to dryrun creator (#315) diff --git a/README.md b/README.md index f0fb9f3b1..5986d8301 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Maven: com.algorand algosdk - 1.14.0 + 1.15.0 ``` diff --git a/pom.xml b/pom.xml index 734777fa0..7ff75af14 100755 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.algorand algosdk - 1.14.0 + 1.15.0 jar ${project.groupId}:${project.artifactId} From 48fd03890a68a32f951cff346ee62e4ce29ff70e Mon Sep 17 00:00:00 2001 From: John Lee Date: Thu, 2 Jun 2022 15:45:34 -0400 Subject: [PATCH 8/8] Update CHANGELOG --- CHANGELOG.md | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3ac0949c..99bfe2ee9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,6 @@ # 1.15.0 ## What's Changed -* Better error message on encoding exception. by @winder in https://github.com/algorand/java-algorand-sdk/pull/258 -* Add WaitForConfirmation function by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/274 -* Bug fix for `logs` on `PendingTransactionResponse` by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/275 -* ABI Interaction Support for JAVA SDK by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/268 -* Resolve #229 and #279 on upgrading jackson packages by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/281 -* Support Foreign objects as ABI arguments by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/277 -* Implement circle ci by @bricerisingalgorand in https://github.com/algorand/java-algorand-sdk/pull/293 -* C2C Feature and Testing by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/290 -* add new key reg txn field by @shiqizng in https://github.com/algorand/java-algorand-sdk/pull/266 -* Update README.md by @rotemh in https://github.com/algorand/java-algorand-sdk/pull/297 -* Update langspec for teal6 by @ahangsu in https://github.com/algorand/java-algorand-sdk/pull/298 -* Unlimited assets regenerated code by @algonautshant in https://github.com/algorand/java-algorand-sdk/pull/302 -* add error message with atomic transaction composer by @Reguzzoni in https://github.com/algorand/java-algorand-sdk/pull/311 -* ran updated generator against latest json specs by @barnjamin in https://github.com/algorand/java-algorand-sdk/pull/307 -* adding createDryrun method by @barnjamin in https://github.com/algorand/java-algorand-sdk/pull/284 -* Dryrun printer by @barnjamin in https://github.com/algorand/java-algorand-sdk/pull/305 -* adding foreign app address to dryrun creator by @barnjamin in https://github.com/algorand/java-algorand-sdk/pull/315 * Rerun code generation based on new models by @Eric-Warehime in https://github.com/algorand/java-algorand-sdk/pull/321 * Excluding gh-pages branch from cicd by @algojack in https://github.com/algorand/java-algorand-sdk/pull/325 * Build: Add SDK code generation workflow by @Eric-Warehime in https://github.com/algorand/java-algorand-sdk/pull/327 @@ -26,8 +9,6 @@ * Introduce unified `MethodCallTransactionBuilder` & deprecate `MethodCallParams.Builder` by @jasonpaulos in https://github.com/algorand/java-algorand-sdk/pull/324 ## New Contributors -* @Reguzzoni made their first contribution in https://github.com/algorand/java-algorand-sdk/pull/311 -* @barnjamin made their first contribution in https://github.com/algorand/java-algorand-sdk/pull/307 * @Eric-Warehime made their first contribution in https://github.com/algorand/java-algorand-sdk/pull/321 * @algoidurovic made their first contribution in https://github.com/algorand/java-algorand-sdk/pull/318