diff --git a/.gitignore b/.gitignore
index 5ac92e3bd..47d5bc3d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@ temp
# Environment information
examples/.env
+test-harness/
tests/cucumber/features/
tests/cucumber/browser/build
tests/browser/bundle.*
diff --git a/.test-env b/.test-env
new file mode 100644
index 000000000..eff6690c0
--- /dev/null
+++ b/.test-env
@@ -0,0 +1,14 @@
+# Configs for testing repo download:
+SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing"
+SDK_TESTING_BRANCH="feature/box-storage"
+SDK_TESTING_HARNESS="test-harness"
+
+VERBOSE_HARNESS=0
+
+# WARNING: If set to 1, new features will be LOST when downloading the test harness.
+# REGARDLESS: modified features are ALWAYS overwritten.
+REMOVE_LOCAL_FEATURES=0
+
+# WARNING: Be careful when turning on the next variable.
+# In that case you'll need to provide all variables expected by `algorand-sdk-testing`'s `.env`
+OVERWRITE_TESTING_ENVIRONMENT=0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0b1167a1..d0059da0a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+# v1.19.1
+
+### Enhancements
+
+- API: Support attaching signatures to standard and multisig transactions by @jdtzmn in https://github.com/algorand/js-algorand-sdk/pull/595
+- AVM: Consolidate TEAL and AVM versions by @michaeldiamant in https://github.com/algorand/js-algorand-sdk/pull/609
+- Testing: Use Dev mode network for cucumber tests by @algochoi in https://github.com/algorand/js-algorand-sdk/pull/614
+
# v1.19.0
## What's Changed
diff --git a/Makefile b/Makefile
index d33c1b011..6f9bb461a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,28 @@
+UNIT_TAGS := "$(subst :, or ,$(shell awk '{print $2}' tests/cucumber/unit.tags | paste -s -d: -))"
+INTEGRATIONS_TAGS := "$(subst :, or ,$(shell awk '{print $2}' tests/cucumber/integration.tags | paste -s -d: -))"
+
unit:
- node_modules/.bin/cucumber-js --tags "@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.tealsign or @unit.dryrun or @unit.applications or @unit.applications.boxes or @unit.responses or @unit.transactions or @unit.transactions.keyreg or @unit.transactions.payment or @unit.responses.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.responses.unlimited_assets or @unit.indexer.ledger_refactoring or @unit.algod.ledger_refactoring or @unit.dryrun.trace.application or @unit.sourcemap" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js
+ node_modules/.bin/cucumber-js --tags $(UNIT_TAGS) tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js
integration:
- node_modules/.bin/cucumber-js --tags "@algod or @assets or @auction or @kmd or @send or @indexer or @rekey_v1 or @send.keyregtxn or @dryrun or @compile or @applications or @indexer.applications or @applications.verified or @applications.boxes or @indexer.231 or @abi or @c2c or @compile.sourcemap" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js
+ node_modules/.bin/cucumber-js --tags $(INTEGRATIONS_TAGS) tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js
+
+# The following assumes that all cucumber steps are defined in `./tests/cucumber/steps/steps.js` and begin past line 135 of that file.
+# Please note any deviations of the above before presuming correctness.
+display-all-js-steps:
+ tail -n +135 tests/cucumber/steps/steps.js | grep -v '^ *//' | awk "/(Given|Then|When)/,/',/" | grep -E "\'.+\'" | sed "s/^[^']*'\([^']*\)'.*/\1/g"
+
+harness:
+ ./test-harness.sh
+
+docker-build:
+ docker build -t js-sdk-testing -f tests/cucumber/docker/Dockerfile $(CURDIR) --build-arg TEST_BROWSER --build-arg CI=true
+
+docker-run:
+ docker ps -a
+ docker run -it --network host js-sdk-testing:latest
-docker-test:
- ./tests/cucumber/docker/run_docker.sh
+docker-test: harness docker-build docker-run
format:
npm run format
diff --git a/README.md b/README.md
index d28deac83..24bd04c7a 100644
--- a/README.md
+++ b/README.md
@@ -22,8 +22,8 @@ Include a minified browser bundle directly in your HTML like so:
```html
```
@@ -32,8 +32,8 @@ or
```html
```
diff --git a/package-lock.json b/package-lock.json
index 95eab649b..f533394fa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "algosdk",
- "version": "1.19.0",
+ "version": "1.19.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "algosdk",
- "version": "1.19.0",
+ "version": "1.19.1",
"license": "MIT",
"dependencies": {
"algo-msgpack-with-bigint": "^2.1.1",
diff --git a/package.json b/package.json
index 9648348b2..7a3234197 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "algosdk",
- "version": "1.19.0",
+ "version": "1.19.1",
"description": "The official JavaScript SDK for Algorand",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
diff --git a/src/client/v2/algod/algod.ts b/src/client/v2/algod/algod.ts
index dbe15ca6c..df0b86a19 100644
--- a/src/client/v2/algod/algod.ts
+++ b/src/client/v2/algod/algod.ts
@@ -6,6 +6,7 @@ import AccountApplicationInformation from './accountApplicationInformation';
import Block from './block';
import Compile from './compile';
import Dryrun from './dryrun';
+import Genesis from './genesis';
import GetAssetByID from './getAssetByID';
import GetApplicationByID from './getApplicationByID';
import GetApplicationBoxByName from './getApplicationBoxByName';
@@ -14,19 +15,20 @@ import HealthCheck from './healthCheck';
import PendingTransactionInformation from './pendingTransactionInformation';
import PendingTransactions from './pendingTransactions';
import PendingTransactionsByAddress from './pendingTransactionsByAddress';
+import GetTransactionProof from './getTransactionProof';
import SendRawTransaction from './sendRawTransaction';
import Status from './status';
import StatusAfterBlock from './statusAfterBlock';
import SuggestedParams from './suggestedParams';
import Supply from './supply';
import Versions from './versions';
-import Genesis from './genesis';
-import Proof from './proof';
import { BaseHTTPClient } from '../../baseHTTPClient';
import {
AlgodTokenHeader,
CustomTokenHeader,
} from '../../urlTokenBaseHTTPClient';
+import LightBlockHeaderProof from './lightBlockHeaderProof';
+import StateProof from './stateproof';
/**
* Algod client connects an application to the Algorand blockchain. The algod client requires a valid algod REST endpoint IP address and algod token from an Algorand node that is connected to the network you plan to interact with.
@@ -488,7 +490,7 @@ export default class AlgodClient extends ServiceClient {
* ```typescript
* const round = 18038133;
* const txId = "MEUOC4RQJB23CQZRFRKYEI6WBO73VTTPST5A7B3S5OKBUY6LFUDA";
- * const proof = await algodClient.getProof(round, txId).do();
+ * const proof = await algodClient.getTransactionProof(round, txId).do();
* ```
*
* [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2blocksroundtransactionstxidproof)
@@ -496,7 +498,39 @@ export default class AlgodClient extends ServiceClient {
* @param txID - The transaction ID for which to generate a proof.
* @category GET
*/
- getProof(round: number, txID: string) {
- return new Proof(this.c, this.intDecoding, round, txID);
+ getTransactionProof(round: number, txID: string) {
+ return new GetTransactionProof(this.c, this.intDecoding, round, txID);
+ }
+
+ /**
+ * Gets a proof for a given light block header inside a state proof commitment.
+ *
+ * #### Example
+ * ```typescript
+ * const round = 11111111;
+ * const lightBlockHeaderProof = await algodClient.getLightBlockHeaderProof(round).do();
+ * ```
+ *
+ * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2#get-v2blocksroundlightheaderproof)
+ * @param round
+ */
+ getLightBlockHeaderProof(round: number) {
+ return new LightBlockHeaderProof(this.c, this.intDecoding, round);
+ }
+
+ /**
+ * Gets a state proof that covers a given round.
+ *
+ * #### Example
+ * ```typescript
+ * const round = 11111111;
+ * const stateProof = await algodClient.getStateProof(round).do();
+ * ```
+ *
+ * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2#get-v2stateproofsround)
+ * @param round
+ */
+ getStateProof(round: number) {
+ return new StateProof(this.c, this.intDecoding, round);
}
}
diff --git a/src/client/v2/algod/getTransactionProof.ts b/src/client/v2/algod/getTransactionProof.ts
new file mode 100644
index 000000000..398039a43
--- /dev/null
+++ b/src/client/v2/algod/getTransactionProof.ts
@@ -0,0 +1,43 @@
+import JSONRequest from '../jsonrequest';
+import HTTPClient from '../../client';
+import IntDecoding from '../../../types/intDecoding';
+
+export default class GetTransactionProof extends JSONRequest {
+ constructor(
+ c: HTTPClient,
+ intDecoding: IntDecoding,
+ private round: number,
+ private txID: string
+ ) {
+ super(c, intDecoding);
+
+ this.round = round;
+ this.txID = txID;
+ }
+
+ path() {
+ return `/v2/blocks/${this.round}/transactions/${this.txID}/proof`;
+ }
+
+ /**
+ * Exclude assets and application data from results
+ * The type of hash function used to create the proof, must be one of: "sha512_256", "sha256"
+ *
+ * #### Example
+ * ```typescript
+ * const hashType = "sha256";
+ * const round = 123456;
+ * const txId = "abc123;
+ * const txProof = await algodClient.getTransactionProof(round, txId)
+ * .hashType(hashType)
+ * .do();
+ * ```
+ *
+ * @param hashType
+ * @category query
+ */
+ hashType(hashType: string) {
+ this.query.hashtype = hashType;
+ return this;
+ }
+}
diff --git a/src/client/v2/algod/lightBlockHeaderProof.ts b/src/client/v2/algod/lightBlockHeaderProof.ts
new file mode 100644
index 000000000..ac3794c36
--- /dev/null
+++ b/src/client/v2/algod/lightBlockHeaderProof.ts
@@ -0,0 +1,15 @@
+import JSONRequest from '../jsonrequest';
+import HTTPClient from '../../client';
+import IntDecoding from '../../../types/intDecoding';
+
+export default class LightBlockHeaderProof extends JSONRequest {
+ constructor(c: HTTPClient, intDecoding: IntDecoding, private round: number) {
+ super(c, intDecoding);
+
+ this.round = round;
+ }
+
+ path() {
+ return `/v2/blocks/${this.round}/lightheader/proof`;
+ }
+}
diff --git a/src/client/v2/algod/models/types.ts b/src/client/v2/algod/models/types.ts
index bdeeaa1e1..a5bdf4674 100644
--- a/src/client/v2/algod/models/types.ts
+++ b/src/client/v2/algod/models/types.ts
@@ -1714,6 +1714,54 @@ export class EvalDeltaKeyValue extends BaseModel {
}
}
+/**
+ * Proof of membership and position of a light block header.
+ */
+export class LightBlockHeaderProof extends BaseModel {
+ /**
+ * The index of the light block header in the vector commitment tree
+ */
+ public index: number | bigint;
+
+ /**
+ * The encoded proof.
+ */
+ public proof: Uint8Array;
+
+ /**
+ * Represents the depth of the tree that is being proven, i.e. the number of edges
+ * from a leaf to the root.
+ */
+ public treedepth: number | bigint;
+
+ /**
+ * Creates a new `LightBlockHeaderProof` object.
+ * @param index - The index of the light block header in the vector commitment tree
+ * @param proof - The encoded proof.
+ * @param treedepth - Represents the depth of the tree that is being proven, i.e. the number of edges
+ * from a leaf to the root.
+ */
+ constructor(
+ index: number | bigint,
+ proof: string | Uint8Array,
+ treedepth: number | bigint
+ ) {
+ super();
+ this.index = index;
+ this.proof =
+ typeof proof === 'string'
+ ? new Uint8Array(Buffer.from(proof, 'base64'))
+ : proof;
+ this.treedepth = treedepth;
+
+ this.attribute_map = {
+ index: 'index',
+ proof: 'proof',
+ treedepth: 'treedepth',
+ };
+ }
+}
+
/**
*
*/
@@ -2121,80 +2169,112 @@ export class PostTransactionsResponse extends BaseModel {
}
/**
- * Proof of transaction in a block.
+ * Represents a state proof and its corresponding message
*/
-export class ProofResponse extends BaseModel {
+export class StateProof extends BaseModel {
/**
- * Index of the transaction in the block's payset.
+ * Represents the message that the state proofs are attesting to.
*/
- public idx: number | bigint;
+ public message: StateProofMessage;
/**
- * Merkle proof of transaction membership.
+ * The encoded StateProof for the message.
*/
- public proof: Uint8Array;
+ public stateproof: Uint8Array;
/**
- * Hash of SignedTxnInBlock for verifying proof.
+ * Creates a new `StateProof` object.
+ * @param message - Represents the message that the state proofs are attesting to.
+ * @param stateproof - The encoded StateProof for the message.
*/
- public stibhash: Uint8Array;
+ constructor(message: StateProofMessage, stateproof: string | Uint8Array) {
+ super();
+ this.message = message;
+ this.stateproof =
+ typeof stateproof === 'string'
+ ? new Uint8Array(Buffer.from(stateproof, 'base64'))
+ : stateproof;
+
+ this.attribute_map = {
+ message: 'Message',
+ stateproof: 'StateProof',
+ };
+ }
+}
+/**
+ * Represents the message that the state proofs are attesting to.
+ */
+export class StateProofMessage extends BaseModel {
/**
- * Represents the depth of the tree that is being proven, i.e. the number of edges
- * from a leaf to the root.
+ * The vector commitment root on all light block headers within a state proof
+ * interval.
*/
- public treedepth: number | bigint;
+ public blockheaderscommitment: Uint8Array;
/**
- * The type of hash function used to create the proof, must be one of:
- * * sha512_256
- * * sha256
+ * The first round the message attests to.
*/
- public hashtype?: string;
+ public firstattestedround: number | bigint;
/**
- * Creates a new `ProofResponse` object.
- * @param idx - Index of the transaction in the block's payset.
- * @param proof - Merkle proof of transaction membership.
- * @param stibhash - Hash of SignedTxnInBlock for verifying proof.
- * @param treedepth - Represents the depth of the tree that is being proven, i.e. the number of edges
- * from a leaf to the root.
- * @param hashtype - The type of hash function used to create the proof, must be one of:
- * * sha512_256
- * * sha256
+ * The last round the message attests to.
+ */
+ public lastattestedround: number | bigint;
+
+ /**
+ * An integer value representing the natural log of the proven weight with 16 bits
+ * of precision. This value would be used to verify the next state proof.
+ */
+ public lnprovenweight: number | bigint;
+
+ /**
+ * The vector commitment root of the top N accounts to sign the next StateProof.
+ */
+ public voterscommitment: Uint8Array;
+
+ /**
+ * Creates a new `StateProofMessage` object.
+ * @param blockheaderscommitment - The vector commitment root on all light block headers within a state proof
+ * interval.
+ * @param firstattestedround - The first round the message attests to.
+ * @param lastattestedround - The last round the message attests to.
+ * @param lnprovenweight - An integer value representing the natural log of the proven weight with 16 bits
+ * of precision. This value would be used to verify the next state proof.
+ * @param voterscommitment - The vector commitment root of the top N accounts to sign the next StateProof.
*/
constructor({
- idx,
- proof,
- stibhash,
- treedepth,
- hashtype,
+ blockheaderscommitment,
+ firstattestedround,
+ lastattestedround,
+ lnprovenweight,
+ voterscommitment,
}: {
- idx: number | bigint;
- proof: string | Uint8Array;
- stibhash: string | Uint8Array;
- treedepth: number | bigint;
- hashtype?: string;
+ blockheaderscommitment: string | Uint8Array;
+ firstattestedround: number | bigint;
+ lastattestedround: number | bigint;
+ lnprovenweight: number | bigint;
+ voterscommitment: string | Uint8Array;
}) {
super();
- this.idx = idx;
- this.proof =
- typeof proof === 'string'
- ? new Uint8Array(Buffer.from(proof, 'base64'))
- : proof;
- this.stibhash =
- typeof stibhash === 'string'
- ? new Uint8Array(Buffer.from(stibhash, 'base64'))
- : stibhash;
- this.treedepth = treedepth;
- this.hashtype = hashtype;
+ this.blockheaderscommitment =
+ typeof blockheaderscommitment === 'string'
+ ? new Uint8Array(Buffer.from(blockheaderscommitment, 'base64'))
+ : blockheaderscommitment;
+ this.firstattestedround = firstattestedround;
+ this.lastattestedround = lastattestedround;
+ this.lnprovenweight = lnprovenweight;
+ this.voterscommitment =
+ typeof voterscommitment === 'string'
+ ? new Uint8Array(Buffer.from(voterscommitment, 'base64'))
+ : voterscommitment;
this.attribute_map = {
- idx: 'idx',
- proof: 'proof',
- stibhash: 'stibhash',
- treedepth: 'treedepth',
- hashtype: 'hashtype',
+ blockheaderscommitment: 'BlockHeadersCommitment',
+ firstattestedround: 'FirstAttestedRound',
+ lastattestedround: 'LastAttestedRound',
+ lnprovenweight: 'LnProvenWeight',
+ voterscommitment: 'VotersCommitment',
};
}
}
@@ -2400,6 +2480,85 @@ export class TransactionParametersResponse extends BaseModel {
}
}
+/**
+ * Proof of transaction in a block.
+ */
+export class TransactionProofResponse extends BaseModel {
+ /**
+ * Index of the transaction in the block's payset.
+ */
+ public idx: number | bigint;
+
+ /**
+ * Proof of transaction membership.
+ */
+ public proof: Uint8Array;
+
+ /**
+ * Hash of SignedTxnInBlock for verifying proof.
+ */
+ public stibhash: Uint8Array;
+
+ /**
+ * Represents the depth of the tree that is being proven, i.e. the number of edges
+ * from a leaf to the root.
+ */
+ public treedepth: number | bigint;
+
+ /**
+ * The type of hash function used to create the proof, must be one of:
+ * * sha512_256
+ * * sha256
+ */
+ public hashtype?: string;
+
+ /**
+ * Creates a new `TransactionProofResponse` object.
+ * @param idx - Index of the transaction in the block's payset.
+ * @param proof - Proof of transaction membership.
+ * @param stibhash - Hash of SignedTxnInBlock for verifying proof.
+ * @param treedepth - Represents the depth of the tree that is being proven, i.e. the number of edges
+ * from a leaf to the root.
+ * @param hashtype - The type of hash function used to create the proof, must be one of:
+ * * sha512_256
+ * * sha256
+ */
+ constructor({
+ idx,
+ proof,
+ stibhash,
+ treedepth,
+ hashtype,
+ }: {
+ idx: number | bigint;
+ proof: string | Uint8Array;
+ stibhash: string | Uint8Array;
+ treedepth: number | bigint;
+ hashtype?: string;
+ }) {
+ super();
+ this.idx = idx;
+ this.proof =
+ typeof proof === 'string'
+ ? new Uint8Array(Buffer.from(proof, 'base64'))
+ : proof;
+ this.stibhash =
+ typeof stibhash === 'string'
+ ? new Uint8Array(Buffer.from(stibhash, 'base64'))
+ : stibhash;
+ this.treedepth = treedepth;
+ this.hashtype = hashtype;
+
+ this.attribute_map = {
+ idx: 'idx',
+ proof: 'proof',
+ stibhash: 'stibhash',
+ treedepth: 'treedepth',
+ hashtype: 'hashtype',
+ };
+ }
+}
+
/**
* algod version information.
*/
diff --git a/src/client/v2/algod/proof.ts b/src/client/v2/algod/proof.ts
deleted file mode 100644
index a8b78e21d..000000000
--- a/src/client/v2/algod/proof.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import JSONRequest from '../jsonrequest';
-import HTTPClient from '../../client';
-import IntDecoding from '../../../types/intDecoding';
-
-export default class Proof extends JSONRequest {
- constructor(
- c: HTTPClient,
- intDecoding: IntDecoding,
- private round: number,
- private txID: string
- ) {
- super(c, intDecoding);
-
- this.round = round;
- this.txID = txID;
- }
-
- path() {
- return `/v2/blocks/${this.round}/transactions/${this.txID}/proof`;
- }
-}
diff --git a/src/client/v2/algod/stateproof.ts b/src/client/v2/algod/stateproof.ts
new file mode 100644
index 000000000..98bab12a7
--- /dev/null
+++ b/src/client/v2/algod/stateproof.ts
@@ -0,0 +1,15 @@
+import JSONRequest from '../jsonrequest';
+import HTTPClient from '../../client';
+import IntDecoding from '../../../types/intDecoding';
+
+export default class StateProof extends JSONRequest {
+ constructor(c: HTTPClient, intDecoding: IntDecoding, private round: number) {
+ super(c, intDecoding);
+
+ this.round = round;
+ }
+
+ path() {
+ return `/v2/stateproofs/${this.round}`;
+ }
+}
diff --git a/src/client/v2/indexer/lookupAccountTransactions.ts b/src/client/v2/indexer/lookupAccountTransactions.ts
index 69f41cd9b..b2343f1ca 100644
--- a/src/client/v2/indexer/lookupAccountTransactions.ts
+++ b/src/client/v2/indexer/lookupAccountTransactions.ts
@@ -76,7 +76,7 @@ export default class LookupAccountTransactions extends JSONRequest {
* .do();
* ```
*
- * @param type - one of `pay`, `keyreg`, `acfg`, `axfer`, `afrz`, `appl`
+ * @param type - one of `pay`, `keyreg`, `acfg`, `axfer`, `afrz`, `appl`, `stpf`
* @category query
*/
txType(type: string) {
diff --git a/src/client/v2/indexer/searchForTransactions.ts b/src/client/v2/indexer/searchForTransactions.ts
index 5afa84c29..7583059cd 100644
--- a/src/client/v2/indexer/searchForTransactions.ts
+++ b/src/client/v2/indexer/searchForTransactions.ts
@@ -52,7 +52,7 @@ export default class SearchForTransactions extends JSONRequest {
* .do();
* ```
*
- * @param type - one of `pay`, `keyreg`, `acfg`, `axfer`, `afrz`, `appl`
+ * @param type - one of `pay`, `keyreg`, `acfg`, `axfer`, `afrz`, `appl`, `stpf`
* @category query
*/
txType(type: string) {
diff --git a/src/logic/logic.ts b/src/logic/logic.ts
index 9f66b23f6..f42532940 100644
--- a/src/logic/logic.ts
+++ b/src/logic/logic.ts
@@ -3,10 +3,12 @@
* Utilities for working with program bytes.
*/
+/** @deprecated langspec.json is deprecated aross all SDKs */
import langspec from './langspec.json';
/**
* Langspec Op Structure
+ * @deprecated for langspec.json is deprecated aross all SDKs
*/
interface OpStructure {
Opcode: number;
@@ -23,13 +25,17 @@ interface OpStructure {
Groups: string[];
}
+/** @deprecated for langspec.json is deprecated aross all SDKs */
let opcodes: {
[key: number]: OpStructure;
};
+/** @deprecated for langspec.json is deprecated aross all SDKs */
const maxCost = 20000;
+/** @deprecated for langspec.json is deprecated aross all SDKs */
const maxLength = 1000;
+/** @deprecated for langspec.json is deprecated aross all SDKs */
export function parseUvarint(
array: Uint8Array
): [numberFound: number, size: number] {
@@ -49,6 +55,7 @@ export function parseUvarint(
return [0, 0];
}
+/** @deprecated for langspec.json is deprecated aross all SDKs */
function readIntConstBlock(
program: Uint8Array,
pc: number
@@ -79,6 +86,7 @@ function readIntConstBlock(
return [size, ints];
}
+/** @deprecated for langspec.json is deprecated aross all SDKs */
function readByteConstBlock(
program: Uint8Array,
pc: number
@@ -116,6 +124,7 @@ function readByteConstBlock(
return [size, byteArrays];
}
+/** @deprecated for langspec.json is deprecated aross all SDKs */
function readPushIntOp(
program: Uint8Array,
pc: number
@@ -129,6 +138,7 @@ function readPushIntOp(
return [size, numberFound];
}
+/** @deprecated for langspec.json is deprecated aross all SDKs */
function readPushByteOp(
program: Uint8Array,
pc: number
@@ -151,6 +161,12 @@ function readPushByteOp(
/** readProgram validates program for length and running cost,
* and additionally provides the found int variables and byte blocks
+ *
+ * @deprecated Validation relies on metadata (`langspec.json`) that
+ * does not accurately represent opcode behavior across program versions.
+ * The behavior of `readProgram` relies on `langspec.json`.
+ * Thus, this method is being deprecated.
+ *
* @param program - Program to check
* @param args - Program arguments as array of Uint8Array arrays
* @throws
@@ -254,6 +270,12 @@ export function readProgram(
/**
* checkProgram validates program for length and running cost
+ *
+ * @deprecated Validation relies on metadata (`langspec.json`) that
+ * does not accurately represent opcode behavior across program versions.
+ * The behavior of `checkProgram` relies on `langspec.json`.
+ * Thus, this method is being deprecated.
+ *
* @param program - Program to check
* @param args - Program arguments as array of Uint8Array arrays
* @throws
@@ -264,25 +286,31 @@ export function checkProgram(program: Uint8Array, args?: Uint8Array[]) {
return success;
}
+/** @deprecated for langspec.json is deprecated aross all SDKs */
export function checkIntConstBlock(program: Uint8Array, pc: number) {
const [size] = readIntConstBlock(program, pc);
return size;
}
+/** @deprecated for langspec.json is deprecated aross all SDKs */
export function checkByteConstBlock(program: Uint8Array, pc: number) {
const [size] = readByteConstBlock(program, pc);
return size;
}
+/** @deprecated for langspec.json is deprecated aross all SDKs */
export function checkPushIntOp(program: Uint8Array, pc: number) {
const [size] = readPushIntOp(program, pc);
return size;
}
+/** @deprecated for langspec.json is deprecated aross all SDKs */
export function checkPushByteOp(program: Uint8Array, pc: number) {
const [size] = readPushByteOp(program, pc);
return size;
}
+/** @deprecated for langspec.json is deprecated aross all SDKs */
export const langspecEvalMaxVersion = langspec.EvalMaxVersion;
+/** @deprecated for langspec.json is deprecated aross all SDKs */
export const langspecLogicSigVersion = langspec.LogicSigVersion;
diff --git a/src/logicsig.ts b/src/logicsig.ts
index 4904d3c52..74c0e0bef 100644
--- a/src/logicsig.ts
+++ b/src/logicsig.ts
@@ -1,10 +1,10 @@
import * as nacl from './nacl/naclWrappers';
import * as address from './encoding/address';
import * as encoding from './encoding/encoding';
-import * as logic from './logic/logic';
import { verifyMultisig } from './multisig';
import * as utils from './utils/utils';
import * as txnBuilder from './transaction';
+import { isValidAddress } from './encoding/address';
import {
EncodedLogicSig,
EncodedLogicSigAccount,
@@ -20,6 +20,38 @@ interface LogicSigStorageStructure {
msig?: EncodedMultisig;
}
+/** sanityCheckProgram performs heuristic program validation:
+ * check if passed in bytes are Algorand address or is B64 encoded, rather than Teal bytes
+ *
+ * @param program - Program bytes to check
+ */
+export function sanityCheckProgram(program: Uint8Array) {
+ if (!program || program.length === 0) throw new Error('empty program');
+
+ const lineBreakOrd = '\n'.charCodeAt(0);
+ const blankSpaceOrd = ' '.charCodeAt(0);
+ const tildeOrd = '~'.charCodeAt(0);
+
+ const isPrintable = (x: number) => blankSpaceOrd <= x && x <= tildeOrd;
+ const isAsciiPrintable = program.every(
+ (x: number) => x === lineBreakOrd || isPrintable(x)
+ );
+
+ if (isAsciiPrintable) {
+ const programStr = Buffer.from(program).toString();
+
+ if (isValidAddress(programStr))
+ throw new Error('requesting program bytes, get Algorand address');
+
+ if (Buffer.from(programStr, 'base64').toString('base64') === programStr)
+ throw new Error('program should not be b64 encoded');
+
+ throw new Error(
+ 'program bytes are all ASCII printable characters, not looking like Teal byte code'
+ );
+ }
+}
+
/**
LogicSig implementation
*/
@@ -49,9 +81,7 @@ export class LogicSig implements LogicSigStorageStructure {
if (programArgs != null)
args = programArgs.map((arg) => new Uint8Array(arg));
- if (!logic.checkProgram(program, args)) {
- throw new Error('Invalid program');
- }
+ sanityCheckProgram(program);
this.logic = program;
this.args = args;
@@ -93,7 +123,7 @@ export class LogicSig implements LogicSigStorageStructure {
}
try {
- logic.checkProgram(this.logic, this.args);
+ sanityCheckProgram(this.logic);
} catch (e) {
return false;
}
diff --git a/src/transaction.ts b/src/transaction.ts
index 29a4f0207..af4efd849 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -113,6 +113,9 @@ interface TransactionStorageStructure
group?: Buffer;
extraPages?: number;
boxes?: BoxReference[];
+ stateProofType?: number | bigint;
+ stateProof?: Uint8Array;
+ stateProofMessage?: Uint8Array;
}
function getKeyregKey(
@@ -202,6 +205,9 @@ export class Transaction implements TransactionStorageStructure {
nonParticipation?: boolean;
group?: Buffer;
extraPages?: number;
+ stateProofType?: number | bigint;
+ stateProof?: Uint8Array;
+ stateProofMessage?: Uint8Array;
constructor({ ...transaction }: AnyTransaction) {
// Populate defaults
@@ -546,6 +552,27 @@ export class Transaction implements TransactionStorageStructure {
// say we are aware of groups
this.group = undefined;
+
+ // stpf fields
+ if (
+ txn.stateProofType !== undefined &&
+ (!Number.isSafeInteger(txn.stateProofType) || txn.stateProofType < 0)
+ )
+ throw Error(
+ 'State Proof type must be a positive number and smaller than 2^53-1'
+ );
+ if (txn.stateProofMessage !== undefined) {
+ if (txn.stateProofMessage.constructor !== Uint8Array)
+ throw Error('stateProofMessage must be a Uint8Array.');
+ } else {
+ txn.stateProofMessage = new Uint8Array(0);
+ }
+ if (txn.stateProof !== undefined) {
+ if (txn.stateProof.constructor !== Uint8Array)
+ throw Error('stateProof must be a Uint8Array.');
+ } else {
+ txn.stateProof = new Uint8Array(0);
+ }
}
// eslint-disable-next-line camelcase
@@ -854,6 +881,42 @@ export class Transaction implements TransactionStorageStructure {
if (txn.grp === undefined) delete txn.grp;
return txn;
}
+ if (this.type === 'stpf') {
+ // state proof txn
+ const txn: EncodedTransaction = {
+ fee: this.fee,
+ fv: this.firstRound,
+ lv: this.lastRound,
+ note: Buffer.from(this.note),
+ snd: Buffer.from(this.from.publicKey),
+ type: this.type,
+ gen: this.genesisID,
+ gh: this.genesisHash,
+ lx: Buffer.from(this.lease),
+ sptype: this.stateProofType,
+ spmsg: Buffer.from(this.stateProofMessage),
+ sp: Buffer.from(this.stateProof),
+ };
+ // allowed zero values
+ if (!txn.sptype) delete txn.sptype;
+ if (!txn.note.length) delete txn.note;
+ if (!txn.lx.length) delete txn.lx;
+ if (!txn.amt) delete txn.amt;
+ if (!txn.fee) delete txn.fee;
+ if (!txn.fv) delete txn.fv;
+ if (!txn.gen) delete txn.gen;
+ if (!txn.apid) delete txn.apid;
+ if (!txn.apaa || !txn.apaa.length) delete txn.apaa;
+ if (!txn.apap) delete txn.apap;
+ if (!txn.apsu) delete txn.apsu;
+ if (!txn.apan) delete txn.apan;
+ if (!txn.apfa || !txn.apfa.length) delete txn.apfa;
+ if (!txn.apas || !txn.apas.length) delete txn.apas;
+ if (!txn.apat || !txn.apat.length) delete txn.apat;
+ if (!txn.apep) delete txn.apep;
+ if (txn.grp === undefined) delete txn.grp;
+ return txn;
+ }
return undefined;
}
@@ -1031,6 +1094,16 @@ export class Transaction implements TransactionStorageStructure {
name: box.n,
}));
}
+ } else if (txnForEnc.type === 'stpf') {
+ if (txnForEnc.sptype !== undefined) {
+ txn.stateProofType = txnForEnc.sptype;
+ }
+ if (txnForEnc.sp !== undefined) {
+ txn.stateProof = txnForEnc.sp;
+ }
+ if (txnForEnc.spmsg !== undefined) {
+ txn.stateProofMessage = txnForEnc.spmsg;
+ }
}
return txn;
}
diff --git a/src/types/blockHeader.ts b/src/types/blockHeader.ts
index 7fa1c2e05..96f2af265 100644
--- a/src/types/blockHeader.ts
+++ b/src/types/blockHeader.ts
@@ -65,7 +65,17 @@ export default interface BlockHeader {
ts: number;
/**
- * Transaction root
+ * Transaction root SHA512_256
*/
txn: string;
+
+ /**
+ * Transaction root SHA256
+ */
+ txn256: string;
+
+ /**
+ * StateProofTracking map of type to tracking data
+ */
+ spt: Map;
}
diff --git a/src/types/transactions/base.ts b/src/types/transactions/base.ts
index f94ebdcbd..391983221 100644
--- a/src/types/transactions/base.ts
+++ b/src/types/transactions/base.ts
@@ -33,6 +33,10 @@ export enum TransactionType {
* Application transaction
*/
appl = 'appl',
+ /**
+ * State proof transaction
+ */
+ stpf = 'stpf',
}
export function isTransactionType(s: string): s is TransactionType {
@@ -42,7 +46,8 @@ export function isTransactionType(s: string): s is TransactionType {
s === TransactionType.acfg ||
s === TransactionType.axfer ||
s === TransactionType.afrz ||
- s === TransactionType.appl
+ s === TransactionType.appl ||
+ s === TransactionType.stpf
);
}
@@ -405,4 +410,19 @@ export interface TransactionParams {
* A grouping of the app ID and name of the box in an Uint8Array
*/
boxes?: BoxReference[];
+
+ /*
+ * Uint64 identifying a particular configuration of state proofs.
+ */
+ stateProofType?: number | bigint;
+
+ /**
+ * Byte array containing the state proof.
+ */
+ stateProof?: Uint8Array;
+
+ /**
+ * Byte array containing the state proof message.
+ */
+ stateProofMessage?: Uint8Array;
}
diff --git a/src/types/transactions/encoded.ts b/src/types/transactions/encoded.ts
index 2fdd7e48d..ff3d5609d 100644
--- a/src/types/transactions/encoded.ts
+++ b/src/types/transactions/encoded.ts
@@ -313,6 +313,21 @@ export interface EncodedTransaction {
* boxes
*/
apbx?: EncodedBoxReference[];
+
+ /*
+ * stateProofType
+ */
+ sptype?: number | bigint;
+
+ /**
+ * stateProof
+ */
+ sp?: Buffer;
+
+ /**
+ * stateProofMessage
+ */
+ spmsg?: Buffer;
}
export interface EncodedSubsig {
diff --git a/src/types/transactions/index.ts b/src/types/transactions/index.ts
index 1ad181ffb..fc4d8543b 100644
--- a/src/types/transactions/index.ts
+++ b/src/types/transactions/index.ts
@@ -16,6 +16,7 @@ import {
ApplicationClearStateTransaction as AppClearStateTxn,
ApplicationNoOpTransaction as AppNoOpTxn,
} from './application';
+import StateProofTxn from './stateproof';
// Utilities
export {
@@ -49,6 +50,7 @@ export {
ApplicationClearStateTransaction as AppClearStateTxn,
ApplicationNoOpTransaction as AppNoOpTxn,
} from './application';
+export { default as StateProofTxn } from './stateproof';
// All possible transaction types
type AnyTransaction =
@@ -65,5 +67,6 @@ type AnyTransaction =
| AppOptInTxn
| AppCloseOutTxn
| AppClearStateTxn
- | AppNoOpTxn;
+ | AppNoOpTxn
+ | StateProofTxn;
export default AnyTransaction;
diff --git a/src/types/transactions/stateproof.ts b/src/types/transactions/stateproof.ts
new file mode 100644
index 000000000..7f2e1167f
--- /dev/null
+++ b/src/types/transactions/stateproof.ts
@@ -0,0 +1,17 @@
+import { TransactionType, TransactionParams } from './base';
+import { ConstructTransaction } from './builder';
+
+type SpecificParameters = Pick<
+ TransactionParams,
+ 'stateProofType' | 'stateProof' | 'stateProofMessage'
+>;
+
+interface Overwrites {
+ type?: TransactionType.stpf;
+}
+
+type StateProofTransaction = ConstructTransaction<
+ SpecificParameters,
+ Overwrites
+>;
+export default StateProofTransaction;
diff --git a/test-harness.sh b/test-harness.sh
new file mode 100755
index 000000000..ad68eaf63
--- /dev/null
+++ b/test-harness.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+START=$(date "+%s")
+
+THIS=$(basename "$0")
+ENV_FILE=".test-env"
+TEST_DIR="tests/cucumber"
+
+set -a
+source "$ENV_FILE"
+set +a
+
+rootdir=$(dirname "$0")
+pushd "$rootdir"
+
+echo "$THIS: VERBOSE_HARNESS=$VERBOSE_HARNESS"
+
+## Reset test harness
+if [ -d "$SDK_TESTING_HARNESS" ]; then
+ pushd "$SDK_TESTING_HARNESS"
+ ./scripts/down.sh
+ popd
+ rm -rf "$SDK_TESTING_HARNESS"
+else
+ echo "$THIS: directory $SDK_TESTING_HARNESS does not exist - NOOP"
+fi
+
+git clone --depth 1 --single-branch --branch "$SDK_TESTING_BRANCH" "$SDK_TESTING_URL" "$SDK_TESTING_HARNESS"
+
+
+echo "$THIS: OVERWRITE_TESTING_ENVIRONMENT=$OVERWRITE_TESTING_ENVIRONMENT"
+if [[ $OVERWRITE_TESTING_ENVIRONMENT == 1 ]]; then
+ echo "$THIS: OVERWRITE downloaded $SDK_TESTING_HARNESS/.env with $ENV_FILE:"
+ cp "$ENV_FILE" "$SDK_TESTING_HARNESS"/.env
+fi
+
+echo "$THIS: REMOVE_LOCAL_FEATURES=$REMOVE_LOCAL_FEATURES"
+## Copy feature files into the project resources
+if [[ $REMOVE_LOCAL_FEATURES == 1 ]]; then
+ echo "$THIS: OVERWRITE wipes clean $TEST_DIR/features"
+ if [[ $VERBOSE_HARNESS == 1 ]]; then
+ ( tree $TEST_DIR/features && echo "$THIS: see the previous for files deleted" ) || true
+ fi
+ rm -rf $TEST_DIR/features
+fi
+mkdir -p $TEST_DIR/features
+cp -r "$SDK_TESTING_HARNESS"/features/* $TEST_DIR/features
+if [[ $VERBOSE_HARNESS == 1 ]]; then
+ ( tree $TEST_DIR/features && echo "$THIS: see the previous for files copied over" ) || true
+fi
+echo "$THIS: seconds it took to get to end of cloning and copying: $(($(date "+%s") - START))s"
+
+## Start test harness environment
+pushd "$SDK_TESTING_HARNESS"
+
+[[ "$VERBOSE_HARNESS" = 1 ]] && V_FLAG="-v" || V_FLAG=""
+echo "$THIS: standing up harnness with command [./up.sh $V_FLAG]"
+./scripts/up.sh "$V_FLAG"
+
+popd
+echo "$THIS: seconds it took to finish testing sdk's up.sh: $(($(date "+%s") - START))s"
+echo ""
+echo "--------------------------------------------------------------------------------"
+echo "|"
+echo "| To run sandbox commands, cd into $SDK_TESTING_HARNESS/.sandbox "
+echo "|"
+echo "--------------------------------------------------------------------------------"
diff --git a/tests/5.Transaction.js b/tests/5.Transaction.js
index cf8eeba07..a3118fefc 100644
--- a/tests/5.Transaction.js
+++ b/tests/5.Transaction.js
@@ -345,6 +345,40 @@ describe('Sign', () => {
assert.deepStrictEqual(reencRep, encRep);
});
+ it('should correctly serialize and deserialize a state proof transaction from msgpack representation', () => {
+ const o = {
+ from: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU',
+ fee: 10,
+ firstRound: 51,
+ lastRound: 61,
+ note: new Uint8Array([123, 12, 200]),
+ genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=',
+ voteKey: '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=',
+ selectionKey: 'oImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX4=',
+ voteFirst: 123,
+ voteLast: 456,
+ voteKeyDilution: 1234,
+ genesisID: '',
+ type: 'stpf',
+ stateProofType: 0,
+ stateProof: new Uint8Array([1, 1, 1, 1]),
+ stateProofMessage: new Uint8Array([0, 0, 0, 0]),
+ };
+ const expectedTxn = new algosdk.Transaction(o);
+ console.log(
+ `${expectedTxn.stateProofType} ${expectedTxn.stateProofMessage} ${expectedTxn.stateProof} ${expectedTxn.type}`
+ );
+ const encRep = expectedTxn.get_obj_for_encoding();
+ console.log(
+ `${encRep.sptype} ${encRep.spmsg} ${encRep.sp} ${encRep.type}`
+ );
+ const encTxn = algosdk.encodeObj(encRep);
+ const decEncRep = algosdk.decodeObj(encTxn);
+ const decTxn = algosdk.Transaction.from_obj_for_encoding(decEncRep);
+ const reencRep = decTxn.get_obj_for_encoding();
+ assert.deepStrictEqual(reencRep, encRep);
+ });
+
it('should correctly serialize and deserialize a key registration transaction from msgpack representation', () => {
const o = {
from: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU',
diff --git a/tests/7.AlgoSDK.js b/tests/7.AlgoSDK.js
index e831786b9..6cc0c67ee 100644
--- a/tests/7.AlgoSDK.js
+++ b/tests/7.AlgoSDK.js
@@ -830,11 +830,6 @@ describe('Algosdk (AKA end to end)', () => {
assert.equal(lsig.logic, program);
assert.deepEqual(lsig.args, args);
});
- it('should throw on invalid program', () => {
- const program = Uint8Array.from([1, 32, 1, 1, 34]);
- program[0] = 128;
- assert.throws(() => algosdk.makeLogicSig(program));
- });
});
describe('Single logic sig', () => {
it('should work on valid program', () => {
diff --git a/tests/8.LogicSig.ts b/tests/8.LogicSig.ts
index e7e7a5fba..08be74abf 100644
--- a/tests/8.LogicSig.ts
+++ b/tests/8.LogicSig.ts
@@ -65,11 +65,6 @@ describe('LogicSig', () => {
const verified = lsig.verify(pk);
assert.strictEqual(verified, false);
});
- it('should fail on invalid program', () => {
- const program = Uint8Array.from([1, 32, 1, 1, 34]);
- program[0] = 128;
- assert.throws(() => algosdk.makeLogicSig(program));
- });
});
describe('address', () => {
@@ -129,11 +124,6 @@ describe('LogicSigAccount', () => {
const decoded = algosdk.LogicSigAccount.fromByte(encoded);
assert.deepStrictEqual(decoded, lsigAccount);
});
- it('should fail on invalid program', () => {
- const program = Uint8Array.from([1, 32, 1, 1, 34]);
- program[0] = 128;
- assert.throws(() => new algosdk.LogicSigAccount(program));
- });
});
describe('sign', () => {
diff --git a/tests/cucumber/docker/run_docker.sh b/tests/cucumber/docker/run_docker.sh
deleted file mode 100755
index 84f7bf0cf..000000000
--- a/tests/cucumber/docker/run_docker.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-# cleanup last test run
-rm -rf test-harness
-rm -rf tests/cucumber/features
-
-# clone test harness
-git clone --single-branch --branch feature/box-storage https://github.com/algorand/algorand-sdk-testing.git test-harness
-
-# move feature files and example files to destination
-mv test-harness/features tests/cucumber/features
-
-if [ $TEST_BROWSER == "chrome" ]; then
- # use latest version of chromedriver for compatability with the current Chrome version
- npm install chromedriver@latest
- # print the version installed
- npm ls chromedriver
-fi
-
-# build test environment
-docker build -t js-sdk-testing -f tests/cucumber/docker/Dockerfile "$(pwd)" --build-arg TEST_BROWSER --build-arg CI=true
-
-# Start test harness environment
-./test-harness/scripts/up.sh
-
-docker run -it \
- --network host \
- js-sdk-testing:latest
diff --git a/tests/cucumber/integration.tags b/tests/cucumber/integration.tags
new file mode 100644
index 000000000..03cb481b6
--- /dev/null
+++ b/tests/cucumber/integration.tags
@@ -0,0 +1,14 @@
+@abi
+@algod
+@applications
+@applications.verified
+@assets
+@auction
+@c2c
+@compile
+@compile.sourcemap
+@dryrun
+@kmd
+@rekey_v1
+@send
+@send.keyregtxn
\ No newline at end of file
diff --git a/tests/cucumber/steps/index.js b/tests/cucumber/steps/index.js
index 0c9c51f0a..0117dc84e 100644
--- a/tests/cucumber/steps/index.js
+++ b/tests/cucumber/steps/index.js
@@ -347,6 +347,16 @@ if (browser) {
rpcArgs = args.slice(0, rpcArgs.length - 1);
}
+ for (const arg of rpcArgs) {
+ if (arg instanceof Uint8Array) {
+ // cannot send Uint8Array or Buffer objects because the arguments will get JSON
+ // encoded when transmitted to the browser
+ throw new Error(
+ `Attempted to send binary data to the browser when invoking test '${type} ${name}'`
+ );
+ }
+ }
+
const { error } = await driver.executeAsyncScript(
// variables are `scoped` because they exist in the upper scope
async (scopedType, scopedName, ...rest) => {
@@ -391,34 +401,54 @@ for (const name of Object.keys(steps.given)) {
const fn = steps.given[name];
if (name === 'mock http responses in {string} loaded from {string}') {
Given(name, function (fileName, jsonDirectory) {
- const body1 = setupMockServerForResponses(
+ let body1 = setupMockServerForResponses(
fileName,
jsonDirectory,
algodMockServerResponder
);
- const body2 = setupMockServerForResponses(
+ let body2 = setupMockServerForResponses(
fileName,
jsonDirectory,
indexerMockServerResponder
);
- return fn.call(this, body2 || body1);
+ let format = 'json';
+ if (fileName.endsWith('base64')) {
+ format = 'msgp';
+ }
+ if (Buffer.isBuffer(body1)) {
+ body1 = body1.toString('base64');
+ }
+ if (Buffer.isBuffer(body2)) {
+ body2 = body2.toString('base64');
+ }
+ return fn.call(this, body2 || body1, format);
});
} else if (
name ===
'mock http responses in {string} loaded from {string} with status {int}.'
) {
Given(name, function (fileName, jsonDirectory, status) {
- const body1 = setupMockServerForResponses(
+ let body1 = setupMockServerForResponses(
fileName,
jsonDirectory,
algodMockServerResponder
);
- const body2 = setupMockServerForResponses(
+ let body2 = setupMockServerForResponses(
fileName,
jsonDirectory,
indexerMockServerResponder
);
- return fn.call(this, body2 || body1, status);
+ let format = 'json';
+ if (fileName.endsWith('base64')) {
+ format = 'msgp';
+ }
+ if (Buffer.isBuffer(body1)) {
+ body1 = body1.toString('base64');
+ }
+ if (Buffer.isBuffer(body2)) {
+ body2 = body2.toString('base64');
+ }
+ return fn.call(this, body2 || body1, status, format);
});
} else if (name === 'mock server recording request paths') {
Given(name, function () {
diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js
index f50749e99..7a3e13688 100644
--- a/tests/cucumber/steps/steps.js
+++ b/tests/cucumber/steps/steps.js
@@ -76,14 +76,6 @@ function parseJSON(json) {
// END OBJECT CREATION FUNCTIONS
-function formatIncludeAll(includeAll) {
- if (!['true', 'false'].includes(includeAll)) {
- throw new Error(`Unknown value for includeAll: ${includeAll}`);
- }
-
- return includeAll === 'true';
-}
-
const steps = {
given: {},
when: {},
@@ -320,29 +312,6 @@ module.exports = function getSteps(options) {
this.pk = algosdk.multisigAddress(this.msig);
});
- When('I create the payment transaction', function () {
- this.txn = {
- from: this.pk,
- to: this.to,
- fee: this.fee,
- firstRound: this.fv,
- lastRound: this.lv,
- genesisHash: this.gh,
- };
- if (this.gen) {
- this.txn.genesisID = this.gen;
- }
- if (this.close) {
- this.txn.closeRemainderTo = this.close;
- }
- if (this.note) {
- this.txn.note = this.note;
- }
- if (this.amt) {
- this.txn.amount = this.amt;
- }
- });
-
When('I sign the transaction with the private key', function () {
const obj = algosdk.signTransaction(this.txn, this.sk);
this.stx = obj.blob;
@@ -684,26 +653,6 @@ module.exports = function getSteps(options) {
);
});
- Then('I get transactions by address only', async function () {
- const transactions = await this.acl.transactionByAddress(this.accounts[0]);
- assert.deepStrictEqual(
- true,
- Object.entries(transactions).length === 0 ||
- 'transactions' in transactions
- );
- });
-
- Then('I get transactions by address and date', async function () {
- const transactions = await this.acl.transactionByAddressAndDate(
- this.accounts[0]
- );
- assert.deepStrictEqual(
- true,
- Object.entries(transactions).length === 0 ||
- 'transactions' in transactions
- );
- });
-
Then('I get pending transactions', async function () {
const transactions = await this.acl.pendingTransactions(10);
assert.deepStrictEqual(
@@ -935,19 +884,16 @@ module.exports = function getSteps(options) {
});
Then('the transaction should go through', async function () {
- let info = await this.acl.pendingTransactionInformation(this.txid);
- assert.deepStrictEqual(true, 'type' in info);
- // let localParams = await this.acl.getTransactionParams();
- // this.lastRound = localParams.lastRound;
await waitForAlgodInDevMode();
- info = await this.acl.transactionById(this.txid);
+ const info = await this.acl.pendingTransactionInformation(this.txid);
assert.deepStrictEqual(true, 'type' in info);
- });
- Then('I can get the transaction by ID', async function () {
- await waitForAlgodInDevMode();
- const info = await this.acl.transactionById(this.txid);
- assert.deepStrictEqual(true, 'type' in info);
+ // TODO: this needs to be modified/removed when v1 is no longer supported
+ // let localParams = await this.acl.getTransactionParams();
+ // this.lastRound = localParams.lastRound;
+ // await waitForAlgodInDevMode();
+ // info = await this.acl.transactionById(this.txid);
+ // assert.deepStrictEqual(true, 'type' in info);
});
Then('the transaction should not go through', function () {
@@ -1065,60 +1011,6 @@ module.exports = function getSteps(options) {
return this.kcl.deleteKey(this.handle, this.wallet_pswd, this.pk);
});
- Given(
- 'key registration transaction parameters {int} {int} {int} {string} {string} {string} {int} {int} {int} {string} {string}',
- function (
- fee,
- fv,
- lv,
- gh,
- votekey,
- selkey,
- votefst,
- votelst,
- votekd,
- gen,
- note
- ) {
- this.fee = parseInt(fee);
- this.fv = parseInt(fv);
- this.lv = parseInt(lv);
- this.gh = gh;
- if (gen !== 'none') {
- this.gen = gen;
- }
- if (note !== 'none') {
- this.note = makeUint8Array(Buffer.from(note, 'base64'));
- }
- this.votekey = votekey;
- this.selkey = selkey;
- this.votefst = votefst;
- this.votelst = votelst;
- this.votekd = votekd;
- }
- );
-
- When('I create the key registration transaction', function () {
- this.txn = {
- fee: this.fee,
- firstRound: this.fv,
- lastRound: this.lv,
- genesisHash: this.gh,
- voteKey: this.votekey,
- selectionKey: this.selkey,
- voteFirst: this.votefst,
- voteLast: this.votelst,
- voteKeyDilution: this.votekd,
- type: 'keyreg',
- };
- if (this.gen) {
- this.txn.genesisID = this.gen;
- }
- if (this.note) {
- this.txn.note = this.note;
- }
- });
-
Given(
'default V2 key registration transaction {string}',
async function (type) {
@@ -1176,13 +1068,6 @@ module.exports = function getSteps(options) {
}
);
- When(
- 'I get recent transactions, limited by {int} transactions',
- function (int) {
- this.acl.transactionByAddress(this.accounts[0], parseInt(int));
- }
- );
-
/// /////////////////////////////////
// begin asset tests
/// /////////////////////////////////
@@ -1683,13 +1568,20 @@ module.exports = function getSteps(options) {
} = options;
let expectedMockResponse;
+ let responseFormat;
Given(
'mock http responses in {string} loaded from {string}',
- function (expectedBody) {
+ function (expectedBody, format) {
if (expectedBody !== null) {
expectedMockResponse = expectedBody;
+ if (format === 'msgp') {
+ expectedMockResponse = new Uint8Array(
+ Buffer.from(expectedMockResponse, 'base64')
+ );
+ }
}
+ responseFormat = format;
this.v2Client = new algosdk.Algodv2(
'',
`http://${mockAlgodResponderHost}`,
@@ -1707,10 +1599,16 @@ module.exports = function getSteps(options) {
Given(
'mock http responses in {string} loaded from {string} with status {int}.',
- function (expectedBody, status) {
+ function (expectedBody, status, format) {
if (expectedBody !== null) {
expectedMockResponse = expectedBody;
+ if (format === 'msgp') {
+ expectedMockResponse = new Uint8Array(
+ Buffer.from(expectedMockResponse, 'base64')
+ );
+ }
}
+ responseFormat = format;
this.v2Client = new algosdk.Algodv2(
'',
`http://${mockAlgodResponderHost}`,
@@ -1734,7 +1632,11 @@ module.exports = function getSteps(options) {
try {
if (client === 'algod') {
// endpoints are ignored by mock server, see setupMockServerForResponses
- this.actualMockResponse = await this.v2Client.status().do();
+ if (responseFormat === 'msgp') {
+ this.actualMockResponse = await this.v2Client.block(0).do();
+ } else {
+ this.actualMockResponse = await this.v2Client.status().do();
+ }
} else if (client === 'indexer') {
// endpoints are ignored by mock server, see setupMockServerForResponses
this.actualMockResponse = await this.indexerClient
@@ -1760,10 +1662,22 @@ module.exports = function getSteps(options) {
Then('the parsed response should equal the mock response.', function () {
if (this.expectedMockResponseCode === 200) {
- assert.strictEqual(
- JSON.stringify(JSON.parse(expectedMockResponse)),
- JSON.stringify(this.actualMockResponse)
- );
+ // assert.deepStrictEqual considers a Buffer and Uint8Array with the same contents as unequal.
+ // These types are fairly interchangable in different parts of the SDK, so we need to normalize
+ // them before comparing, which is why we chain encoding/decoding below.
+ if (responseFormat === 'json') {
+ assert.strictEqual(
+ JSON.stringify(JSON.parse(expectedMockResponse)),
+ JSON.stringify(this.actualMockResponse)
+ );
+ } else {
+ assert.deepStrictEqual(
+ algosdk.decodeObj(
+ new Uint8Array(algosdk.encodeObj(this.actualMockResponse))
+ ),
+ algosdk.decodeObj(expectedMockResponse)
+ );
+ }
}
});
@@ -1816,13 +1730,6 @@ module.exports = function getSteps(options) {
}
);
- When(
- 'we make a Pending Transaction Information against txid {string} with max {int}',
- function (txid, max) {
- this.v2Client.pendingTransactionInformation(txid).max(max).do();
- }
- );
-
When(
'we make a Pending Transaction Information against txid {string} with format {string}',
async function (txid, format) {
@@ -2113,19 +2020,6 @@ module.exports = function getSteps(options) {
}
);
- When(
- 'we make a Lookup Asset Balances call against asset index {int} with limit {int} nextToken {string} currencyGreaterThan {int} currencyLessThan {int}',
- async function (index, limit, nextToken, currencyGreater, currencyLesser) {
- await this.indexerClient
- .lookupAssetBalances(index)
- .limit(limit)
- .currencyGreaterThan(currencyGreater)
- .currencyLessThan(currencyLesser)
- .nextToken(nextToken)
- .do();
- }
- );
-
When(
'we make a Lookup Asset Balances call against asset index {int} with limit {int} afterAddress {string} currencyGreaterThan {int} currencyLessThan {int}',
async function (
@@ -2144,51 +2038,6 @@ module.exports = function getSteps(options) {
}
);
- When(
- 'we make a Lookup Asset Transactions call against asset index {int} with NotePrefix {string} TxType {string} SigType {string} txid {string} round {int} minRound {int} maxRound {int} limit {int} beforeTime {int} afterTime {int} currencyGreaterThan {int} currencyLessThan {int} address {string} addressRole {string} ExcluseCloseTo {string}',
- async function (
- assetIndex,
- notePrefix,
- txType,
- sigType,
- txid,
- round,
- minRound,
- maxRound,
- limit,
- beforeTime,
- afterTime,
- currencyGreater,
- currencyLesser,
- address,
- addressRole,
- excludeCloseToAsString
- ) {
- let excludeCloseTo = false;
- if (excludeCloseToAsString === 'true') {
- excludeCloseTo = true;
- }
- await this.indexerClient
- .lookupAssetTransactions(assetIndex)
- .notePrefix(notePrefix)
- .txType(txType)
- .sigType(sigType)
- .txid(txid)
- .round(round)
- .minRound(minRound)
- .maxRound(maxRound)
- .limit(limit)
- .beforeTime(beforeTime)
- .afterTime(afterTime)
- .currencyGreaterThan(currencyGreater)
- .currencyLessThan(currencyLesser)
- .address(address)
- .addressRole(addressRole)
- .excludeCloseTo(excludeCloseTo)
- .do();
- }
- );
-
When(
'we make a Lookup Asset Transactions call against asset index {int} with NotePrefix {string} TxType {string} SigType {string} txid {string} round {int} minRound {int} maxRound {int} limit {int} beforeTime {string} afterTime {string} currencyGreaterThan {int} currencyLessThan {int} address {string} addressRole {string} ExcluseCloseTo {string}',
async function (
@@ -2393,13 +2242,6 @@ module.exports = function getSteps(options) {
}
);
- When(
- 'we make a LookupApplications call with {int} and {int}',
- async function (index, round) {
- await this.indexerClient.lookupApplications(index).round(round).do();
- }
- );
-
When(
'we make a LookupApplicationLogsByID call with applicationID {int} limit {int} minRound {int} maxRound {int} nextToken {string} sender {string} and txID {string}',
async function (appID, limit, minRound, maxRound, nextToken, sender, txID) {
@@ -2415,26 +2257,6 @@ module.exports = function getSteps(options) {
}
);
- When(
- 'we make a Search Accounts call with assetID {int} limit {int} currencyGreaterThan {int} currencyLessThan {int} and nextToken {string}',
- async function (
- assetIndex,
- limit,
- currencyGreater,
- currencyLesser,
- nextToken
- ) {
- await this.indexerClient
- .searchAccounts()
- .assetID(assetIndex)
- .limit(limit)
- .currencyGreaterThan(currencyGreater)
- .currencyLessThan(currencyLesser)
- .nextToken(nextToken)
- .do();
- }
- );
-
When(
'we make a Search Accounts call with assetID {int} limit {int} currencyGreaterThan {int} currencyLessThan {int} and round {int}',
async function (assetIndex, limit, currencyGreater, currencyLesser, round) {
@@ -2478,63 +2300,6 @@ module.exports = function getSteps(options) {
}
);
- When(
- 'we make a Search For Transactions call with account {string} NotePrefix {string} TxType {string} SigType {string} txid {string} round {int} minRound {int} maxRound {int} limit {int} beforeTime {int} afterTime {int} currencyGreaterThan {int} currencyLessThan {int} assetIndex {int} addressRole {string} ExcluseCloseTo {string}',
- async function (
- account,
- notePrefix,
- txType,
- sigType,
- txid,
- round,
- minRound,
- maxRound,
- limit,
- beforeTime,
- afterTime,
- currencyGreater,
- currencyLesser,
- assetIndex,
- addressRole,
- excludeCloseToAsString
- ) {
- let excludeCloseTo = false;
- if (excludeCloseToAsString === 'true') {
- excludeCloseTo = true;
- }
- await this.indexerClient
- .searchForTransactions()
- .address(account)
- .notePrefix(notePrefix)
- .txType(txType)
- .sigType(sigType)
- .txid(txid)
- .round(round)
- .minRound(minRound)
- .maxRound(maxRound)
- .limit(limit)
- .beforeTime(beforeTime)
- .afterTime(afterTime)
- .currencyGreaterThan(currencyGreater)
- .currencyLessThan(currencyLesser)
- .assetID(assetIndex)
- .addressRole(addressRole)
- .excludeCloseTo(excludeCloseTo)
- .do();
- }
- );
-
- When(
- 'we make a SearchForApplications call with {int} and {int}',
- async function (index, round) {
- await this.indexerClient
- .searchForApplications()
- .index(index)
- .round(round)
- .do();
- }
- );
-
When(
'we make a SearchForApplications call with creator {string}',
async function (creator) {
@@ -2640,21 +2405,6 @@ module.exports = function getSteps(options) {
}
);
- When(
- 'we make a SearchForAssets call with limit {int} creator {string} name {string} unit {string} index {int} and nextToken {string}',
- async function (limit, creator, name, unit, index, nextToken) {
- await this.indexerClient
- .searchForAssets()
- .limit(limit)
- .creator(creator)
- .name(name)
- .unit(unit)
- .index(index)
- .nextToken(nextToken)
- .do();
- }
- );
-
When(
'we make a SearchForAssets call with limit {int} creator {string} name {string} unit {string} index {int}',
async function (limit, creator, name, unit, index) {
@@ -2676,13 +2426,6 @@ module.exports = function getSteps(options) {
}
);
- When(
- 'we make a SearchForApplications call with creator {int}',
- async function (index) {
- await this.indexerClient.searchForApplications().creator(index).do();
- }
- );
-
When(
'we make a LookupApplications call with applicationID {int}',
async function (index) {
@@ -2993,825 +2736,12 @@ module.exports = function getSteps(options) {
);
/// /////////////////////////////////
- // begin indexer and integration tests
+ // begin rekey test helpers
/// /////////////////////////////////
- const indexerIntegrationClients = {};
-
- Given(
- 'indexer client {int} at {string} port {int} with token {string}',
- (clientNum, indexerHost, indexerPort, indexerToken) => {
- let mutableIndexerHost = indexerHost;
-
- if (!mutableIndexerHost.startsWith('http')) {
- mutableIndexerHost = `http://${mutableIndexerHost}`;
- }
- indexerIntegrationClients[clientNum] = new algosdk.Indexer(
- indexerToken,
- mutableIndexerHost,
- indexerPort,
- {}
- );
- }
- );
-
- When('I use {int} to check the services health', async (clientNum) => {
- const ic = indexerIntegrationClients[clientNum];
- await ic.makeHealthCheck().do();
- });
-
- Then('I receive status code {int}', async (code) => {
- // Currently only supports the good case. code != 200 should throw an exception.
- assert.strictEqual(code, 200);
- });
-
- let integrationBlockResponse;
-
- When('I use {int} to lookup block {int}', async (clientNum, blockNum) => {
- const ic = indexerIntegrationClients[clientNum];
- integrationBlockResponse = await ic.lookupBlock(blockNum).do();
- });
-
- Then(
- 'The block was confirmed at {int}, contains {int} transactions, has the previous block hash {string}',
- (timestamp, numTransactions, prevHash) => {
- assert.strictEqual(timestamp, integrationBlockResponse.timestamp);
- assert.strictEqual(
- numTransactions,
- integrationBlockResponse.transactions.length
- );
- assert.strictEqual(
- prevHash,
- integrationBlockResponse['previous-block-hash']
- );
- }
- );
-
- let integrationLookupAccountResponse;
-
- When(
- 'I use {int} to lookup account {string} at round {int}',
- async (clientNum, account, round) => {
- const ic = indexerIntegrationClients[clientNum];
- integrationLookupAccountResponse = await ic
- .lookupAccountByID(account)
- .round(round)
- .do();
- }
- );
-
- Then(
- 'The account has {int} assets, the first is asset {int} has a frozen status of {string} and amount {int}.',
- (numAssets, firstAssetIndex, firstAssetFrozenStatus, firstAssetAmount) => {
- const firstAssetFrozenBool = firstAssetFrozenStatus === 'true';
- assert.strictEqual(
- numAssets,
- integrationLookupAccountResponse.account.assets.length
- );
- if (numAssets === 0) {
- return;
- }
- const scrutinizedAsset =
- integrationLookupAccountResponse.account.assets[0];
- assert.strictEqual(firstAssetIndex, scrutinizedAsset['asset-id']);
- assert.strictEqual(firstAssetFrozenBool, scrutinizedAsset['is-frozen']);
- assert.strictEqual(firstAssetAmount, scrutinizedAsset.amount);
- }
- );
-
- Then(
- 'The account created {int} assets, the first is asset {int} is named {string} with a total amount of {int} {string}',
- (
- numCreatedAssets,
- firstCreatedAssetIndex,
- assetName,
- assetIssuance,
- assetUnit
- ) => {
- assert.strictEqual(
- numCreatedAssets,
- integrationLookupAccountResponse.account['created-assets'].length
- );
- const scrutinizedAsset =
- integrationLookupAccountResponse.account['created-assets'][0];
- assert.strictEqual(firstCreatedAssetIndex, scrutinizedAsset.index);
- assert.strictEqual(assetName, scrutinizedAsset.params.name);
- assert.strictEqual(assetIssuance, scrutinizedAsset.params.total);
- assert.strictEqual(assetUnit, scrutinizedAsset.params['unit-name']);
- }
- );
-
- Then(
- 'The account has {int} μalgos and {int} assets, {int} has {int}',
- (microAlgos, numAssets, assetIndexToScrutinize, assetAmount) => {
- assert.strictEqual(
- microAlgos,
- integrationLookupAccountResponse.account.amount
- );
- if (numAssets === 0) {
- return;
- }
- assert.strictEqual(
- numAssets,
- integrationLookupAccountResponse.account.assets.length
- );
- if (assetIndexToScrutinize === 0) {
- return;
- }
- for (
- let idx = 0;
- idx < integrationLookupAccountResponse.account.assets.length;
- idx++
- ) {
- const scrutinizedAsset =
- integrationLookupAccountResponse.account.assets[idx];
- if (scrutinizedAsset.index === assetIndexToScrutinize) {
- assert.strictEqual(assetAmount, scrutinizedAsset.amount);
- }
- }
- }
- );
-
- let integrationLookupAssetResponse;
-
- When('I use {int} to lookup asset {int}', async (clientNum, assetIndex) => {
- const ic = indexerIntegrationClients[clientNum];
- integrationLookupAssetResponse = await ic.lookupAssetByID(assetIndex).do();
- });
-
- Then(
- 'The asset found has: {string}, {string}, {string}, {int}, {string}, {int}, {string}',
- (
- name,
- units,
- creator,
- decimals,
- defaultFrozen,
- totalIssuance,
- clawback
- ) => {
- const assetParams = integrationLookupAssetResponse.asset.params;
- assert.strictEqual(name, assetParams.name);
- assert.strictEqual(units, assetParams['unit-name']);
- assert.strictEqual(creator, assetParams.creator);
- assert.strictEqual(decimals, assetParams.decimals);
- const defaultFrozenBool = defaultFrozen === 'true';
- assert.strictEqual(defaultFrozenBool, assetParams['default-frozen']);
- assert.strictEqual(totalIssuance, assetParams.total);
- assert.strictEqual(clawback, assetParams.clawback);
- }
- );
-
- let integrationLookupAssetBalancesResponse;
-
- When(
- 'I use {int} to lookup asset balances for {int} with {int}, {int}, {int} and token {string}',
- async (
- clientNum,
- assetIndex,
- currencyGreater,
- currencyLesser,
- limit,
- nextToken
- ) => {
- const ic = indexerIntegrationClients[clientNum];
- integrationLookupAssetBalancesResponse = await ic
- .lookupAssetBalances(assetIndex)
- .currencyGreaterThan(currencyGreater)
- .currencyLessThan(currencyLesser)
- .limit(limit)
- .nextToken(nextToken)
- .do();
- }
- );
-
- When(
- 'I get the next page using {int} to lookup asset balances for {int} with {int}, {int}, {int}',
- async (clientNum, assetIndex, currencyGreater, currencyLesser, limit) => {
- const ic = indexerIntegrationClients[clientNum];
- const nextToken = integrationLookupAssetBalancesResponse['next-token'];
- integrationLookupAssetBalancesResponse = await ic
- .lookupAssetBalances(assetIndex)
- .currencyGreaterThan(currencyGreater)
- .currencyLessThan(currencyLesser)
- .limit(limit)
- .nextToken(nextToken)
- .do();
- }
- );
-
- Then(
- 'There are {int} with the asset, the first is {string} has {string} and {int}',
- (numAccounts, firstAccountAddress, isFrozenString, accountAmount) => {
- assert.strictEqual(
- numAccounts,
- integrationLookupAssetBalancesResponse.balances.length
- );
- if (numAccounts === 0) {
- return;
- }
- const firstHolder = integrationLookupAssetBalancesResponse.balances[0];
- assert.strictEqual(firstAccountAddress, firstHolder.address);
- const isFrozenBool = isFrozenString === 'true';
- assert.strictEqual(isFrozenBool, firstHolder['is-frozen']);
- assert.strictEqual(accountAmount, firstHolder.amount);
- }
- );
-
- let integrationSearchAccountsResponse;
-
- When(
- 'I use {int} to search for an account with {int}, {int}, {int}, {int} and token {string}',
- async (
- clientNum,
- assetIndex,
- limit,
- currencyGreater,
- currencyLesser,
- nextToken
- ) => {
- const ic = indexerIntegrationClients[clientNum];
- integrationSearchAccountsResponse = await ic
- .searchAccounts()
- .assetID(assetIndex)
- .currencyGreaterThan(currencyGreater)
- .currencyLessThan(currencyLesser)
- .limit(limit)
- .nextToken(nextToken)
- .do();
- }
- );
-
- When(
- 'I use {int} to search for an account with {int}, {int}, {int}, {int}, {string}, {int} and token {string}',
- async function (
- clientNum,
- assetIndex,
- limit,
- currencyGreater,
- currencyLesser,
- authAddr,
- appID,
- nextToken
- ) {
- const ic = indexerIntegrationClients[clientNum];
- integrationSearchAccountsResponse = await ic
- .searchAccounts()
- .assetID(assetIndex)
- .currencyGreaterThan(currencyGreater)
- .currencyLessThan(currencyLesser)
- .limit(limit)
- .authAddr(authAddr)
- .applicationID(appID)
- .nextToken(nextToken)
- .do();
- this.responseForDirectJsonComparison = integrationSearchAccountsResponse;
- }
- );
-
- When(
- 'I use {int} to search for an account with {int}, {int}, {int}, {int}, {string}, {int}, {string} and token {string}',
- async function (
- clientNum,
- assetIndex,
- limit,
- currencyGreater,
- currencyLesser,
- authAddr,
- appID,
- includeAll,
- nextToken
- ) {
- const ic = indexerIntegrationClients[clientNum];
- integrationSearchAccountsResponse = await ic
- .searchAccounts()
- .assetID(assetIndex)
- .currencyGreaterThan(currencyGreater)
- .currencyLessThan(currencyLesser)
- .limit(limit)
- .authAddr(authAddr)
- .applicationID(appID)
- .includeAll(formatIncludeAll(includeAll))
- .nextToken(nextToken)
- .do();
- this.responseForDirectJsonComparison = integrationSearchAccountsResponse;
- }
- );
-
- Then(
- 'There are {int}, the first has {int}, {int}, {int}, {int}, {string}, {int}, {string}, {string}',
- (
- numAccounts,
- pendingRewards,
- rewardsBase,
- rewards,
- withoutRewards,
- address,
- amount,
- status,
- type
- ) => {
- assert.strictEqual(
- numAccounts,
- integrationSearchAccountsResponse.accounts.length
- );
- if (numAccounts === 0) {
- return;
- }
- const scrutinizedAccount = integrationSearchAccountsResponse.accounts[0];
- assert.strictEqual(pendingRewards, scrutinizedAccount['pending-rewards']);
- assert.strictEqual(rewardsBase, scrutinizedAccount['reward-base']);
- assert.strictEqual(rewards, scrutinizedAccount.rewards);
- assert.strictEqual(
- withoutRewards,
- scrutinizedAccount['amount-without-pending-rewards']
- );
- assert.strictEqual(address, scrutinizedAccount.address);
- assert.strictEqual(amount, scrutinizedAccount.amount);
- assert.strictEqual(status, scrutinizedAccount.status);
- if (type) {
- assert.strictEqual(type, scrutinizedAccount['sig-type']);
- }
- }
- );
-
- Then(
- 'I get the next page using {int} to search for an account with {int}, {int}, {int} and {int}',
- async (clientNum, assetIndex, limit, currencyGreater, currencyLesser) => {
- const ic = indexerIntegrationClients[clientNum];
- const nextToken = integrationSearchAccountsResponse['next-token'];
- integrationSearchAccountsResponse = await ic
- .searchAccounts()
- .assetID(assetIndex)
- .currencyGreaterThan(currencyGreater)
- .currencyLessThan(currencyLesser)
- .limit(limit)
- .nextToken(nextToken)
- .do();
- }
- );
-
- Then(
- 'The first account is online and has {string}, {int}, {int}, {int}, {string}, {string}',
- (address, keyDilution, firstValid, lastValid, voteKey, selKey) => {
- const scrutinizedAccount = integrationSearchAccountsResponse.accounts[0];
- assert.strictEqual('Online', scrutinizedAccount.status);
- assert.strictEqual(address, scrutinizedAccount.address);
- assert.strictEqual(
- keyDilution,
- scrutinizedAccount.participation['vote-key-dilution']
- );
- assert.strictEqual(
- firstValid,
- scrutinizedAccount.participation['vote-first-valid']
- );
- assert.strictEqual(
- lastValid,
- scrutinizedAccount.participation['vote-last-valid']
- );
- assert.strictEqual(
- voteKey,
- scrutinizedAccount.participation['vote-participation-key']
- );
- assert.strictEqual(
- selKey,
- scrutinizedAccount.participation['selection-participation-key']
- );
- }
- );
-
- let integrationSearchTransactionsResponse;
-
- When(
- 'I use {int} to search for transactions with {int}, {string}, {string}, {string}, {string}, {int}, {int}, {int}, {int}, {string}, {string}, {int}, {int}, {string}, {string}, {string} and token {string}',
- async (
- clientNum,
- limit,
- notePrefix,
- txType,
- sigType,
- txid,
- round,
- minRound,
- maxRound,
- assetId,
- beforeTime,
- afterTime,
- currencyGreater,
- currencyLesser,
- address,
- addressRole,
- excludeCloseToString,
- nextToken
- ) => {
- const ic = indexerIntegrationClients[clientNum];
- const excludeCloseToBool = excludeCloseToString === 'true';
- integrationSearchTransactionsResponse = await ic
- .searchForTransactions()
- .limit(limit)
- .notePrefix(notePrefix)
- .txType(txType)
- .sigType(sigType)
- .txid(txid)
- .round(round)
- .minRound(minRound)
- .maxRound(maxRound)
- .assetID(assetId)
- .beforeTime(beforeTime)
- .afterTime(afterTime)
- .currencyGreaterThan(currencyGreater)
- .currencyLessThan(currencyLesser)
- .address(address)
- .addressRole(addressRole)
- .excludeCloseTo(excludeCloseToBool)
- .nextToken(nextToken)
- .do();
- }
- );
-
- When(
- 'I use {int} to search for transactions with {int}, {string}, {string}, {string}, {string}, {int}, {int}, {int}, {int}, {string}, {string}, {int}, {int}, {string}, {string}, {string}, {int} and token {string}',
- async function (
- clientNum,
- limit,
- notePrefix,
- txType,
- sigType,
- txid,
- round,
- minRound,
- maxRound,
- assetId,
- beforeTime,
- afterTime,
- currencyGreater,
- currencyLesser,
- address,
- addressRole,
- excludeCloseToString,
- appID,
- nextToken
- ) {
- const ic = indexerIntegrationClients[clientNum];
- const excludeCloseToBool = excludeCloseToString === 'true';
- integrationSearchTransactionsResponse = await ic
- .searchForTransactions()
- .limit(limit)
- .notePrefix(notePrefix)
- .txType(txType)
- .sigType(sigType)
- .txid(txid)
- .round(round)
- .minRound(minRound)
- .maxRound(maxRound)
- .assetID(assetId)
- .beforeTime(beforeTime)
- .afterTime(afterTime)
- .currencyGreaterThan(currencyGreater)
- .currencyLessThan(currencyLesser)
- .address(address)
- .addressRole(addressRole)
- .excludeCloseTo(excludeCloseToBool)
- .applicationID(appID)
- .nextToken(nextToken)
- .do();
- this.responseForDirectJsonComparison = integrationSearchTransactionsResponse;
- }
- );
-
- When(
- 'I use {int} to search for all {string} transactions',
- async (clientNum, account) => {
- const ic = indexerIntegrationClients[clientNum];
- integrationSearchTransactionsResponse = await ic
- .searchForTransactions()
- .address(account)
- .do();
- }
- );
-
- When(
- 'I use {int} to search for all {int} asset transactions',
- async (clientNum, assetIndex) => {
- const ic = indexerIntegrationClients[clientNum];
- integrationSearchTransactionsResponse = await ic
- .searchForTransactions()
- .assetID(assetIndex)
- .do();
- }
- );
-
- When(
- 'I use {int} to search for applications with {int}, {int}, and token {string}',
- async function (clientNum, limit, appID, token) {
- const ic = indexerIntegrationClients[clientNum];
- this.responseForDirectJsonComparison = await ic
- .searchForApplications()
- .limit(limit)
- .index(appID)
- .nextToken(token)
- .do();
- }
- );
-
- When(
- 'I use {int} to search for applications with {int}, {int}, {string} and token {string}',
- async function (clientNum, limit, appID, includeAll, token) {
- const ic = indexerIntegrationClients[clientNum];
- this.responseForDirectJsonComparison = await ic
- .searchForApplications()
- .limit(limit)
- .index(appID)
- .includeAll(formatIncludeAll(includeAll))
- .nextToken(token)
- .do();
- }
- );
-
- When(
- 'I use {int} to lookup application with {int}',
- async function (clientNum, appID) {
- const ic = indexerIntegrationClients[clientNum];
- this.responseForDirectJsonComparison = await ic
- .lookupApplications(appID)
- .do();
- }
- );
-
- When(
- 'I use {int} to lookup application with {int} and {string}',
- async function (clientNum, appID, includeAll) {
- const ic = indexerIntegrationClients[clientNum];
- try {
- this.responseForDirectJsonComparison = await ic
- .lookupApplications(appID)
- .includeAll(formatIncludeAll(includeAll))
- .do();
- } catch (err) {
- if (err.status !== 404) {
- throw err;
- }
- this.responseForDirectJsonComparison = err.response.body;
- }
- }
- );
-
- function sortKeys(x) {
- // recursively sorts on keys, unless the passed object is an array of dicts that all contain the property 'key',
- // in which case it sorts on the value corresponding to key 'key'
- if (typeof x !== 'object' || !x) return x;
- if (Array.isArray(x)) {
- if (
- x.every(
- (subobject) =>
- typeof subobject === 'object' &&
- Object.prototype.hasOwnProperty.call(subobject, 'key')
- )
- ) {
- return x.sort((a, b) => (a.key > b.key ? 1 : -1));
- }
- return x.map(sortKeys);
- }
- return Object.keys(x)
- .sort()
- .reduce((o, k) => ({ ...o, [k]: sortKeys(x[k]) }), {});
- }
-
- Then('the parsed response should equal {string}.', async function (jsonFile) {
- const rawResponse = await loadResource(jsonFile);
- const responseFromFile = sortKeys(JSON.parse(rawResponse.toString()));
- this.responseForDirectJsonComparison = sortKeys(
- this.responseForDirectJsonComparison
- );
- assert.strictEqual(
- JSON.stringify(this.responseForDirectJsonComparison),
- JSON.stringify(responseFromFile)
- );
- });
-
- When(
- 'I get the next page using {int} to search for transactions with {int} and {int}',
- async (clientNum, limit, maxRound) => {
- const ic = indexerIntegrationClients[clientNum];
- const nextToken = integrationSearchTransactionsResponse['next-token'];
- integrationSearchTransactionsResponse = await ic
- .searchForTransactions()
- .limit(limit)
- .maxRound(maxRound)
- .nextToken(nextToken)
- .do();
- }
- );
-
- Then(
- 'there are {int} transactions in the response, the first is {string}.',
- (numTransactions, txid) => {
- assert.strictEqual(
- numTransactions,
- integrationSearchTransactionsResponse.transactions.length
- );
- if (numTransactions === 0) {
- return;
- }
- assert.strictEqual(
- txid,
- integrationSearchTransactionsResponse.transactions[0].id
- );
- }
- );
-
- Then('Every transaction has tx-type {string}', (txType) => {
- for (
- let idx = 0;
- idx < integrationSearchTransactionsResponse.transactions.length;
- idx++
- ) {
- const scrutinizedTxn =
- integrationSearchTransactionsResponse.transactions[idx];
- assert.strictEqual(txType, scrutinizedTxn['tx-type']);
- }
- });
-
- Then('Every transaction has sig-type {string}', (sigType) => {
- function getSigTypeFromTxnResponse(txn) {
- if (txn.signature.logicsig) {
- return 'lsig';
- }
- if (txn.signature.sig) {
- return 'sig';
- }
- if (txn.signature.multisig) {
- return 'msig';
- }
- return 'did not recognize sigtype of txn';
- }
- for (
- let idx = 0;
- idx < integrationSearchTransactionsResponse.transactions.length;
- idx++
- ) {
- const scrutinizedTxn =
- integrationSearchTransactionsResponse.transactions[idx];
- assert.strictEqual(sigType, getSigTypeFromTxnResponse(scrutinizedTxn));
- }
- });
-
- Then('Every transaction has round {int}', (round) => {
- for (
- let idx = 0;
- idx < integrationSearchTransactionsResponse.transactions.length;
- idx++
- ) {
- const scrutinizedTxn =
- integrationSearchTransactionsResponse.transactions[idx];
- assert.strictEqual(round, scrutinizedTxn['confirmed-round']);
- }
- });
-
- Then('Every transaction has round >= {int}', (round) => {
- for (
- let idx = 0;
- idx < integrationSearchTransactionsResponse.transactions.length;
- idx++
- ) {
- const scrutinizedTxn =
- integrationSearchTransactionsResponse.transactions[idx];
- assert.ok(round <= scrutinizedTxn['confirmed-round']);
- }
- });
-
- Then('Every transaction has round <= {int}', (round) => {
- for (
- let idx = 0;
- idx < integrationSearchTransactionsResponse.transactions.length;
- idx++
- ) {
- const scrutinizedTxn =
- integrationSearchTransactionsResponse.transactions[idx];
- assert.ok(round >= scrutinizedTxn['confirmed-round']);
- }
- });
-
- Then('Every transaction works with asset-id {int}', (assetId) => {
- function extractIdFromTransaction(txn) {
- if (txn['created-asset-index']) {
- return txn['created-asset-index'];
- }
- if (txn['asset-config-transaction']) {
- return txn['asset-config-transaction']['asset-id'];
- }
- if (txn['asset-transfer-transaction']) {
- return txn['asset-transfer-transaction']['asset-id'];
- }
- if (txn['asset-freeze-transaction']) {
- return txn['asset-freeze-transaction']['asset-id'];
- }
- return 'could not find asset id within txn';
- }
- for (
- let idx = 0;
- idx < integrationSearchTransactionsResponse.transactions.length;
- idx++
- ) {
- const scrutinizedTxn =
- integrationSearchTransactionsResponse.transactions[idx];
- assert.strictEqual(assetId, extractIdFromTransaction(scrutinizedTxn));
- }
- });
-
- Then('Every transaction is older than {string}', (olderThan) => {
- for (
- let idx = 0;
- idx < integrationSearchTransactionsResponse.transactions.length;
- idx++
- ) {
- const scrutinizedTxn =
- integrationSearchTransactionsResponse.transactions[idx];
- assert.ok(scrutinizedTxn['round-time'] < Date.parse(olderThan) / 1000);
- }
- });
-
- Then('Every transaction is newer than {string}', (newerThan) => {
- for (
- let idx = 0;
- idx < integrationSearchTransactionsResponse.transactions.length;
- idx++
- ) {
- const scrutinizedTxn =
- integrationSearchTransactionsResponse.transactions[idx];
- assert.ok(scrutinizedTxn['round-time'] > Date.parse(newerThan) / 1000);
- }
- });
-
- Then(
- 'Every transaction moves between {int} and {int} currency',
- (lowerBound, upperBound) => {
- function getAmountMoved(txn) {
- if (txn['payment-transaction']) {
- return txn['payment-transaction'].amount;
- }
- if (txn['asset-transfer-transaction']) {
- return txn['asset-transfer-transaction'].amount;
- }
- return 'could not get amount moved from txn';
- }
- for (
- let idx = 0;
- idx < integrationSearchTransactionsResponse.transactions.length;
- idx++
- ) {
- const scrutinizedTxn =
- integrationSearchTransactionsResponse.transactions[idx];
- const amountMoved = getAmountMoved(scrutinizedTxn);
- if (upperBound !== 0) {
- assert.ok(amountMoved <= upperBound);
- }
- assert.ok(amountMoved >= lowerBound);
- }
- }
- );
-
- let integrationSearchAssetsResponse;
-
- When(
- 'I use {int} to search for assets with {int}, {int}, {string}, {string}, {string}, and token {string}',
- async (clientNum, zero, assetId, creator, name, unit, nextToken) => {
- const ic = indexerIntegrationClients[clientNum];
- integrationSearchAssetsResponse = await ic
- .searchForAssets()
- .index(assetId)
- .creator(creator)
- .name(name)
- .unit(unit)
- .nextToken(nextToken)
- .do();
- }
- );
-
- Then(
- 'there are {int} assets in the response, the first is {int}.',
- (numAssets, firstAssetId) => {
- assert.strictEqual(
- numAssets,
- integrationSearchAssetsResponse.assets.length
- );
- if (numAssets === 0) {
- return;
- }
- assert.strictEqual(
- firstAssetId,
- integrationSearchAssetsResponse.assets[0].index
- );
- }
- );
-
- /// /////////////////////////////////
- // begin rekey test helpers
- /// /////////////////////////////////
-
- When('I add a rekeyTo field with address {string}', function (address) {
- this.txn.reKeyTo = address;
- });
+ When('I add a rekeyTo field with address {string}', function (address) {
+ this.txn.reKeyTo = address;
+ });
When(
'I add a rekeyTo field with the private key algorand address',
@@ -5584,6 +4514,43 @@ module.exports = function getSteps(options) {
}
);
+ When(
+ 'we make a GetLightBlockHeaderProof call for round {int}',
+ async function (int) {
+ await this.v2Client.getLightBlockHeaderProof(int).do();
+ }
+ );
+
+ When('we make a GetStateProof call for round {int}', async function (int) {
+ await this.v2Client.getStateProof(int).do();
+ });
+
+ Given(
+ 'a base64 encoded program bytes for heuristic sanity check {string}',
+ async function (programByteStr) {
+ this.seeminglyProgram = new Uint8Array(
+ Buffer.from(programByteStr, 'base64')
+ );
+ }
+ );
+
+ When('I start heuristic sanity check over the bytes', async function () {
+ this.actualErrMsg = undefined;
+ try {
+ new algosdk.LogicSigAccount(this.seeminglyProgram); // eslint-disable-line
+ } catch (e) {
+ this.actualErrMsg = e.message;
+ }
+ });
+
+ Then(
+ 'if the heuristic sanity check throws an error, the error contains {string}',
+ async function (errMsg) {
+ if (errMsg !== '') assert.ok(this.actualErrMsg.includes(errMsg));
+ else assert.strictEqual(this.actualErrMsg, undefined);
+ }
+ );
+
if (!options.ignoreReturn) {
return steps;
}
diff --git a/tests/cucumber/unit.tags b/tests/cucumber/unit.tags
new file mode 100644
index 000000000..2d3f46b8e
--- /dev/null
+++ b/tests/cucumber/unit.tags
@@ -0,0 +1,26 @@
+@unit.abijson
+@unit.abijson.byname
+@unit.algod
+@unit.algod.ledger_refactoring
+@unit.applications
+@unit.atomic_transaction_composer
+@unit.dryrun
+@unit.dryrun.trace.application
+@unit.feetest
+@unit.indexer
+@unit.indexer.ledger_refactoring
+@unit.indexer.logs
+@unit.offline
+@unit.program_sanity_check
+@unit.rekey
+@unit.responses
+@unit.responses.231
+@unit.responses.unlimited_assets
+@unit.sourcemap
+@unit.stateproof.paths
+@unit.stateproof.responses
+@unit.stateproof.responses.msgp
+@unit.tealsign
+@unit.transactions
+@unit.transactions.keyreg
+@unit.transactions.payment