From a122ae11218ac5069e3fcc76f71f4fbd8ec77372 Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Thu, 30 Jun 2022 07:52:41 -0400 Subject: [PATCH 01/15] Boxes for application calls in static array (#573) * Initial work to define box types in appl calls * More changes to boxes field * Change boxes to array types, fix transactions, and tests * Write box translation tests * Add cucumber test steps * Revert pretty print * Fix decoding for txn boxes * Revise tests to make box args similar to app args * Clean up some code and add a blank test case * Fix err case * Fix box ref catch * Delete box name field for length 0 array * Revise guard * Address some PR feedback * Fix box translation condition from index to id * Alias own reference condition * Fix unit tests and add more comments * Add guard and change reference condition for better readability * Refactor box reference condition * Export box reference type in index.ts and change SDK testing branch * Revert back test branch to older version * Remove redundant null check on array --- src/boxStorage.ts | 42 +++++++++++++ src/composer.ts | 7 +++ src/makeTxn.ts | 37 +++++++++++ src/transaction.ts | 38 +++++++++++ src/types/transactions/application.ts | 3 + src/types/transactions/base.ts | 20 ++++++ src/types/transactions/encoded.ts | 17 +++++ src/types/transactions/index.ts | 7 ++- tests/5.Transaction.js | 55 ++++++++++++++++ tests/cucumber/docker/run_docker.sh | 2 +- tests/cucumber/steps/steps.js | 91 +++++++++++++++++++-------- 11 files changed, 291 insertions(+), 28 deletions(-) create mode 100644 src/boxStorage.ts diff --git a/src/boxStorage.ts b/src/boxStorage.ts new file mode 100644 index 000000000..6d07014ac --- /dev/null +++ b/src/boxStorage.ts @@ -0,0 +1,42 @@ +import { EncodedBoxReference } from './types'; +import { BoxReference } from './types/transactions/base'; + +function translateBoxReference( + reference: BoxReference, + foreignApps: number[], + appIndex: number +): EncodedBoxReference { + const referenceId = reference.appIndex; + const referenceName = reference.name; + const isOwnReference = referenceId === 0 || referenceId === appIndex; + let index = 0; + + if (foreignApps != null) { + // Foreign apps start from index 1; index 0 is its own app ID. + index = foreignApps.indexOf(referenceId) + 1; + } + // Check if the app referenced is itself after checking the foreign apps array. + // If index is zero, then the app ID was not found in the foreign apps array + // or the foreign apps array was null. + if (index === 0 && !isOwnReference) { + // Error if the app is trying to reference a foreign app that was not in + // its own foreign apps array. + throw new Error(`Box ref with appId ${referenceId} not in foreign-apps`); + } + return { i: index, n: referenceName }; +} + +/** + * translateBoxReferences translates an array of BoxReferences with app IDs + * into an array of EncodedBoxReferences with foreign indices. + */ +export function translateBoxReferences( + references: BoxReference[], + foreignApps: number[], + appIndex: number +): EncodedBoxReference[] { + if (!Array.isArray(references)) return []; + return references.map((bx) => + translateBoxReference(bx, foreignApps, appIndex) + ); +} diff --git a/src/composer.ts b/src/composer.ts index b5afa2fa1..b9756b61c 100644 --- a/src/composer.ts +++ b/src/composer.ts @@ -21,6 +21,7 @@ import { isTransactionWithSigner, } from './signer'; import { + BoxReference, OnApplicationComplete, SuggestedParams, } from './types/transactions/base'; @@ -201,6 +202,7 @@ export class AtomicTransactionComposer { numLocalInts, numLocalByteSlices, extraPages, + boxes, note, lease, rekeyTo, @@ -232,6 +234,8 @@ export class AtomicTransactionComposer { numLocalByteSlices?: number; /** The number of extra pages to allocate for the application's programs. Only set this if this is an application creation call. If omitted, defaults to 0. */ extraPages?: number; + /** The box references for this application call */ + boxes?: BoxReference[]; /** The note value for this application call */ note?: Uint8Array; /** The lease value for this application call */ @@ -317,6 +321,8 @@ export class AtomicTransactionComposer { const refArgTypes: ABIReferenceType[] = []; const refArgValues: ABIValue[] = []; const refArgIndexToBasicArgIndex: Map = new Map(); + // TODO: Box encoding for ABI + const boxReferences: BoxReference[] = !boxes ? [] : boxes; for (let i = 0; i < methodArgs.length; i++) { let argType = method.args[i].type; @@ -437,6 +443,7 @@ export class AtomicTransactionComposer { accounts: foreignAccounts, foreignApps, foreignAssets, + boxes: boxReferences, onComplete: onComplete == null ? OnApplicationComplete.NoOpOC : onComplete, approvalProgram, diff --git a/src/makeTxn.ts b/src/makeTxn.ts index d0782e6de..9b468cf1a 100644 --- a/src/makeTxn.ts +++ b/src/makeTxn.ts @@ -1100,6 +1100,7 @@ export function makeAssetTransferTxnWithSuggestedParamsFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index + * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions @@ -1119,6 +1120,7 @@ export function makeApplicationCreateTxn( accounts?: AppCreateTxn['appAccounts'], foreignApps?: AppCreateTxn['appForeignApps'], foreignAssets?: AppCreateTxn['appForeignAssets'], + boxes?: AppCreateTxn['boxes'], note?: AppCreateTxn['note'], lease?: AppCreateTxn['lease'], rekeyTo?: AppCreateTxn['reKeyTo'], @@ -1140,6 +1142,7 @@ export function makeApplicationCreateTxn( appAccounts: accounts, appForeignApps: foreignApps, appForeignAssets: foreignAssets, + boxes, note, lease, reKeyTo: rekeyTo, @@ -1181,6 +1184,7 @@ export function makeApplicationCreateTxnFromObject( | 'accounts' | 'foreignApps' | 'foreignAssets' + | 'boxes' | 'note' | 'lease' | 'rekeyTo' @@ -1202,6 +1206,7 @@ export function makeApplicationCreateTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, + o.boxes, o.note, o.lease, o.rekeyTo, @@ -1227,6 +1232,7 @@ export function makeApplicationCreateTxnFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index + * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions @@ -1241,6 +1247,7 @@ export function makeApplicationUpdateTxn( accounts?: AppUpdateTxn['appAccounts'], foreignApps?: AppUpdateTxn['appForeignApps'], foreignAssets?: AppUpdateTxn['appForeignAssets'], + boxes?: AppUpdateTxn['boxes'], note?: AppUpdateTxn['note'], lease?: AppUpdateTxn['lease'], rekeyTo?: AppUpdateTxn['reKeyTo'] @@ -1257,6 +1264,7 @@ export function makeApplicationUpdateTxn( appAccounts: accounts, appForeignApps: foreignApps, appForeignAssets: foreignAssets, + boxes, note, lease, reKeyTo: rekeyTo, @@ -1288,6 +1296,7 @@ export function makeApplicationUpdateTxnFromObject( | 'accounts' | 'foreignApps' | 'foreignAssets' + | 'boxes' | 'note' | 'lease' | 'rekeyTo' @@ -1304,6 +1313,7 @@ export function makeApplicationUpdateTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, + o.boxes, o.note, o.lease, o.rekeyTo @@ -1326,6 +1336,7 @@ export function makeApplicationUpdateTxnFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index + * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions @@ -1338,6 +1349,7 @@ export function makeApplicationDeleteTxn( accounts?: AppDeleteTxn['appAccounts'], foreignApps?: AppDeleteTxn['appForeignApps'], foreignAssets?: AppDeleteTxn['appForeignAssets'], + boxes?: AppDeleteTxn['boxes'], note?: AppDeleteTxn['note'], lease?: AppDeleteTxn['lease'], rekeyTo?: AppDeleteTxn['reKeyTo'] @@ -1352,6 +1364,7 @@ export function makeApplicationDeleteTxn( appAccounts: accounts, appForeignApps: foreignApps, appForeignAssets: foreignAssets, + boxes, note, lease, reKeyTo: rekeyTo, @@ -1379,6 +1392,7 @@ export function makeApplicationDeleteTxnFromObject( | 'accounts' | 'foreignApps' | 'foreignAssets' + | 'boxes' | 'note' | 'lease' | 'rekeyTo' @@ -1393,6 +1407,7 @@ export function makeApplicationDeleteTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, + o.boxes, o.note, o.lease, o.rekeyTo @@ -1415,6 +1430,7 @@ export function makeApplicationDeleteTxnFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index + * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions @@ -1427,6 +1443,7 @@ export function makeApplicationOptInTxn( accounts?: AppOptInTxn['appAccounts'], foreignApps?: AppOptInTxn['appForeignApps'], foreignAssets?: AppOptInTxn['appForeignAssets'], + boxes?: AppOptInTxn['boxes'], note?: AppOptInTxn['note'], lease?: AppOptInTxn['lease'], rekeyTo?: AppOptInTxn['reKeyTo'] @@ -1441,6 +1458,7 @@ export function makeApplicationOptInTxn( appAccounts: accounts, appForeignApps: foreignApps, appForeignAssets: foreignAssets, + boxes, note, lease, reKeyTo: rekeyTo, @@ -1468,6 +1486,7 @@ export function makeApplicationOptInTxnFromObject( | 'accounts' | 'foreignApps' | 'foreignAssets' + | 'boxes' | 'note' | 'lease' | 'rekeyTo' @@ -1482,6 +1501,7 @@ export function makeApplicationOptInTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, + o.boxes, o.note, o.lease, o.rekeyTo @@ -1504,6 +1524,7 @@ export function makeApplicationOptInTxnFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index + * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions @@ -1516,6 +1537,7 @@ export function makeApplicationCloseOutTxn( accounts?: AppCloseOutTxn['appAccounts'], foreignApps?: AppCloseOutTxn['appForeignApps'], foreignAssets?: AppCloseOutTxn['appForeignAssets'], + boxes?: AppCloseOutTxn['boxes'], note?: AppCloseOutTxn['note'], lease?: AppCloseOutTxn['lease'], rekeyTo?: AppCloseOutTxn['reKeyTo'] @@ -1530,6 +1552,7 @@ export function makeApplicationCloseOutTxn( appAccounts: accounts, appForeignApps: foreignApps, appForeignAssets: foreignAssets, + boxes, note, lease, reKeyTo: rekeyTo, @@ -1557,6 +1580,7 @@ export function makeApplicationCloseOutTxnFromObject( | 'accounts' | 'foreignApps' | 'foreignAssets' + | 'boxes' | 'note' | 'lease' | 'rekeyTo' @@ -1571,6 +1595,7 @@ export function makeApplicationCloseOutTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, + o.boxes, o.note, o.lease, o.rekeyTo @@ -1593,6 +1618,7 @@ export function makeApplicationCloseOutTxnFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index + * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions @@ -1605,6 +1631,7 @@ export function makeApplicationClearStateTxn( accounts?: AppClearStateTxn['appAccounts'], foreignApps?: AppClearStateTxn['appForeignApps'], foreignAssets?: AppClearStateTxn['appForeignAssets'], + boxes?: AppClearStateTxn['boxes'], note?: AppClearStateTxn['note'], lease?: AppClearStateTxn['lease'], rekeyTo?: AppClearStateTxn['reKeyTo'] @@ -1619,6 +1646,7 @@ export function makeApplicationClearStateTxn( appAccounts: accounts, appForeignApps: foreignApps, appForeignAssets: foreignAssets, + boxes, note, lease, reKeyTo: rekeyTo, @@ -1646,6 +1674,7 @@ export function makeApplicationClearStateTxnFromObject( | 'accounts' | 'foreignApps' | 'foreignAssets' + | 'boxes' | 'note' | 'lease' | 'rekeyTo' @@ -1660,6 +1689,7 @@ export function makeApplicationClearStateTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, + o.boxes, o.note, o.lease, o.rekeyTo @@ -1682,6 +1712,7 @@ export function makeApplicationClearStateTxnFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index + * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions @@ -1694,6 +1725,7 @@ export function makeApplicationNoOpTxn( accounts?: AppNoOpTxn['appAccounts'], foreignApps?: AppNoOpTxn['appForeignApps'], foreignAssets?: AppNoOpTxn['appForeignAssets'], + boxes?: AppNoOpTxn['boxes'], note?: AppNoOpTxn['note'], lease?: AppNoOpTxn['lease'], rekeyTo?: AppNoOpTxn['reKeyTo'] @@ -1708,6 +1740,7 @@ export function makeApplicationNoOpTxn( appAccounts: accounts, appForeignApps: foreignApps, appForeignAssets: foreignAssets, + boxes, note, lease, reKeyTo: rekeyTo, @@ -1735,6 +1768,7 @@ export function makeApplicationNoOpTxnFromObject( | 'accounts' | 'foreignApps' | 'foreignAssets' + | 'boxes' | 'note' | 'lease' | 'rekeyTo' @@ -1749,6 +1783,7 @@ export function makeApplicationNoOpTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, + o.boxes, o.note, o.lease, o.rekeyTo @@ -1781,6 +1816,7 @@ export function makeApplicationCallTxnFromObject( | 'accounts' | 'foreignApps' | 'foreignAssets' + | 'boxes' | 'note' | 'lease' | 'rekeyTo' @@ -1825,6 +1861,7 @@ export function makeApplicationCallTxnFromObject( appAccounts: options.accounts, appForeignApps: options.foreignApps, appForeignAssets: options.foreignAssets, + boxes: options.boxes, note: options.note, lease: options.lease, reKeyTo: options.rekeyTo, diff --git a/src/transaction.ts b/src/transaction.ts index 10825a43e..1d9ce87b7 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -3,11 +3,13 @@ import * as address from './encoding/address'; import * as encoding from './encoding/encoding'; import * as nacl from './nacl/naclWrappers'; import * as utils from './utils/utils'; +import { translateBoxReferences } from './boxStorage'; import { OnApplicationComplete, TransactionParams, TransactionType, isTransactionType, + BoxReference, } from './types/transactions/base'; import AnyTransaction, { MustHaveSuggestedParams, @@ -110,6 +112,7 @@ interface TransactionStorageStructure nonParticipation?: boolean; group?: Buffer; extraPages?: number; + boxes?: BoxReference[]; } function getKeyregKey( @@ -192,6 +195,7 @@ export class Transaction implements TransactionStorageStructure { appAccounts?: Address[]; appForeignApps?: number[]; appForeignAssets?: number[]; + boxes?: BoxReference[]; type?: TransactionType; flatFee: boolean; reKeyTo?: Address; @@ -415,6 +419,20 @@ export class Transaction implements TransactionStorageStructure { ); }); } + if (txn.boxes !== undefined) { + if (!Array.isArray(txn.boxes)) + throw Error('boxes must be an Array of BoxReference.'); + txn.boxes = txn.boxes.slice(); + txn.boxes.forEach((box) => { + if ( + !Number.isSafeInteger(box.appIndex) || + box.name.constructor !== Uint8Array + ) + throw Error( + 'box app index must be a number and name must be an Uint8Array.' + ); + }); + } if ( txn.assetMetadataHash !== undefined && txn.assetMetadataHash.length !== 0 @@ -783,6 +801,11 @@ export class Transaction implements TransactionStorageStructure { apfa: this.appForeignApps, apas: this.appForeignAssets, apep: this.extraPages, + apbx: translateBoxReferences( + this.boxes, + this.appForeignApps, + this.appIndex + ), }; if (this.reKeyTo !== undefined) { txn.rekey = Buffer.from(this.reKeyTo.publicKey); @@ -821,6 +844,11 @@ export class Transaction implements TransactionStorageStructure { if (!txn.apan) delete txn.apan; if (!txn.apfa || !txn.apfa.length) delete txn.apfa; if (!txn.apas || !txn.apas.length) delete txn.apas; + for (const box of txn.apbx) { + if (!box.i) delete box.i; + if (!box.n || !box.n.length) delete box.n; + } + if (!txn.apbx || !txn.apbx.length) delete txn.apbx; if (!txn.apat || !txn.apat.length) delete txn.apat; if (!txn.apep) delete txn.apep; if (txn.grp === undefined) delete txn.grp; @@ -993,6 +1021,16 @@ export class Transaction implements TransactionStorageStructure { if (txnForEnc.apas !== undefined) { txn.appForeignAssets = txnForEnc.apas; } + if (txnForEnc.apbx !== undefined) { + txn.boxes = txnForEnc.apbx.map((box) => ({ + // Translate foreign app index to app ID + appIndex: + box.i === 0 || box.i === txn.appIndex + ? txn.appIndex + : txn.appForeignApps[box.i - 1], + name: box.n, + })); + } } return txn; } diff --git a/src/types/transactions/application.ts b/src/types/transactions/application.ts index f5d2f19d3..701240f25 100644 --- a/src/types/transactions/application.ts +++ b/src/types/transactions/application.ts @@ -19,6 +19,7 @@ type SpecificParametersForCreate = Pick< | 'appAccounts' | 'appForeignApps' | 'appForeignAssets' + | 'boxes' | 'extraPages' >; @@ -45,6 +46,7 @@ type SpecificParametersForUpdate = Pick< | 'appAccounts' | 'appForeignApps' | 'appForeignAssets' + | 'boxes' >; interface OverwritesForUpdate { @@ -68,6 +70,7 @@ type SpecificParametersForDelete = Pick< | 'appAccounts' | 'appForeignApps' | 'appForeignAssets' + | 'boxes' >; interface OverwritesForDelete { diff --git a/src/types/transactions/base.ts b/src/types/transactions/base.ts index 739c88e76..f94ebdcbd 100644 --- a/src/types/transactions/base.ts +++ b/src/types/transactions/base.ts @@ -125,6 +125,21 @@ export interface SuggestedParams { genesisHash: string; } +/** + * A grouping of the app ID and name of the box in an Uint8Array + */ +export interface BoxReference { + /** + * A unique application index + */ + appIndex: number; + + /** + * Name of box to reference + */ + name: Uint8Array; +} + /** * A full list of all available transaction parameters * @@ -385,4 +400,9 @@ export interface TransactionParams { * Int representing extra pages of memory to rent during an application create transaction. */ extraPages?: number; + + /** + * A grouping of the app ID and name of the box in an Uint8Array + */ + boxes?: BoxReference[]; } diff --git a/src/types/transactions/encoded.ts b/src/types/transactions/encoded.ts index bc5b9e96c..2fdd7e48d 100644 --- a/src/types/transactions/encoded.ts +++ b/src/types/transactions/encoded.ts @@ -83,6 +83,18 @@ export interface EncodedGlobalStateSchema { nbs: number; } +export interface EncodedBoxReference { + /** + * index of the app ID in the foreign apps array + */ + i: number; + + /** + * box name + */ + n: Uint8Array; +} + /** * A rough structure for the encoded transaction object. Every property is labelled with its associated Transaction type property */ @@ -296,6 +308,11 @@ export interface EncodedTransaction { * extraPages */ apep?: number; + + /** + * boxes + */ + apbx?: EncodedBoxReference[]; } export interface EncodedSubsig { diff --git a/src/types/transactions/index.ts b/src/types/transactions/index.ts index 320225953..1ad181ffb 100644 --- a/src/types/transactions/index.ts +++ b/src/types/transactions/index.ts @@ -18,7 +18,12 @@ import { } from './application'; // Utilities -export { TransactionParams, TransactionType, SuggestedParams } from './base'; +export { + TransactionParams, + TransactionType, + SuggestedParams, + BoxReference, +} from './base'; export { MustHaveSuggestedParams, MustHaveSuggestedParamsInline, diff --git a/tests/5.Transaction.js b/tests/5.Transaction.js index 710d16e22..cf8eeba07 100644 --- a/tests/5.Transaction.js +++ b/tests/5.Transaction.js @@ -1,5 +1,6 @@ const assert = require('assert'); const algosdk = require('../index'); +const { translateBoxReferences } = require('../src/boxStorage'); const group = require('../src/group'); describe('Sign', () => { @@ -35,6 +36,7 @@ describe('Sign', () => { ]; const appForeignApps = [17, 200]; const appForeignAssets = [7, 8, 9]; + const boxes = [{ appIndex: 0, name: Uint8Array.from([0]) }]; const o = { from: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', fee: 10, @@ -48,6 +50,7 @@ describe('Sign', () => { appAccounts, appForeignApps, appForeignAssets, + boxes, }; const txn = new algosdk.Transaction(o); assert.deepStrictEqual(appArgs, [ @@ -64,6 +67,7 @@ describe('Sign', () => { assert.ok(txn.appAccounts !== appAccounts); assert.ok(txn.appForeignApps !== appForeignApps); assert.ok(txn.appForeignAssets !== appForeignAssets); + assert.ok(txn.boxes !== boxes); }); it('should not complain on a missing note', () => { @@ -527,6 +531,7 @@ describe('Sign', () => { extraPages: 2, foreignApps: [3, 4], foreignAssets: [5, 6], + boxes: [{ appIndex: 0, name: Uint8Array.from([0]) }], lease: Uint8Array.from(new Array(32).fill(7)), note: new Uint8Array(Buffer.from('note value')), rekeyTo: 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', @@ -1515,5 +1520,55 @@ describe('Sign', () => { assert.deepStrictEqual(txgroup[0].group, txgroup[1].group); }); + it('should be able to translate box references to encoded references', () => { + const testCases = [ + [ + [{ appIndex: 100, name: [0, 1, 2, 3] }], + [100], + 9999, + [{ i: 1, n: [0, 1, 2, 3] }], + ], + [[], [], 9999, []], + [ + [ + { appIndex: 0, name: [0, 1, 2, 3] }, + { appIndex: 9999, name: [4, 5, 6, 7] }, + ], + [100], + 9999, + [ + { i: 0, n: [0, 1, 2, 3] }, + { i: 0, n: [4, 5, 6, 7] }, + ], + ], + [ + [{ appIndex: 100, name: [0, 1, 2, 3] }], + [100], + 100, + [{ i: 1, n: [0, 1, 2, 3] }], + ], + [ + [ + { appIndex: 7777, name: [0, 1, 2, 3] }, + { appIndex: 8888, name: [4, 5, 6, 7] }, + ], + [100, 7777, 8888, 9999], + 9999, + [ + { i: 2, n: [0, 1, 2, 3] }, + { i: 3, n: [4, 5, 6, 7] }, + ], + ], + ]; + for (const testCase of testCases) { + const expected = testCase[3]; + const actual = translateBoxReferences( + testCase[0], + testCase[1], + testCase[2] + ); + assert.deepStrictEqual(expected, actual); + } + }); }); }); diff --git a/tests/cucumber/docker/run_docker.sh b/tests/cucumber/docker/run_docker.sh index 72e6bb977..5db4bb447 100755 --- a/tests/cucumber/docker/run_docker.sh +++ b/tests/cucumber/docker/run_docker.sh @@ -7,7 +7,7 @@ rm -rf test-harness rm -rf tests/cucumber/features # clone test harness -git clone --single-branch --branch master https://github.com/algorand/algorand-sdk-testing.git test-harness +git clone --single-branch --branch box-reference 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 diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 8f03544f6..c9e32e845 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -3993,6 +3993,21 @@ module.exports = function getSteps(options) { return makeUint8Array(data); } + function processAppArgs(subArg) { + switch (subArg[0]) { + case 'str': + return makeUint8Array(Buffer.from(subArg[1])); + case 'int': + return makeUint8Array([parseInt(subArg[1])]); + case 'addr': + return algosdk.decodeAddress(subArg[1]).publicKey; + case 'b64': + return makeUint8Array(Buffer.from(subArg[1], 'base64')); + default: + throw Error(`did not recognize app arg of type${subArg[0]}`); + } + } + function splitAndProcessAppArgs(inArgs) { const splitArgs = inArgs.split(','); const subArgs = []; @@ -4001,28 +4016,32 @@ module.exports = function getSteps(options) { }); const appArgs = []; subArgs.forEach((subArg) => { - switch (subArg[0]) { - case 'str': - appArgs.push(makeUint8Array(Buffer.from(subArg[1]))); - break; - case 'int': - appArgs.push(makeUint8Array([parseInt(subArg[1])])); - break; - case 'addr': - appArgs.push(algosdk.decodeAddress(subArg[1]).publicKey); - break; - case 'b64': - appArgs.push(Buffer.from(subArg[1], 'base64')); - break; - default: - throw Error(`did not recognize app arg of type${subArg[0]}`); - } + appArgs.push(processAppArgs(subArg)); }); return appArgs; } + function splitAndProcessBoxReferences(boxRefs) { + const splitRefs = boxRefs.split(','); + const boxRefArray = []; + let appIndex = 0; + + for (let i = 0; i < splitRefs.length; i++) { + if (i % 2 === 0) { + appIndex = parseInt(splitRefs[i]); + } else { + const refArg = splitRefs[i].split(':'); + boxRefArray.push({ + appIndex, + name: processAppArgs(refArg), + }); + } + } + return boxRefArray; + } + When( - 'I build an application transaction with operation {string}, application-id {int}, sender {string}, approval-program {string}, clear-program {string}, global-bytes {int}, global-ints {int}, local-bytes {int}, local-ints {int}, app-args {string}, foreign-apps {string}, foreign-assets {string}, app-accounts {string}, fee {int}, first-valid {int}, last-valid {int}, genesis-hash {string}, extra-pages {int}', + 'I build an application transaction with operation {string}, application-id {int}, sender {string}, approval-program {string}, clear-program {string}, global-bytes {int}, global-ints {int}, local-bytes {int}, local-ints {int}, app-args {string}, foreign-apps {string}, foreign-assets {string}, app-accounts {string}, fee {int}, first-valid {int}, last-valid {int}, genesis-hash {string}, extra-pages {int}, boxes {string}', async function ( operationString, appIndex, @@ -4041,7 +4060,8 @@ module.exports = function getSteps(options) { firstValid, lastValid, genesisHashBase64, - extraPages + extraPages, + boxesCommaSeparatedString ) { // operation string to enum const operation = operationStringToEnum(operationString); @@ -4091,6 +4111,11 @@ module.exports = function getSteps(options) { if (appAccountsCommaSeparatedString !== '') { appAccounts = appAccountsCommaSeparatedString.split(','); } + // split and process box references + let boxes; + if (boxesCommaSeparatedString !== '') { + boxes = splitAndProcessBoxReferences(boxesCommaSeparatedString); + } // build suggested params object const sp = { genesisHash: genesisHashBase64, @@ -4109,7 +4134,8 @@ module.exports = function getSteps(options) { appArgs, appAccounts, foreignApps, - foreignAssets + foreignAssets, + boxes ); return; case 'create': @@ -4127,6 +4153,7 @@ module.exports = function getSteps(options) { appAccounts, foreignApps, foreignAssets, + boxes, undefined, undefined, undefined, @@ -4143,7 +4170,8 @@ module.exports = function getSteps(options) { appArgs, appAccounts, foreignApps, - foreignAssets + foreignAssets, + boxes ); return; case 'optin': @@ -4154,7 +4182,8 @@ module.exports = function getSteps(options) { appArgs, appAccounts, foreignApps, - foreignAssets + foreignAssets, + boxes ); return; case 'delete': @@ -4165,7 +4194,8 @@ module.exports = function getSteps(options) { appArgs, appAccounts, foreignApps, - foreignAssets + foreignAssets, + boxes ); return; case 'clear': @@ -4176,7 +4206,8 @@ module.exports = function getSteps(options) { appArgs, appAccounts, foreignApps, - foreignAssets + foreignAssets, + boxes ); return; case 'closeout': @@ -4187,7 +4218,8 @@ module.exports = function getSteps(options) { appArgs, appAccounts, foreignApps, - foreignAssets + foreignAssets, + boxes ); return; default: @@ -4263,7 +4295,7 @@ module.exports = function getSteps(options) { ); Given( - 'I build an application transaction with the transient account, the current application, suggested params, operation {string}, approval-program {string}, clear-program {string}, global-bytes {int}, global-ints {int}, local-bytes {int}, local-ints {int}, app-args {string}, foreign-apps {string}, foreign-assets {string}, app-accounts {string}, extra-pages {int}', + 'I build an application transaction with the transient account, the current application, suggested params, operation {string}, approval-program {string}, clear-program {string}, global-bytes {int}, global-ints {int}, local-bytes {int}, local-ints {int}, app-args {string}, foreign-apps {string}, foreign-assets {string}, app-accounts {string}, extra-pages {int}, boxes {string}', async function ( operationString, approvalProgramFile, @@ -4276,7 +4308,8 @@ module.exports = function getSteps(options) { foreignAppsCommaSeparatedString, foreignAssetsCommaSeparatedString, appAccountsCommaSeparatedString, - extraPages + extraPages, + boxesCommaSeparatedString ) { if (operationString === 'create') { this.currentApplicationIndex = 0; @@ -4330,6 +4363,11 @@ module.exports = function getSteps(options) { if (appAccountsCommaSeparatedString !== '') { appAccounts = appAccountsCommaSeparatedString.split(','); } + // split and process box references + let boxes; + if (boxesCommaSeparatedString !== '') { + boxes = splitAndProcessBoxReferences(boxesCommaSeparatedString); + } const sp = await this.v2Client.getTransactionParams().do(); if (sp.firstRound === 0) sp.firstRound = 1; const o = { @@ -4348,6 +4386,7 @@ module.exports = function getSteps(options) { appAccounts, appForeignApps: foreignApps, appForeignAssets: foreignAssets, + boxes, extraPages, }; this.txn = new algosdk.Transaction(o); From 077c572b3ceae4889a0ae7e595c29798b2124e9e Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Thu, 7 Jul 2022 16:56:19 -0400 Subject: [PATCH 02/15] GetApplicationBoxByName API (#586) * Initial work to define box types in appl calls * More changes to boxes field * Change boxes to array types, fix transactions, and tests * Write box translation tests * Add cucumber test steps * Revert pretty print * Fix decoding for txn boxes * Revise tests to make box args similar to app args * Clean up some code and add a blank test case * Fix err case * Fix box ref catch * Delete box name field for length 0 array * Add box by name endpoint on algod * Add encoding tests for uri * Move scope for cucumber test helper functions * Revise guard * Address some PR feedback * Fix box translation condition from index to id * Alias own reference condition * Fix unit tests and add more comments * Add guard and change reference condition for better readability * Refactor box reference condition * Refactor URI encoding * Add box integration test * Change box name encoding scheme as query param in API * Fix tests by changing how names are ingested * Delete unused encoding URI functions * Change app funding step from then to given * Revise link in Box API comments and add type hint to json request --- Makefile | 4 +- src/client/v2/algod/algod.ts | 19 +++ .../v2/algod/getApplicationBoxByName.ts | 24 ++++ src/client/v2/algod/models/types.ts | 37 +++++ tests/cucumber/docker/run_docker.sh | 2 +- tests/cucumber/steps/steps.js | 133 +++++++++++------- 6 files changed, 169 insertions(+), 50 deletions(-) create mode 100644 src/client/v2/algod/getApplicationBoxByName.ts diff --git a/Makefile b/Makefile index bf31b7975..d047241dc 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ 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.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" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js + 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" 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 or @send.keyregtxn or @dryrun or @compile or @applications or @indexer.applications or @applications.verified or @indexer.231 or @abi or @c2c" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js + node_modules/.bin/cucumber-js --tags "@algod or @assets or @auction or @kmd or @send or @indexer or @rekey 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" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js docker-test: ./tests/cucumber/docker/run_docker.sh diff --git a/src/client/v2/algod/algod.ts b/src/client/v2/algod/algod.ts index c9d4b2b64..f947ea86b 100644 --- a/src/client/v2/algod/algod.ts +++ b/src/client/v2/algod/algod.ts @@ -8,6 +8,7 @@ import Compile from './compile'; import Dryrun from './dryrun'; import GetAssetByID from './getAssetByID'; import GetApplicationByID from './getApplicationByID'; +import GetApplicationBoxByName from './getApplicationBoxByName'; import HealthCheck from './healthCheck'; import PendingTransactionInformation from './pendingTransactionInformation'; import PendingTransactions from './pendingTransactions'; @@ -429,6 +430,24 @@ export default class AlgodClient extends ServiceClient { return new GetApplicationByID(this.c, this.intDecoding, index); } + /** + * Given an application ID and the box name (key), return the value stored in the box. + * + * #### Example + * ```typescript + * const index = 60553466; + * const boxName = Buffer.from("foo"); + * const app = await algodClient.getApplicationBoxByName(index).name(boxName).do(); + * ``` + * + * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idbox) + * @param index - The application ID to look up. + * @category GET + */ + getApplicationBoxByName(index: number) { + return new GetApplicationBoxByName(this.c, this.intDecoding, index); + } + /** * Returns the entire genesis file. * diff --git a/src/client/v2/algod/getApplicationBoxByName.ts b/src/client/v2/algod/getApplicationBoxByName.ts new file mode 100644 index 000000000..63360e2a4 --- /dev/null +++ b/src/client/v2/algod/getApplicationBoxByName.ts @@ -0,0 +1,24 @@ +import JSONRequest from '../jsonrequest'; +import HTTPClient from '../../client'; +import IntDecoding from '../../../types/intDecoding'; +import { BoxReference } from '../../../types'; + +export default class GetApplicationBoxByName extends JSONRequest { + constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { + super(c, intDecoding); + this.index = index; + } + + path() { + return `/v2/applications/${this.index}/box`; + } + + // name sets the box name in base64 encoded format. + name(name: Uint8Array) { + // Encode query in base64 format and append the encoding prefix. + let encodedName = Buffer.from(name).toString('base64'); + encodedName = `b64:${encodedName}`; + this.query.name = encodeURI(encodedName); + return this; + } +} diff --git a/src/client/v2/algod/models/types.ts b/src/client/v2/algod/models/types.ts index d91d84cc0..dbf5aeb54 100644 --- a/src/client/v2/algod/models/types.ts +++ b/src/client/v2/algod/models/types.ts @@ -1029,6 +1029,43 @@ export class BlockResponse extends BaseModel { } } +/** + * Box name and its content. + */ +export class Box extends BaseModel { + /** + * (name) box name, base64 encoded + */ + public name: Uint8Array; + + /** + * (value) box value, base64 encoded. + */ + public value: Uint8Array; + + /** + * Creates a new `Box` object. + * @param name - (name) box name, base64 encoded + * @param value - (value) box value, base64 encoded. + */ + constructor(name: string | Uint8Array, value: string | Uint8Array) { + super(); + this.name = + typeof name === 'string' + ? new Uint8Array(Buffer.from(name, 'base64')) + : name; + this.value = + typeof value === 'string' + ? new Uint8Array(Buffer.from(value, 'base64')) + : value; + + this.attribute_map = { + name: 'name', + value: 'value', + }; + } +} + export class BuildVersion extends BaseModel { public branch: string; diff --git a/tests/cucumber/docker/run_docker.sh b/tests/cucumber/docker/run_docker.sh index 5db4bb447..84f7bf0cf 100755 --- a/tests/cucumber/docker/run_docker.sh +++ b/tests/cucumber/docker/run_docker.sh @@ -7,7 +7,7 @@ rm -rf test-harness rm -rf tests/cucumber/features # clone test harness -git clone --single-branch --branch box-reference https://github.com/algorand/algorand-sdk-testing.git 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 diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index c9e32e845..44e808805 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -127,6 +127,54 @@ module.exports = function getSteps(options) { const { algod_token: algodToken, kmd_token: kmdToken } = options; + // String parsing helper methods + function processAppArgs(subArg) { + switch (subArg[0]) { + case 'str': + return makeUint8Array(Buffer.from(subArg[1])); + case 'int': + return makeUint8Array([parseInt(subArg[1])]); + case 'addr': + return algosdk.decodeAddress(subArg[1]).publicKey; + case 'b64': + return makeUint8Array(Buffer.from(subArg[1], 'base64')); + default: + throw Error(`did not recognize app arg of type${subArg[0]}`); + } + } + + function splitAndProcessAppArgs(inArgs) { + const splitArgs = inArgs.split(','); + const subArgs = []; + splitArgs.forEach((subArg) => { + subArgs.push(subArg.split(':')); + }); + const appArgs = []; + subArgs.forEach((subArg) => { + appArgs.push(processAppArgs(subArg)); + }); + return appArgs; + } + + function splitAndProcessBoxReferences(boxRefs) { + const splitRefs = boxRefs.split(','); + const boxRefArray = []; + let appIndex = 0; + + for (let i = 0; i < splitRefs.length; i++) { + if (i % 2 === 0) { + appIndex = parseInt(splitRefs[i]); + } else { + const refArg = splitRefs[i].split(':'); + boxRefArray.push({ + appIndex, + name: processAppArgs(refArg), + }); + } + } + return boxRefArray; + } + Given('an algod client', async function () { this.acl = new algosdk.Algod(algodToken, 'http://localhost', 60000); return this.acl; @@ -1787,6 +1835,14 @@ module.exports = function getSteps(options) { } ); + When( + 'we make a GetApplicationBoxByName call for applicationID {int} with encoded box name {string}', + async function (index, boxName) { + const box = splitAndProcessAppArgs(boxName)[0]; + await this.v2Client.getApplicationBoxByName(index).name(box).do(); + } + ); + let anyPendingTransactionInfoResponse; When('we make any Pending Transaction Information call', async function () { @@ -3993,53 +4049,6 @@ module.exports = function getSteps(options) { return makeUint8Array(data); } - function processAppArgs(subArg) { - switch (subArg[0]) { - case 'str': - return makeUint8Array(Buffer.from(subArg[1])); - case 'int': - return makeUint8Array([parseInt(subArg[1])]); - case 'addr': - return algosdk.decodeAddress(subArg[1]).publicKey; - case 'b64': - return makeUint8Array(Buffer.from(subArg[1], 'base64')); - default: - throw Error(`did not recognize app arg of type${subArg[0]}`); - } - } - - function splitAndProcessAppArgs(inArgs) { - const splitArgs = inArgs.split(','); - const subArgs = []; - splitArgs.forEach((subArg) => { - subArgs.push(subArg.split(':')); - }); - const appArgs = []; - subArgs.forEach((subArg) => { - appArgs.push(processAppArgs(subArg)); - }); - return appArgs; - } - - function splitAndProcessBoxReferences(boxRefs) { - const splitRefs = boxRefs.split(','); - const boxRefArray = []; - let appIndex = 0; - - for (let i = 0; i < splitRefs.length; i++) { - if (i % 2 === 0) { - appIndex = parseInt(splitRefs[i]); - } else { - const refArg = splitRefs[i].split(':'); - boxRefArray.push({ - appIndex, - name: processAppArgs(refArg), - }); - } - } - return boxRefArray; - } - When( 'I build an application transaction with operation {string}, application-id {int}, sender {string}, approval-program {string}, clear-program {string}, global-bytes {int}, global-ints {int}, local-bytes {int}, local-ints {int}, app-args {string}, foreign-apps {string}, foreign-assets {string}, app-accounts {string}, fee {int}, first-valid {int}, last-valid {int}, genesis-hash {string}, extra-pages {int}, boxes {string}', async function ( @@ -5380,6 +5389,36 @@ module.exports = function getSteps(options) { } ); + Then( + 'the contents of the box with name {string} should be {string}. If there is an error it is {string}.', + async function (boxName, boxValue, errString) { + try { + const boxKey = splitAndProcessAppArgs(boxName)[0]; + const resp = await this.v2Client + .getApplicationBoxByName(this.currentApplicationIndex) + .name(boxKey) + .do(); + const actualName = resp.name; + const actualValue = resp.value; + assert.deepStrictEqual( + Buffer.from(boxKey), + Buffer.from(actualName, 'base64') + ); + assert.deepStrictEqual(boxValue, actualValue); + } catch (err) { + if (errString !== '') { + assert.deepStrictEqual( + true, + err.message.includes(errString), + `expected ${errString} got ${err.message}` + ); + } else { + throw err; + } + } + } + ); + if (!options.ignoreReturn) { return steps; } From 293b4d2ca8e72d62536eb5996ccdc03e9b663b12 Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Fri, 15 Jul 2022 13:51:35 -0400 Subject: [PATCH 03/15] Box Storage: Add GetApplicationBoxes API and cucumber steps (#608) * Regnerate API models * Add list boxes API, cucumber tests * Refactor steps to use map * Refactor steps * Check list lengths for any duplicates * Revert to feature branch * Fix branch name --- src/client/v2/algod/algod.ts | 18 +++++++++ src/client/v2/algod/getApplicationBoxes.ts | 22 +++++++++++ src/client/v2/algod/models/types.ts | 46 ++++++++++++++++++++++ tests/cucumber/steps/steps.js | 32 ++++++++++++++- 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/client/v2/algod/getApplicationBoxes.ts diff --git a/src/client/v2/algod/algod.ts b/src/client/v2/algod/algod.ts index f947ea86b..dbe15ca6c 100644 --- a/src/client/v2/algod/algod.ts +++ b/src/client/v2/algod/algod.ts @@ -9,6 +9,7 @@ import Dryrun from './dryrun'; import GetAssetByID from './getAssetByID'; import GetApplicationByID from './getApplicationByID'; import GetApplicationBoxByName from './getApplicationBoxByName'; +import GetApplicationBoxes from './getApplicationBoxes'; import HealthCheck from './healthCheck'; import PendingTransactionInformation from './pendingTransactionInformation'; import PendingTransactions from './pendingTransactions'; @@ -448,6 +449,23 @@ export default class AlgodClient extends ServiceClient { return new GetApplicationBoxByName(this.c, this.intDecoding, index); } + /** + * Given an application ID, return all the box names associated with the app. + * + * #### Example + * ```typescript + * const index = 60553466; + * const app = await algodClient.getApplicationBoxes(index).max(3).do(); + * ``` + * + * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idboxes) + * @param index - The application ID to look up. + * @category GET + */ + getApplicationBoxes(index: number) { + return new GetApplicationBoxes(this.c, this.intDecoding, index); + } + /** * Returns the entire genesis file. * diff --git a/src/client/v2/algod/getApplicationBoxes.ts b/src/client/v2/algod/getApplicationBoxes.ts new file mode 100644 index 000000000..9a48c2948 --- /dev/null +++ b/src/client/v2/algod/getApplicationBoxes.ts @@ -0,0 +1,22 @@ +import JSONRequest from '../jsonrequest'; +import HTTPClient from '../../client'; +import IntDecoding from '../../../types/intDecoding'; +import { BoxesResponse } from './models/types'; + +export default class GetApplicationBoxes extends JSONRequest { + constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { + super(c, intDecoding); + this.index = index; + this.query.max = 0; + } + + path() { + return `/v2/applications/${this.index}/boxes`; + } + + // max sets the maximum number of results to be returned from this query. + max(max: number) { + this.query.max = max; + return this; + } +} diff --git a/src/client/v2/algod/models/types.ts b/src/client/v2/algod/models/types.ts index dbf5aeb54..bdeeaa1e1 100644 --- a/src/client/v2/algod/models/types.ts +++ b/src/client/v2/algod/models/types.ts @@ -1066,6 +1066,52 @@ export class Box extends BaseModel { } } +/** + * Box descriptor describes a Box. + */ +export class BoxDescriptor extends BaseModel { + /** + * Base64 encoded box name + */ + public name: Uint8Array; + + /** + * Creates a new `BoxDescriptor` object. + * @param name - Base64 encoded box name + */ + constructor(name: string | Uint8Array) { + super(); + this.name = + typeof name === 'string' + ? new Uint8Array(Buffer.from(name, 'base64')) + : name; + + this.attribute_map = { + name: 'name', + }; + } +} + +/** + * Box names of an application + */ +export class BoxesResponse extends BaseModel { + public boxes: BoxDescriptor[]; + + /** + * Creates a new `BoxesResponse` object. + * @param boxes - + */ + constructor(boxes: BoxDescriptor[]) { + super(); + this.boxes = boxes; + + this.attribute_map = { + boxes: 'boxes', + }; + } +} + export class BuildVersion extends BaseModel { public branch: string; diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 44e808805..96085e5bc 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -144,6 +144,9 @@ module.exports = function getSteps(options) { } function splitAndProcessAppArgs(inArgs) { + if (inArgs == null || inArgs === '') { + return []; + } const splitArgs = inArgs.split(','); const subArgs = []; splitArgs.forEach((subArg) => { @@ -157,6 +160,9 @@ module.exports = function getSteps(options) { } function splitAndProcessBoxReferences(boxRefs) { + if (boxRefs == null || boxRefs === '') { + return []; + } const splitRefs = boxRefs.split(','); const boxRefArray = []; let appIndex = 0; @@ -1843,6 +1849,13 @@ module.exports = function getSteps(options) { } ); + When( + 'we make a GetApplicationBoxes call for applicationID {int} with max {int}', + async function (index, limit) { + await this.v2Client.getApplicationBoxes(index).max(limit).do(); + } + ); + let anyPendingTransactionInfoResponse; When('we make any Pending Transaction Information call', async function () { @@ -5390,7 +5403,7 @@ module.exports = function getSteps(options) { ); Then( - 'the contents of the box with name {string} should be {string}. If there is an error it is {string}.', + 'the contents of the box with name {string} in the current application should be {string}. If there is an error it is {string}.', async function (boxName, boxValue, errString) { try { const boxKey = splitAndProcessAppArgs(boxName)[0]; @@ -5419,6 +5432,23 @@ module.exports = function getSteps(options) { } ); + Then( + 'the current application should have the following boxes {string}.', + async function (boxNames) { + const boxes = splitAndProcessAppArgs(boxNames); + + const resp = await this.v2Client + .getApplicationBoxes(this.currentApplicationIndex) + .do(); + assert.deepStrictEqual(boxes.length, resp.boxes.length); + const actualBoxes = new Set( + resp.boxes.map((b) => Buffer.from(b.name, 'base64')) + ); + const expectedBoxes = new Set(boxes.map(Buffer.from)); + assert.deepStrictEqual(expectedBoxes, actualBoxes); + } + ); + if (!options.ignoreReturn) { return steps; } From 578ddf0639c780e68b0fb46967a41b53e0af8ecb Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Sat, 3 Sep 2022 01:10:24 -0400 Subject: [PATCH 04/15] Enhancement: Merge `develop` to `feature/box-storage` (#640) * bump version and add to changelog * update README.md for new version * prettier on CHANGELOG * Update README.md Co-authored-by: John Lee * Update README.md Co-authored-by: John Lee * Enhancement: Use sandbox for SDK Testing and remove Indexer v1 steps (#623) * Add files to enable sandbox sdk testing * Add some todo comments and formatting * Update changes to script and env * Delete indexer v1 test steps * More changes to cucumber tests * Change features directory in JS SDK * Remove more indexer integration tests * Update test env * Update test-harness.sh Co-authored-by: Zeph Grunschlag * Break out cucumber tags into their own files (#625) * Command to generate input for Unused Steps Analysis Script + Remove those steps (#627) * command to generate input for Unused Steps Analysis Script + Remove those steps * remvoe the steps * In response to CR remark. Update Makefile * Update .test-env Co-authored-by: Zeph Grunschlag Co-authored-by: Zeph Grunschlag * Bugfix: Pass verbosity to the harness and sandbox (#630) * Ignore algorand-sdk-testing test-harness dir (#634) * Enhancement: Deprecating use of langspec (#632) * enhancement: Initial stateproofs support (#629) * Initial stateproofs support * Some more unused steps (#637) * dummy push * revert change of dummy push, ci runs now Co-authored-by: Barbara Poon Co-authored-by: algobarb <78746954+algobarb@users.noreply.github.com> Co-authored-by: John Lee Co-authored-by: algochoi <86622919+algochoi@users.noreply.github.com> Co-authored-by: Zeph Grunschlag Co-authored-by: Michael Diamant Co-authored-by: Eric Warehime --- .gitignore | 1 + .test-env | 14 + CHANGELOG.md | 8 + Makefile | 25 +- README.md | 8 +- package-lock.json | 4 +- package.json | 2 +- src/client/v2/algod/algod.ts | 44 +- src/client/v2/algod/getTransactionProof.ts | 43 + src/client/v2/algod/lightBlockHeaderProof.ts | 15 + src/client/v2/algod/models/types.ts | 259 +++- src/client/v2/algod/proof.ts | 21 - src/client/v2/algod/stateproof.ts | 15 + .../v2/indexer/lookupAccountTransactions.ts | 2 +- .../v2/indexer/searchForTransactions.ts | 2 +- src/logic/logic.ts | 28 + src/logicsig.ts | 40 +- src/transaction.ts | 73 + src/types/blockHeader.ts | 12 +- src/types/transactions/base.ts | 22 +- src/types/transactions/encoded.ts | 15 + src/types/transactions/index.ts | 5 +- src/types/transactions/stateproof.ts | 17 + test-harness.sh | 69 + tests/5.Transaction.js | 34 + tests/7.AlgoSDK.js | 5 - tests/8.LogicSig.ts | 10 - tests/cucumber/docker/run_docker.sh | 30 - tests/cucumber/integration.tags | 14 + tests/cucumber/steps/index.js | 42 +- tests/cucumber/steps/steps.js | 1201 ++--------------- tests/cucumber/unit.tags | 26 + 32 files changed, 841 insertions(+), 1265 deletions(-) create mode 100644 .test-env create mode 100644 src/client/v2/algod/getTransactionProof.ts create mode 100644 src/client/v2/algod/lightBlockHeaderProof.ts delete mode 100644 src/client/v2/algod/proof.ts create mode 100644 src/client/v2/algod/stateproof.ts create mode 100644 src/types/transactions/stateproof.ts create mode 100755 test-harness.sh delete mode 100755 tests/cucumber/docker/run_docker.sh create mode 100644 tests/cucumber/integration.tags create mode 100644 tests/cucumber/unit.tags 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 From af8078430f6ee3cb2263d9cdceb9e2a9e61758ef Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Tue, 6 Sep 2022 15:24:13 -0400 Subject: [PATCH 05/15] Box support for Indexer endpoints (#619) * Add boxes endpoint with path tests * Add lookup box by name api for indexer * Merge develop branch into feature/box-storage (#620) * properly set trace maxWidth (#593) * fix: safe intDecoding (#599) * fix: safe intDecoding * test: parse number in edge case * refactor: remove optional chaining for node12 * Remove code that relies on node's path module (#598) * Remove code that relies on node's path module * Replace url-parse with built in WHATWG URL API * Removed path-browserify fallback from webpack config * Removed path-browserify and url-parse from npm dependencies * Removed references to `path-browserify` in FAQ.md * bump version * Github-Actions: Adding PR title and label checks (#600) * Remove unused/unmaintained templates (#607) * Dev Tools: Source map decoder (#590) * adding source map decoder * Enhancement: Upgrade typedoc and plugins (#605) * Update ts-node, typescript, and typedoc to latest * docs: tealSign (#610) * bump version and add to changelog * update README.md for new version * API: Support attaching signatures to standard and multisig transactions (#595) * Add attach signature method to transaction class * Add multisig external signature methods * Fix failing multisig test * Add signature length checks * Add method to create an unsigned multisig transaction blob * Rename multisig create methods and use unencoded transaction * Refactor `createMultisigTransactionWithSignature` to use `createMultisigTransaction` method * Fix algosdk createMultisigTransaction export * Use MultisigMetadata without pks in new create method * These types should be consolidated in the future, and addrs seems like a better convention to use long-term * More descriptive test suite name * AVM: Consolidate TEAL and AVM versions (#609) * Testing: Use Dev mode network for cucumber tests (#614) * Send zero txn to itself * Refactor block advance functions * Revise steps to allow for rekeying transient accounts * Try to reduce flaky tests * Move constant into step * Add artificial sleep instead of sending blank txns when mimicking wait for block API * Reduce flaky tests * Remove unnecessary use of v2 algod client (#616) * Remove unnecessary use of v2 algod client * Add missing await for async function calls (#617) * Rename rekey tag in makefile * Revert testing branch back to master Co-authored-by: Michael Diamant * Revert package lock from develop Co-authored-by: Joe Polny <50534337+joe-p@users.noreply.github.com> Co-authored-by: AlgoDoggo <93348148+AlgoDoggo@users.noreply.github.com> Co-authored-by: Bryan Dela Cruz Co-authored-by: Lucky Baar Co-authored-by: Jack <87339414+algojack@users.noreply.github.com> Co-authored-by: Eric Warehime Co-authored-by: Ben Guidarelli Co-authored-by: Fionna Chan Co-authored-by: Jack Smith Co-authored-by: Jacob Daitzman Co-authored-by: Michael Diamant * Revert package lock * Fix comments in box related APIs * Add next token to indexer box API test * rm unused step * Update src/client/v2/algod/getApplicationBoxByName.ts * Update .test-env Co-authored-by: Joe Polny <50534337+joe-p@users.noreply.github.com> Co-authored-by: AlgoDoggo <93348148+AlgoDoggo@users.noreply.github.com> Co-authored-by: Bryan Dela Cruz Co-authored-by: Lucky Baar Co-authored-by: Jack <87339414+algojack@users.noreply.github.com> Co-authored-by: Eric Warehime Co-authored-by: Ben Guidarelli Co-authored-by: Fionna Chan Co-authored-by: Jack Smith Co-authored-by: Jacob Daitzman Co-authored-by: Michael Diamant Co-authored-by: Hang Su Co-authored-by: Hang Su <87964331+ahangsu@users.noreply.github.com> --- src/client/v2/algod/algod.ts | 4 +- .../v2/algod/getApplicationBoxByName.ts | 33 +++++++- src/client/v2/algod/getApplicationBoxes.ts | 32 +++++++- src/client/v2/indexer/indexer.ts | 34 +++++++++ .../lookupApplicationBoxByIDandName.ts | 52 +++++++++++++ .../v2/indexer/searchForApplicationBoxes.ts | 75 +++++++++++++++++++ tests/cucumber/steps/steps.js | 22 ++++++ tests/cucumber/unit.tags | 1 + 8 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 src/client/v2/indexer/lookupApplicationBoxByIDandName.ts create mode 100644 src/client/v2/indexer/searchForApplicationBoxes.ts diff --git a/src/client/v2/algod/algod.ts b/src/client/v2/algod/algod.ts index df0b86a19..a044388d0 100644 --- a/src/client/v2/algod/algod.ts +++ b/src/client/v2/algod/algod.ts @@ -440,7 +440,7 @@ export default class AlgodClient extends ServiceClient { * ```typescript * const index = 60553466; * const boxName = Buffer.from("foo"); - * const app = await algodClient.getApplicationBoxByName(index).name(boxName).do(); + * const boxValue = await algodClient.getApplicationBoxByName(index).name(boxName).do(); * ``` * * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idbox) @@ -457,7 +457,7 @@ export default class AlgodClient extends ServiceClient { * #### Example * ```typescript * const index = 60553466; - * const app = await algodClient.getApplicationBoxes(index).max(3).do(); + * const boxesResult = await algodClient.getApplicationBoxes(index).max(3).do(); * ``` * * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idboxes) diff --git a/src/client/v2/algod/getApplicationBoxByName.ts b/src/client/v2/algod/getApplicationBoxByName.ts index 63360e2a4..685698f7f 100644 --- a/src/client/v2/algod/getApplicationBoxByName.ts +++ b/src/client/v2/algod/getApplicationBoxByName.ts @@ -3,17 +3,48 @@ import HTTPClient from '../../client'; import IntDecoding from '../../../types/intDecoding'; import { BoxReference } from '../../../types'; +/** + * Given an application ID and the box name (key), return the value stored in the box. + * + * #### Example + * ```typescript + * const index = 1234; + * const boxName = Buffer.from("foo"); + * const boxValue = await algodClient.getApplicationBoxByName(index).name(boxName).do(); + * ``` + * + * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idbox) + * @param index - The application ID to look up. + * @category GET + */ export default class GetApplicationBoxByName extends JSONRequest { constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { super(c, intDecoding); this.index = index; } + /** + * @returns `/v2/applications/${index}/box` + */ path() { return `/v2/applications/${this.index}/box`; } - // name sets the box name in base64 encoded format. + /** + * Box name in bytes, and encodes it into a b64 string with goal encoded prefix. + * + * #### Example + * ```typescript + * const boxName = Buffer.from("foo"); + * const boxValue = await algodClient + * .getApplicationBoxByName(1234) + * .name(boxName) + * .do(); + * ``` + * + * @param name - name of box in bytes. + * @category query + */ name(name: Uint8Array) { // Encode query in base64 format and append the encoding prefix. let encodedName = Buffer.from(name).toString('base64'); diff --git a/src/client/v2/algod/getApplicationBoxes.ts b/src/client/v2/algod/getApplicationBoxes.ts index 9a48c2948..626dc4e85 100644 --- a/src/client/v2/algod/getApplicationBoxes.ts +++ b/src/client/v2/algod/getApplicationBoxes.ts @@ -3,6 +3,19 @@ import HTTPClient from '../../client'; import IntDecoding from '../../../types/intDecoding'; import { BoxesResponse } from './models/types'; +/** + * Given an application ID, return all the box names associated with the app. + * + * #### Example + * ```typescript + * const index = 1234; + * const boxesResult = await algodClient.getApplicationBoxes(index).max(3).do(); + * ``` + * + * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idboxes) + * @param index - The application ID to look up. + * @category GET + */ export default class GetApplicationBoxes extends JSONRequest { constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { super(c, intDecoding); @@ -10,11 +23,28 @@ export default class GetApplicationBoxes extends JSONRequest { this.query.max = 0; } + /** + * @returns `/v2/applications/${index}/boxes` + */ path() { return `/v2/applications/${this.index}/boxes`; } - // max sets the maximum number of results to be returned from this query. + /** + * Limit results for pagination. + * + * #### Example + * ```typescript + * const maxResults = 20; + * const boxesResult = await algodClient + * .GetApplicationBoxes(1234) + * .limit(maxResults) + * .do(); + * ``` + * + * @param limit - maximum number of results to return. + * @category query + */ max(max: number) { this.query.max = max; return this; diff --git a/src/client/v2/indexer/indexer.ts b/src/client/v2/indexer/indexer.ts index ed90f7474..af6c3360a 100644 --- a/src/client/v2/indexer/indexer.ts +++ b/src/client/v2/indexer/indexer.ts @@ -13,10 +13,12 @@ import LookupAccountCreatedApplications from './lookupAccountCreatedApplications import LookupAssetByID from './lookupAssetByID'; import LookupApplications from './lookupApplications'; import LookupApplicationLogs from './lookupApplicationLogs'; +import LookupApplicationBoxByIDandName from './lookupApplicationBoxByIDandName'; import SearchAccounts from './searchAccounts'; import SearchForTransactions from './searchForTransactions'; import SearchForAssets from './searchForAssets'; import SearchForApplications from './searchForApplications'; +import SearchForApplicationBoxes from './searchForApplicationBoxes'; import { BaseHTTPClient } from '../../baseHTTPClient'; import { CustomTokenHeader, @@ -372,4 +374,36 @@ export default class IndexerClient extends ServiceClient { searchForApplications() { return new SearchForApplications(this.c, this.intDecoding); } + + /** + * Returns information about indexed application boxes. + * + * #### Example + * ```typescript + * const boxesResult = await indexerClient.searchForApplicationBoxes().do(); + * ``` + * + * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applicationsapplication-idboxes) + * @param appID - The ID of the application with boxes. + * @category GET + */ + searchForApplicationBoxes(appID: number) { + return new SearchForApplicationBoxes(this.c, this.intDecoding, appID); + } + + /** + * Returns information about the application box given its name. + * + * #### Example + * ```typescript + * const boxValue = await indexerClient.lookupApplicationBoxByIDandName().do(); + * ``` + * + * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applicationsapplication-idbox) + * @param appID - The ID of the application with boxes. + * @category GET + */ + lookupApplicationBoxByIDandName(appID: number) { + return new LookupApplicationBoxByIDandName(this.c, this.intDecoding, appID); + } } diff --git a/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts b/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts new file mode 100644 index 000000000..9dc3fc2c4 --- /dev/null +++ b/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts @@ -0,0 +1,52 @@ +import JSONRequest from '../jsonrequest'; +import HTTPClient from '../../client'; +import IntDecoding from '../../../types/intDecoding'; + +export default class LookupApplicationBoxByIDandName extends JSONRequest { + /** + * Returns information about indexed application boxes. + * + * #### Example + * ```typescript + * const boxValue = await indexerClient.LookupApplicationBoxByIDandName(1234).do(); + * ``` + * + * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applicationsapplication-idbox) + * @oaram index - application index. + * @category GET + */ + constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { + super(c, intDecoding); + this.index = index; + } + + /** + * @returns `/v2/applications/${index}/box` + */ + path() { + return `/v2/applications/${this.index}/box`; + } + + /** + * Box name in bytes, and encodes it into a b64 string with goal encoded prefix. + * + * #### Example + * ```typescript + * const boxName = Buffer.from("foo"); + * const boxValue = await indexerClient + * .LookupApplicationBoxByIDandName(1234) + * .name(boxName) + * .do(); + * ``` + * + * @param name - name of box in bytes. + * @category query + */ + name(name: Uint8Array) { + // Encode query in base64 format and append the encoding prefix. + let encodedName = Buffer.from(name).toString('base64'); + encodedName = `b64:${encodedName}`; + this.query.name = encodeURI(encodedName); + return this; + } +} diff --git a/src/client/v2/indexer/searchForApplicationBoxes.ts b/src/client/v2/indexer/searchForApplicationBoxes.ts new file mode 100644 index 000000000..08da7c510 --- /dev/null +++ b/src/client/v2/indexer/searchForApplicationBoxes.ts @@ -0,0 +1,75 @@ +import JSONRequest from '../jsonrequest'; +import HTTPClient from '../../client'; +import IntDecoding from '../../../types/intDecoding'; + +export default class SearchForApplicationBoxes extends JSONRequest { + /** + * Returns information about indexed application boxes. + * + * #### Example + * ```typescript + * const boxesResult = await indexerClient.SearchForApplicationBoxes(1234).do(); + * ``` + * + * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applicationsapplication-idboxes) + * @oaram index - application index. + * @category GET + */ + constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { + super(c, intDecoding); + this.index = index; + } + + /** + * @returns `/v2/applications/${index}/boxes` + */ + path() { + return `/v2/applications/${this.index}/boxes`; + } + + /** + * Specify the next page of results. + * + * #### Example + * ```typescript + * const maxResults = 20; + * + * const boxesPage1 = await indexerClient + * .SearchForApplicationBoxes(1234) + * .limit(maxResults) + * .do(); + * + * const boxesPage2 = await indexerClient + * .SearchForApplicationBoxes(1234) + * .limit(maxResults) + * .nextToken(boxesPage1["next-token"]) + * .do(); + * ``` + * @param nextToken - provided by the previous results. + * @category query + */ + nextToken(next: string) { + this.query.next = next; + return this; + } + + /** + * Limit results for pagination. + * + * #### Example + * ```typescript + * const maxResults = 20; + * const boxesResult = await indexerClient + * .SearchForApplicationBoxes(1234) + * .limit(maxResults) + * .do(); + * ``` + * + * @param limit - maximum number of results to return. + * @category query + */ + limit(limit: number) { + this.query.limit = limit; + return this; + } +} diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 7a3e13688..899a0f933 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -2242,6 +2242,28 @@ module.exports = function getSteps(options) { } ); + When( + 'we make a SearchForApplicationBoxes call with applicationID {int} with max {int} nextToken {string}', + async function (index, limit, token) { + await this.indexerClient + .searchForApplicationBoxes(index) + .limit(limit) + .nextToken(token) + .do(); + } + ); + + When( + 'we make a LookupApplicationBoxByIDandName call with applicationID {int} with encoded box name {string}', + async function (index, name) { + const boxKey = splitAndProcessAppArgs(name)[0]; + await this.indexerClient + .lookupApplicationBoxByIDandName(index) + .name(boxKey) + .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) { diff --git a/tests/cucumber/unit.tags b/tests/cucumber/unit.tags index 2d3f46b8e..0ff7ea3f5 100644 --- a/tests/cucumber/unit.tags +++ b/tests/cucumber/unit.tags @@ -3,6 +3,7 @@ @unit.algod @unit.algod.ledger_refactoring @unit.applications +@unit.applications.boxes @unit.atomic_transaction_composer @unit.dryrun @unit.dryrun.trace.application From 6e3b9587641491a40d4d4a058ba677f7c6834858 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 7 Sep 2022 11:52:58 -0400 Subject: [PATCH 06/15] boxes integration algod test --- tests/cucumber/integration.tags | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/cucumber/integration.tags b/tests/cucumber/integration.tags index 03cb481b6..52ba486e9 100644 --- a/tests/cucumber/integration.tags +++ b/tests/cucumber/integration.tags @@ -1,6 +1,7 @@ @abi @algod @applications +@applications.boxes @applications.verified @assets @auction @@ -11,4 +12,4 @@ @kmd @rekey_v1 @send -@send.keyregtxn \ No newline at end of file +@send.keyregtxn From b45e83c32937ffa32b67fdb1bb6babc85470decb Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Thu, 15 Sep 2022 11:12:37 -0400 Subject: [PATCH 07/15] Enhancement: Boxes Indexer Integration test (#641) * add indexer impl in sdk * merge step for indexer * add indexer confirmed steps * add indexer confirmed steps * use sleep step * change to sleep on ms * per new step expectation * unify with applications.boxes * phrasing * Update .test-env * minor --- tests/cucumber/steps/steps.js | 111 ++++++++++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 12 deletions(-) diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 899a0f933..c30f8d755 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -201,6 +201,10 @@ module.exports = function getSteps(options) { this.v2Client = new algosdk.Algodv2(algodToken, 'http://localhost', 60000); }); + Given('an indexer v2 client', function () { + this.indexerV2client = new algosdk.Indexer('', 'http://localhost', 59999); + }); + Given('wallet information', async function () { this.wallet_name = 'unencrypted-default-wallet'; this.wallet_pswd = ''; @@ -4439,14 +4443,26 @@ module.exports = function getSteps(options) { ); Then( - 'the contents of the box with name {string} in the current application should be {string}. If there is an error it is {string}.', - async function (boxName, boxValue, errString) { + 'according to {string}, the contents of the box with name {string} in the current application should be {string}. If there is an error it is {string}.', + async function (fromClient, boxName, boxValue, errString) { try { const boxKey = splitAndProcessAppArgs(boxName)[0]; - const resp = await this.v2Client - .getApplicationBoxByName(this.currentApplicationIndex) - .name(boxKey) - .do(); + + let resp = null; + if (fromClient === 'algod') { + resp = await this.v2Client + .getApplicationBoxByName(this.currentApplicationIndex) + .name(boxKey) + .do(); + } else if (fromClient === 'indexer') { + resp = await this.indexerV2client + .lookupApplicationBoxByIDandName(this.currentApplicationIndex) + .name(boxKey) + .do(); + } else { + assert.fail(`expecting algod or indexer, got ${fromClient}`); + } + const actualName = resp.name; const actualValue = resp.value; assert.deepStrictEqual( @@ -4468,14 +4484,28 @@ module.exports = function getSteps(options) { } ); - Then( - 'the current application should have the following boxes {string}.', - async function (boxNames) { - const boxes = splitAndProcessAppArgs(boxNames); + function splitBoxNames(boxB64Names) { + if (boxB64Names == null || boxB64Names === '') { + return []; + } + const splitBoxB64Names = boxB64Names.split(':'); + const boxNames = []; + splitBoxB64Names.forEach((subArg) => { + boxNames.push(makeUint8Array(Buffer.from(subArg, 'base64'))); + }); + return boxNames; + } - const resp = await this.v2Client - .getApplicationBoxes(this.currentApplicationIndex) + Then( + 'according to indexer, with {int} being the parameter that limits results, and {string} being the parameter that sets the next result, the current application should have the following boxes {string}.', + async function (limit, nextPage, boxNames) { + const boxes = splitBoxNames(boxNames); + const resp = await this.indexerV2client + .searchForApplicationBoxes(this.currentApplicationIndex) + .limit(limit) + .nextToken(nextPage) .do(); + assert.deepStrictEqual(boxes.length, resp.boxes.length); const actualBoxes = new Set( resp.boxes.map((b) => Buffer.from(b.name, 'base64')) @@ -4485,6 +4515,63 @@ module.exports = function getSteps(options) { } ); + Then( + 'according to {string}, with {int} being the parameter that limits results, the current application should have {int} boxes.', + async function (fromClient, limit, expectedBoxNum) { + let resp = null; + if (fromClient === 'algod') { + resp = await this.v2Client + .getApplicationBoxes(this.currentApplicationIndex) + .max(limit) + .do(); + } else if (fromClient === 'indexer') { + resp = await this.indexerV2client + .searchForApplicationBoxes(this.currentApplicationIndex) + .limit(limit) + .do(); + } else { + assert.fail(`expecting algod or indexer, got ${fromClient}`); + } + + assert.deepStrictEqual(expectedBoxNum, resp.boxes.length); + } + ); + + Then( + 'according to {string}, the current application should have the following boxes {string}.', + async function (fromClient, boxNames) { + const boxes = splitBoxNames(boxNames); + + let resp = null; + if (fromClient === 'algod') { + resp = await this.v2Client + .getApplicationBoxes(this.currentApplicationIndex) + .do(); + } else if (fromClient === 'indexer') { + resp = await this.indexerV2client + .searchForApplicationBoxes(this.currentApplicationIndex) + .do(); + } else { + assert.fail(`expecting algod or indexer, got ${fromClient}`); + } + + assert.deepStrictEqual(boxes.length, resp.boxes.length); + const actualBoxes = new Set( + resp.boxes.map((b) => Buffer.from(b.name, 'base64')) + ); + const expectedBoxes = new Set(boxes.map(Buffer.from)); + assert.deepStrictEqual(expectedBoxes, actualBoxes); + } + ); + + Then( + 'I sleep for {int} milliseconds for indexer to digest things down.', + async (milliseconds) => { + const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + await sleep(milliseconds); + } + ); + Given('a source map json file {string}', async function (srcmap) { const js = parseJSON(await loadResource(srcmap)); this.sourcemap = new algosdk.SourceMap(js); From 15e591b577af7a67d0127a1dc46410533cb63627 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 20 Sep 2022 13:43:59 -0400 Subject: [PATCH 08/15] remove unnecessary @applications tag --- tests/cucumber/integration.tags | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/cucumber/integration.tags b/tests/cucumber/integration.tags index 52ba486e9..91dace71b 100644 --- a/tests/cucumber/integration.tags +++ b/tests/cucumber/integration.tags @@ -1,6 +1,5 @@ @abi @algod -@applications @applications.boxes @applications.verified @assets From 82fb7fd5e8dc7a209fb36eeb8907e6f8cc369047 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 20 Sep 2022 15:24:15 -0400 Subject: [PATCH 09/15] revert package-lock.json to develop --- package-lock.json | 666 +++++++++++++++++----------------------------- 1 file changed, 248 insertions(+), 418 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5751c56d3..2d069eee4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "algosdk", - "version": "1.20.0", + "version": "1.21.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "algosdk", - "version": "1.20.0", + "version": "1.21.0", "license": "MIT", "dependencies": { "algo-msgpack-with-bigint": "^2.1.1", @@ -207,20 +207,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", @@ -230,25 +216,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", @@ -332,9 +299,9 @@ } }, "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", + "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==", "dev": true, "engines": { "node": ">=10" @@ -344,9 +311,9 @@ } }, "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", "dev": true, "dependencies": { "defer-to-connect": "^2.0.0" @@ -386,9 +353,9 @@ "dev": true }, "node_modules/@types/cacheable-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", - "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", "dev": true, "dependencies": { "@types/http-cache-semantics": "*", @@ -430,9 +397,9 @@ "dev": true }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", "dev": true }, "node_modules/@types/json-bigint": { @@ -441,12 +408,6 @@ "integrity": "sha512-WW+0cfH3ovFN6ROV+p/Xfw36dT6s16hbXBYIG49PYw6+j6e+AkpqYccctgxwyicBmC8CZDBnPhOH94shFhXgHQ==", "dev": true }, - "node_modules/@types/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==", - "dev": true - }, "node_modules/@types/json-schema": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", @@ -460,9 +421,9 @@ "dev": true }, "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", "dev": true, "dependencies": { "@types/node": "*" @@ -1046,9 +1007,9 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true, "engines": { "node": ">=8" @@ -1434,9 +1395,9 @@ } }, "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, "node_modules/bytes": { @@ -1553,30 +1514,24 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { - "anymatch": "~3.1.2", + "anymatch": "~3.1.1", "braces": "~3.0.2", - "glob-parent": "~5.1.2", + "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "~3.5.0" }, "engines": { "node": ">= 8.10.0" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.1" } }, "node_modules/chokidar/node_modules/fsevents": { @@ -1746,15 +1701,12 @@ } }, "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", "dev": true, "dependencies": { "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/color-convert": { @@ -1818,19 +1770,6 @@ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, - "node_modules/compress-brotli": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/compress-brotli/-/compress-brotli-1.3.8.tgz", - "integrity": "sha512-lVcQsjhxhIXsuupfy9fmZUFtAIdBmXA7EGY6GBdgZ++qkM9zG4YFT8iU7FoBxzryNDMOpD1HIFHUSX4D87oqhQ==", - "dev": true, - "dependencies": { - "@types/json-buffer": "~3.0.0", - "json-buffer": "~3.0.1" - }, - "engines": { - "node": ">= 12" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3457,15 +3396,15 @@ "dev": true }, "node_modules/geckodriver": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-3.0.2.tgz", - "integrity": "sha512-GHOQzQnTeZOJdcdEXLuzmcRwkbHuei1VivXkn2BLyleKiT6lTvl0T7vm+d0wvr/EZC7jr0m1u1pBHSfqtuFuNQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-3.0.1.tgz", + "integrity": "sha512-cHmbNFqt4eelymsuVt7B5nh+qYGpPCltM7rd+k+CBaTvxGGr4j6STeOYahXMNdSeUbCVhqP345OuqWnvHYAz4Q==", "dev": true, "hasInstallScript": true, "dependencies": { "adm-zip": "0.5.9", "bluebird": "3.7.2", - "got": "11.8.5", + "got": "11.8.2", "https-proxy-agent": "5.0.0", "tar": "6.1.11" }, @@ -3539,9 +3478,9 @@ } }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -3621,9 +3560,9 @@ } }, "node_modules/got": { - "version": "11.8.5", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", - "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", "dev": true, "dependencies": { "@sindresorhus/is": "^4.0.0", @@ -3631,7 +3570,7 @@ "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", + "cacheable-request": "^7.0.1", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", @@ -4614,9 +4553,9 @@ "dev": true }, "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, "node_modules/json-schema-traverse": { @@ -4644,39 +4583,38 @@ "dev": true }, "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "dev": true, + "engines": [ + "node >=0.6.0" + ], "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.4.0", + "json-schema": "0.2.3", "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" } }, "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz", + "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==", "dev": true, "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" + "set-immediate-shim": "~1.0.1" } }, "node_modules/keyv": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.3.3.tgz", - "integrity": "sha512-AcysI17RvakTh8ir03+a3zJr5r0ovnAH/XTXei/4HIv3bL2K/jzvgivLK9UuI/JbU1aJjM3NSAnVvVVd3n+4DQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", "dev": true, "dependencies": { - "compress-brotli": "^1.3.8", "json-buffer": "3.0.1" } }, @@ -5128,9 +5066,9 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "node_modules/minipass": { @@ -5171,32 +5109,33 @@ } }, "node_modules/mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.0.tgz", + "integrity": "sha512-GRGG/q9bIaUkHJB9NL+KZNjDhMBHB30zW3bZW9qOiYr+QChyLjPzswaxFWkI1q6lGlSL28EQYzAi2vKWNkPx+g==", "dev": true, "dependencies": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", + "chokidar": "3.5.1", + "debug": "4.3.1", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "7.1.7", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "4.2.1", + "minimatch": "3.0.4", "ms": "2.1.3", - "nanoid": "3.3.1", - "serialize-javascript": "6.0.0", + "nanoid": "3.1.23", + "serialize-javascript": "5.0.1", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "workerpool": "6.2.0", + "wide-align": "1.1.3", + "workerpool": "6.1.4", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -5228,29 +5167,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/mocha/node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -5315,18 +5231,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mocha/node_modules/minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mocha/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5372,15 +5276,6 @@ "node": ">=8" } }, - "node_modules/mocha/node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/mocha/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5481,9 +5376,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -5571,9 +5466,9 @@ } }, "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.0.1.tgz", + "integrity": "sha512-VU4pzAuh7Kip71XEmO9aNREYAdMHFGTVj/i+CaTImS8x0i1d3jUZkXhqluy/PRgjPLMgsLQulYY3PJ/aSbSjpQ==", "dev": true, "engines": { "node": ">=10" @@ -6235,9 +6130,9 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "dev": true, "dependencies": { "picomatch": "^2.2.1" @@ -6346,9 +6241,9 @@ } }, "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.1.2.tgz", + "integrity": "sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA==", "dev": true }, "node_modules/resolve-cwd": { @@ -6382,15 +6277,12 @@ } }, "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", "dev": true, "dependencies": { "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/restore-cursor": { @@ -6536,9 +6428,9 @@ "dev": true }, "node_modules/semver-regex": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz", - "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.2.tgz", + "integrity": "sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA==", "dev": true, "engines": { "node": ">=8" @@ -6625,11 +6517,14 @@ "node": ">= 0.8.0" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true + "node_modules/set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, "node_modules/setprototypeof": { "version": "1.1.1", @@ -6781,9 +6676,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", "dev": true, "dependencies": { "buffer-from": "^1.0.0", @@ -6956,9 +6851,9 @@ } }, "node_modules/string-width/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true, "engines": { "node": ">=4" @@ -7262,15 +7157,14 @@ } }, "node_modules/terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz", + "integrity": "sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g==", "dev": true, "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map-support": "~0.5.20" + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" }, "bin": { "terser": "bin/terser" @@ -7327,16 +7221,13 @@ "node": ">=0.10.0" } }, - "node_modules/terser/node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "node_modules/terser/node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true, - "bin": { - "acorn": "bin/acorn" - }, "engines": { - "node": ">=0.4.0" + "node": ">= 8" } }, "node_modules/text-table": { @@ -8208,6 +8099,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, "node_modules/wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -8224,9 +8124,9 @@ } }, "node_modules/workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.4.tgz", + "integrity": "sha512-jGWPzsUqzkow8HoAvqaPWTUPCrlPJaJ5tY8Iz7n1uCz3tTp6s3CDG0FF1NsX42WNlkRSW6Mr+CDZGnNoSsKa7g==", "dev": true }, "node_modules/wrap-ansi": { @@ -8519,39 +8419,12 @@ "strip-json-comments": "^3.1.1" } }, - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, "@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, "@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", @@ -8625,15 +8498,15 @@ } }, "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", + "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==", "dev": true }, "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", "dev": true, "requires": { "defer-to-connect": "^2.0.0" @@ -8670,9 +8543,9 @@ "dev": true }, "@types/cacheable-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", - "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", "dev": true, "requires": { "@types/http-cache-semantics": "*", @@ -8714,9 +8587,9 @@ "dev": true }, "@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", "dev": true }, "@types/json-bigint": { @@ -8725,12 +8598,6 @@ "integrity": "sha512-WW+0cfH3ovFN6ROV+p/Xfw36dT6s16hbXBYIG49PYw6+j6e+AkpqYccctgxwyicBmC8CZDBnPhOH94shFhXgHQ==", "dev": true }, - "@types/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==", - "dev": true - }, "@types/json-schema": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", @@ -8744,9 +8611,9 @@ "dev": true }, "@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", "dev": true, "requires": { "@types/node": "*" @@ -9197,9 +9064,9 @@ } }, "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { @@ -9494,9 +9361,9 @@ "dev": true }, "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, "bytes": { @@ -9581,19 +9448,19 @@ } }, "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "dev": true, "requires": { - "anymatch": "~3.1.2", + "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "~3.5.0" }, "dependencies": { "fsevents": { @@ -9723,9 +9590,9 @@ } }, "clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", "dev": true, "requires": { "mimic-response": "^1.0.0" @@ -9783,16 +9650,6 @@ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, - "compress-brotli": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/compress-brotli/-/compress-brotli-1.3.8.tgz", - "integrity": "sha512-lVcQsjhxhIXsuupfy9fmZUFtAIdBmXA7EGY6GBdgZ++qkM9zG4YFT8iU7FoBxzryNDMOpD1HIFHUSX4D87oqhQ==", - "dev": true, - "requires": { - "@types/json-buffer": "~3.0.0", - "json-buffer": "~3.0.1" - } - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -11080,14 +10937,14 @@ "dev": true }, "geckodriver": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-3.0.2.tgz", - "integrity": "sha512-GHOQzQnTeZOJdcdEXLuzmcRwkbHuei1VivXkn2BLyleKiT6lTvl0T7vm+d0wvr/EZC7jr0m1u1pBHSfqtuFuNQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-3.0.1.tgz", + "integrity": "sha512-cHmbNFqt4eelymsuVt7B5nh+qYGpPCltM7rd+k+CBaTvxGGr4j6STeOYahXMNdSeUbCVhqP345OuqWnvHYAz4Q==", "dev": true, "requires": { "adm-zip": "0.5.9", "bluebird": "3.7.2", - "got": "11.8.5", + "got": "11.8.2", "https-proxy-agent": "5.0.0", "tar": "6.1.11" } @@ -11139,9 +10996,9 @@ "dev": true }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -11199,9 +11056,9 @@ } }, "got": { - "version": "11.8.5", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", - "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", "dev": true, "requires": { "@sindresorhus/is": "^4.0.0", @@ -11209,7 +11066,7 @@ "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", + "cacheable-request": "^7.0.1", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", @@ -11906,9 +11763,9 @@ "dev": true }, "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, "json-schema-traverse": { @@ -11936,36 +11793,35 @@ "dev": true }, "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.4.0", + "json-schema": "0.2.3", "verror": "1.10.0" } }, "jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz", + "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==", "dev": true, "requires": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" + "set-immediate-shim": "~1.0.1" } }, "keyv": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.3.3.tgz", - "integrity": "sha512-AcysI17RvakTh8ir03+a3zJr5r0ovnAH/XTXei/4HIv3bL2K/jzvgivLK9UuI/JbU1aJjM3NSAnVvVVd3n+4DQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", "dev": true, "requires": { - "compress-brotli": "^1.3.8", "json-buffer": "3.0.1" } }, @@ -12312,9 +12168,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "minipass": { @@ -12343,32 +12199,33 @@ "dev": true }, "mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.0.tgz", + "integrity": "sha512-GRGG/q9bIaUkHJB9NL+KZNjDhMBHB30zW3bZW9qOiYr+QChyLjPzswaxFWkI1q6lGlSL28EQYzAi2vKWNkPx+g==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", + "chokidar": "3.5.1", + "debug": "4.3.1", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "7.1.7", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "4.2.1", + "minimatch": "3.0.4", "ms": "2.1.3", - "nanoid": "3.3.1", - "serialize-javascript": "6.0.0", + "nanoid": "3.1.23", + "serialize-javascript": "5.0.1", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "workerpool": "6.2.0", + "wide-align": "1.1.3", + "workerpool": "6.1.4", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -12380,23 +12237,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -12437,15 +12277,6 @@ "p-locate": "^5.0.0" } }, - "minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -12476,15 +12307,6 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -12569,9 +12391,9 @@ } }, "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", "dev": true }, "natural-compare": { @@ -12646,9 +12468,9 @@ "dev": true }, "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.0.1.tgz", + "integrity": "sha512-VU4pzAuh7Kip71XEmO9aNREYAdMHFGTVj/i+CaTImS8x0i1d3jUZkXhqluy/PRgjPLMgsLQulYY3PJ/aSbSjpQ==", "dev": true }, "npm-run-path": { @@ -13131,9 +12953,9 @@ } }, "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "dev": true, "requires": { "picomatch": "^2.2.1" @@ -13216,9 +13038,9 @@ } }, "resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.1.2.tgz", + "integrity": "sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA==", "dev": true }, "resolve-cwd": { @@ -13245,9 +13067,9 @@ "dev": true }, "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", "dev": true, "requires": { "lowercase-keys": "^2.0.0" @@ -13350,9 +13172,9 @@ "dev": true }, "semver-regex": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz", - "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.2.tgz", + "integrity": "sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA==", "dev": true }, "send": { @@ -13428,10 +13250,10 @@ "send": "0.17.1" } }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", "dev": true }, "setprototypeof": { @@ -13549,9 +13371,9 @@ } }, "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -13708,9 +13530,9 @@ }, "dependencies": { "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "is-fullwidth-code-point": { @@ -13937,21 +13759,20 @@ } }, "terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz", + "integrity": "sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g==", "dev": true, "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map-support": "~0.5.20" + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" }, "dependencies": { - "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true } } @@ -14630,6 +14451,15 @@ "is-typed-array": "^1.1.3" } }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -14643,9 +14473,9 @@ "dev": true }, "workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.4.tgz", + "integrity": "sha512-jGWPzsUqzkow8HoAvqaPWTUPCrlPJaJ5tY8Iz7n1uCz3tTp6s3CDG0FF1NsX42WNlkRSW6Mr+CDZGnNoSsKa7g==", "dev": true }, "wrap-ansi": { From 5e97b5c9a0777e934b7904d9f6f0a90c1ec3bc40 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 21 Sep 2022 16:19:39 -0400 Subject: [PATCH 10/15] Box storage changes (#656) --- src/boxStorage.ts | 4 +- src/client/client.ts | 23 ++++-- src/client/v2/algod/algod.ts | 15 +++- .../v2/algod/getApplicationBoxByName.ts | 50 ++++++------ src/client/v2/algod/getApplicationBoxes.ts | 30 +++++++- src/client/v2/indexer/indexer.ts | 37 +++++++-- .../lookupApplicationBoxByIDandName.ts | 50 ++++++------ .../v2/indexer/searchForApplicationBoxes.ts | 77 ++++++++++++++++--- src/client/v2/jsonrequest.ts | 13 +++- src/main.ts | 5 +- src/transaction.ts | 36 --------- tests/cucumber/steps/steps.js | 28 +++---- 12 files changed, 234 insertions(+), 134 deletions(-) diff --git a/src/boxStorage.ts b/src/boxStorage.ts index 6d07014ac..05e994800 100644 --- a/src/boxStorage.ts +++ b/src/boxStorage.ts @@ -31,11 +31,11 @@ function translateBoxReference( * into an array of EncodedBoxReferences with foreign indices. */ export function translateBoxReferences( - references: BoxReference[], + references: BoxReference[] | undefined, foreignApps: number[], appIndex: number ): EncodedBoxReference[] { - if (!Array.isArray(references)) return []; + if (references == null) return []; return references.map((bx) => translateBoxReference(bx, foreignApps, appIndex) ); diff --git a/src/client/client.ts b/src/client/client.ts index 8f7ece2e8..b40a0a666 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -171,6 +171,7 @@ export default class HTTPClient { private static prepareResponse( res: BaseHTTPClientResponse, format: 'application/msgpack' | 'application/json', + parseBody: boolean, jsonOptions: utils.JSONOptions = {} ): HTTPClientResponse { let { body } = res; @@ -180,7 +181,7 @@ export default class HTTPClient { text = (body && Buffer.from(body).toString()) || ''; } - if (format === 'application/json') { + if (parseBody && format === 'application/json') { body = HTTPClient.parseJSON(text, res.status, jsonOptions); } @@ -203,7 +204,8 @@ export default class HTTPClient { // eslint-disable-next-line no-param-reassign err.response = HTTPClient.prepareResponse( err.response, - 'application/json' + 'application/json', + true ); // eslint-disable-next-line no-param-reassign err.status = err.response.status; @@ -218,13 +220,16 @@ export default class HTTPClient { * @param requestHeaders - An object containing additional request headers to use. * @param jsonOptions - Options object to use to decode JSON responses. See * utils.parseJSON for the options available. + * @param parseBody - An optional boolean indicating whether the response body should be parsed + * or not. * @returns Response object. */ async get( relativePath: string, query?: Query, requestHeaders: Record = {}, - jsonOptions: utils.JSONOptions = {} + jsonOptions: utils.JSONOptions = {}, + parseBody: boolean = true ): Promise { const format = getAcceptFormat(query); const fullHeaders = { ...requestHeaders, accept: format }; @@ -236,7 +241,7 @@ export default class HTTPClient { fullHeaders ); - return HTTPClient.prepareResponse(res, format, jsonOptions); + return HTTPClient.prepareResponse(res, format, parseBody, jsonOptions); } catch (err) { throw HTTPClient.prepareResponseError(err); } @@ -251,7 +256,8 @@ export default class HTTPClient { relativePath: string, data: any, requestHeaders: Record = {}, - query?: Query + query?: Query, + parseBody: boolean = true ): Promise { const fullHeaders = { 'content-type': 'application/json', @@ -266,7 +272,7 @@ export default class HTTPClient { fullHeaders ); - return HTTPClient.prepareResponse(res, 'application/json'); + return HTTPClient.prepareResponse(res, 'application/json', parseBody); } catch (err) { throw HTTPClient.prepareResponseError(err); } @@ -280,7 +286,8 @@ export default class HTTPClient { async delete( relativePath: string, data: any, - requestHeaders: Record = {} + requestHeaders: Record = {}, + parseBody: boolean = true ) { const fullHeaders = { 'content-type': 'application/json', @@ -294,6 +301,6 @@ export default class HTTPClient { fullHeaders ); - return HTTPClient.prepareResponse(res, 'application/json'); + return HTTPClient.prepareResponse(res, 'application/json', parseBody); } } diff --git a/src/client/v2/algod/algod.ts b/src/client/v2/algod/algod.ts index a044388d0..bd647c9f7 100644 --- a/src/client/v2/algod/algod.ts +++ b/src/client/v2/algod/algod.ts @@ -440,15 +440,21 @@ export default class AlgodClient extends ServiceClient { * ```typescript * const index = 60553466; * const boxName = Buffer.from("foo"); - * const boxValue = await algodClient.getApplicationBoxByName(index).name(boxName).do(); + * const boxResponse = await algodClient.getApplicationBoxByName(index, boxName).do(); + * const boxValue = boxResponse.value; * ``` * * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idbox) * @param index - The application ID to look up. * @category GET */ - getApplicationBoxByName(index: number) { - return new GetApplicationBoxByName(this.c, this.intDecoding, index); + getApplicationBoxByName(index: number, boxName: Uint8Array) { + return new GetApplicationBoxByName( + this.c, + this.intDecoding, + index, + boxName + ); } /** @@ -457,7 +463,8 @@ export default class AlgodClient extends ServiceClient { * #### Example * ```typescript * const index = 60553466; - * const boxesResult = await algodClient.getApplicationBoxes(index).max(3).do(); + * const boxesResponse = await algodClient.getApplicationBoxes(index).max(3).do(); + * const boxNames = boxesResponse.boxes.map(box => box.name); * ``` * * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idboxes) diff --git a/src/client/v2/algod/getApplicationBoxByName.ts b/src/client/v2/algod/getApplicationBoxByName.ts index 685698f7f..7848aa98a 100644 --- a/src/client/v2/algod/getApplicationBoxByName.ts +++ b/src/client/v2/algod/getApplicationBoxByName.ts @@ -1,26 +1,38 @@ import JSONRequest from '../jsonrequest'; import HTTPClient from '../../client'; import IntDecoding from '../../../types/intDecoding'; -import { BoxReference } from '../../../types'; +import { Box } from './models/types'; /** * Given an application ID and the box name (key), return the value stored in the box. * * #### Example * ```typescript - * const index = 1234; + * const index = 60553466; * const boxName = Buffer.from("foo"); - * const boxValue = await algodClient.getApplicationBoxByName(index).name(boxName).do(); + * const boxResponse = await algodClient.getApplicationBoxByName(index, boxName).do(); + * const boxValue = boxResponse.value; * ``` * * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idbox) * @param index - The application ID to look up. * @category GET */ -export default class GetApplicationBoxByName extends JSONRequest { - constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { +export default class GetApplicationBoxByName extends JSONRequest< + Box, + Record +> { + constructor( + c: HTTPClient, + intDecoding: IntDecoding, + private index: number, + name: Uint8Array + ) { super(c, intDecoding); this.index = index; + // Encode name in base64 format and append the encoding prefix. + const encodedName = Buffer.from(name).toString('base64'); + this.query.name = encodeURI(`b64:${encodedName}`); } /** @@ -30,26 +42,12 @@ export default class GetApplicationBoxByName extends JSONRequest { return `/v2/applications/${this.index}/box`; } - /** - * Box name in bytes, and encodes it into a b64 string with goal encoded prefix. - * - * #### Example - * ```typescript - * const boxName = Buffer.from("foo"); - * const boxValue = await algodClient - * .getApplicationBoxByName(1234) - * .name(boxName) - * .do(); - * ``` - * - * @param name - name of box in bytes. - * @category query - */ - name(name: Uint8Array) { - // Encode query in base64 format and append the encoding prefix. - let encodedName = Buffer.from(name).toString('base64'); - encodedName = `b64:${encodedName}`; - this.query.name = encodeURI(encodedName); - return this; + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): Box { + if (body.name == null) + throw new Error(`Response does not contain "name" property: ${body}`); + if (body.value == null) + throw new Error(`Response does not contain "value" property: ${body}`); + return new Box(body.name, body.value); } } diff --git a/src/client/v2/algod/getApplicationBoxes.ts b/src/client/v2/algod/getApplicationBoxes.ts index 626dc4e85..10d1e9253 100644 --- a/src/client/v2/algod/getApplicationBoxes.ts +++ b/src/client/v2/algod/getApplicationBoxes.ts @@ -1,22 +1,26 @@ import JSONRequest from '../jsonrequest'; import HTTPClient from '../../client'; import IntDecoding from '../../../types/intDecoding'; -import { BoxesResponse } from './models/types'; +import { BoxesResponse, BoxDescriptor } from './models/types'; /** * Given an application ID, return all the box names associated with the app. * * #### Example * ```typescript - * const index = 1234; - * const boxesResult = await algodClient.getApplicationBoxes(index).max(3).do(); + * const index = 60553466; + * const boxesResponse = await algodClient.getApplicationBoxes(index).max(3).do(); + * const boxNames = boxesResponse.boxes.map(box => box.name); * ``` * * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idboxes) * @param index - The application ID to look up. * @category GET */ -export default class GetApplicationBoxes extends JSONRequest { +export default class GetApplicationBoxes extends JSONRequest< + BoxesResponse, + Record +> { constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { super(c, intDecoding); this.index = index; @@ -49,4 +53,22 @@ export default class GetApplicationBoxes extends JSONRequest { this.query.max = max; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): BoxesResponse { + if (body.boxes == null || !Array.isArray(body.boxes)) + throw new Error( + `Response does not contain "boxes" array property: ${body}` + ); + + const boxes = (body.boxes as any[]).map((box, index) => { + if (box.name == null) + throw new Error( + `Response box at index ${index} does not contain "name" property: ${box}` + ); + return new BoxDescriptor(box.name); + }); + + return new BoxesResponse(boxes); + } } diff --git a/src/client/v2/indexer/indexer.ts b/src/client/v2/indexer/indexer.ts index af6c3360a..7fd053e24 100644 --- a/src/client/v2/indexer/indexer.ts +++ b/src/client/v2/indexer/indexer.ts @@ -18,13 +18,17 @@ import SearchAccounts from './searchAccounts'; import SearchForTransactions from './searchForTransactions'; import SearchForAssets from './searchForAssets'; import SearchForApplications from './searchForApplications'; -import SearchForApplicationBoxes from './searchForApplicationBoxes'; +import SearchForApplicationBoxes, { + SearchForApplicationBoxesResponse, +} from './searchForApplicationBoxes'; import { BaseHTTPClient } from '../../baseHTTPClient'; import { CustomTokenHeader, IndexerTokenHeader, } from '../../urlTokenBaseHTTPClient'; +export { SearchForApplicationBoxesResponse }; + /** * The Indexer provides a REST API interface of API calls to support searching the Algorand Blockchain. * @@ -380,7 +384,21 @@ export default class IndexerClient extends ServiceClient { * * #### Example * ```typescript - * const boxesResult = await indexerClient.searchForApplicationBoxes().do(); + * const maxResults = 20; + * const appID = 1234; + * + * const responsePage1 = await indexerClient + * .searchForApplicationBoxes(appID) + * .limit(maxResults) + * .do(); + * const boxNamesPage1 = responsePage1.boxes.map(box => box.name); + * + * const responsePage2 = await indexerClient + * .searchForApplicationBoxes(appID) + * .limit(maxResults) + * .nextToken(responsePage1.nextToken) + * .do(); + * const boxNamesPage2 = responsePage2.boxes.map(box => box.name); * ``` * * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applicationsapplication-idboxes) @@ -396,14 +414,23 @@ export default class IndexerClient extends ServiceClient { * * #### Example * ```typescript - * const boxValue = await indexerClient.lookupApplicationBoxByIDandName().do(); + * const boxName = Buffer.from("foo"); + * const boxResponse = await indexerClient + * .LookupApplicationBoxByIDandName(1234, boxName) + * .do(); + * const boxValue = boxResponse.value; * ``` * * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applicationsapplication-idbox) * @param appID - The ID of the application with boxes. * @category GET */ - lookupApplicationBoxByIDandName(appID: number) { - return new LookupApplicationBoxByIDandName(this.c, this.intDecoding, appID); + lookupApplicationBoxByIDandName(appID: number, boxName: Uint8Array) { + return new LookupApplicationBoxByIDandName( + this.c, + this.intDecoding, + appID, + boxName + ); } } diff --git a/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts b/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts index 9dc3fc2c4..80c069913 100644 --- a/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts +++ b/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts @@ -1,23 +1,39 @@ import JSONRequest from '../jsonrequest'; import HTTPClient from '../../client'; import IntDecoding from '../../../types/intDecoding'; +import { Box } from '../algod/models/types'; -export default class LookupApplicationBoxByIDandName extends JSONRequest { +export default class LookupApplicationBoxByIDandName extends JSONRequest< + Box, + Record +> { /** * Returns information about indexed application boxes. * * #### Example * ```typescript - * const boxValue = await indexerClient.LookupApplicationBoxByIDandName(1234).do(); + * const boxName = Buffer.from("foo"); + * const boxResponse = await indexerClient + * .LookupApplicationBoxByIDandName(1234, boxName) + * .do(); + * const boxValue = boxResponse.value; * ``` * * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applicationsapplication-idbox) * @oaram index - application index. * @category GET */ - constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { + constructor( + c: HTTPClient, + intDecoding: IntDecoding, + private index: number, + boxName: Uint8Array + ) { super(c, intDecoding); this.index = index; + // Encode query in base64 format and append the encoding prefix. + const encodedName = Buffer.from(boxName).toString('base64'); + this.query.name = encodeURI(`b64:${encodedName}`); } /** @@ -27,26 +43,12 @@ export default class LookupApplicationBoxByIDandName extends JSONRequest { return `/v2/applications/${this.index}/box`; } - /** - * Box name in bytes, and encodes it into a b64 string with goal encoded prefix. - * - * #### Example - * ```typescript - * const boxName = Buffer.from("foo"); - * const boxValue = await indexerClient - * .LookupApplicationBoxByIDandName(1234) - * .name(boxName) - * .do(); - * ``` - * - * @param name - name of box in bytes. - * @category query - */ - name(name: Uint8Array) { - // Encode query in base64 format and append the encoding prefix. - let encodedName = Buffer.from(name).toString('base64'); - encodedName = `b64:${encodedName}`; - this.query.name = encodeURI(encodedName); - return this; + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): Box { + if (body.name == null) + throw new Error(`Response does not contain "name" property: ${body}`); + if (body.value == null) + throw new Error(`Response does not contain "value" property: ${body}`); + return new Box(body.name, body.value); } } diff --git a/src/client/v2/indexer/searchForApplicationBoxes.ts b/src/client/v2/indexer/searchForApplicationBoxes.ts index 08da7c510..6c34cb15e 100644 --- a/src/client/v2/indexer/searchForApplicationBoxes.ts +++ b/src/client/v2/indexer/searchForApplicationBoxes.ts @@ -1,14 +1,38 @@ import JSONRequest from '../jsonrequest'; import HTTPClient from '../../client'; import IntDecoding from '../../../types/intDecoding'; +import { BoxDescriptor } from '../algod/models/types'; -export default class SearchForApplicationBoxes extends JSONRequest { +export interface SearchForApplicationBoxesResponse { + applicationId: number; + boxes: BoxDescriptor[]; + nextToken?: string; +} + +export default class SearchForApplicationBoxes extends JSONRequest< + SearchForApplicationBoxesResponse, + Record +> { /** * Returns information about indexed application boxes. * * #### Example * ```typescript - * const boxesResult = await indexerClient.SearchForApplicationBoxes(1234).do(); + * const maxResults = 20; + * const appID = 1234; + * + * const responsePage1 = await indexerClient + * .searchForApplicationBoxes(appID) + * .limit(maxResults) + * .do(); + * const boxNamesPage1 = responsePage1.boxes.map(box => box.name); + * + * const responsePage2 = await indexerClient + * .searchForApplicationBoxes(appID) + * .limit(maxResults) + * .nextToken(responsePage1.nextToken) + * .do(); + * const boxNamesPage2 = responsePage2.boxes.map(box => box.name); * ``` * * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applicationsapplication-idboxes) @@ -33,17 +57,20 @@ export default class SearchForApplicationBoxes extends JSONRequest { * #### Example * ```typescript * const maxResults = 20; + * const appID = 1234; * - * const boxesPage1 = await indexerClient - * .SearchForApplicationBoxes(1234) + * const responsePage1 = await indexerClient + * .searchForApplicationBoxes(appID) * .limit(maxResults) * .do(); + * const boxNamesPage1 = responsePage1.boxes.map(box => box.name); * - * const boxesPage2 = await indexerClient - * .SearchForApplicationBoxes(1234) + * const responsePage2 = await indexerClient + * .searchForApplicationBoxes(appID) * .limit(maxResults) - * .nextToken(boxesPage1["next-token"]) + * .nextToken(responsePage1.nextToken) * .do(); + * const boxNamesPage2 = responsePage2.boxes.map(box => box.name); * ``` * @param nextToken - provided by the previous results. * @category query @@ -59,8 +86,8 @@ export default class SearchForApplicationBoxes extends JSONRequest { * #### Example * ```typescript * const maxResults = 20; - * const boxesResult = await indexerClient - * .SearchForApplicationBoxes(1234) + * const boxesResponse = await indexerClient + * .searchForApplicationBoxes(1234) * .limit(maxResults) * .do(); * ``` @@ -72,4 +99,36 @@ export default class SearchForApplicationBoxes extends JSONRequest { this.query.limit = limit; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): SearchForApplicationBoxesResponse { + if (typeof body['application-id'] !== 'number') { + throw new Error( + `Response does not contain "application-id" number property: ${body}` + ); + } + const applicationId: number = body['application-id']; + + if (body.boxes == null || !Array.isArray(body.boxes)) + throw new Error( + `Response does not contain "boxes" array property: ${body}` + ); + const boxes = (body.boxes as any[]).map((box, index) => { + if (box.name == null) + throw new Error( + `Response box at index ${index} does not contain "name" property: ${box}` + ); + return new BoxDescriptor(box.name); + }); + + const response: SearchForApplicationBoxesResponse = { + applicationId, + boxes, + }; + if (body['next-token'] != null) { + response.nextToken = body['next-token']; + } + + return response; + } } diff --git a/src/client/v2/jsonrequest.ts b/src/client/v2/jsonrequest.ts index 16e344440..5b1062d6b 100644 --- a/src/client/v2/jsonrequest.ts +++ b/src/client/v2/jsonrequest.ts @@ -50,7 +50,7 @@ export default abstract class JSONRequest< /** * Execute the request. * @param headers - Additional headers to send in the request. Optional. - * @returns A promise which resolves to the response data. + * @returns A promise which resolves to the parsed response data. * @category JSONRequest */ async do(headers: Record = {}): Promise { @@ -62,6 +62,17 @@ export default abstract class JSONRequest< return this.prepare(res.body); } + /** + * Execute the request, but do not process the response data in any way. + * @param headers - Additional headers to send in the request. Optional. + * @returns A promise which resolves to the raw response data, exactly as returned by the server. + * @category JSONRequest + */ + async doRaw(headers: Record = {}): Promise { + const res = await this.c.get(this.path(), this.query, headers, {}, false); + return res.body; + } + /** * Configure how integers in this request's JSON response will be decoded. * diff --git a/src/main.ts b/src/main.ts index 9c27efa82..c14d97bc4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -122,7 +122,10 @@ export { default as Algodv2 } from './client/v2/algod/algod'; export { default as Kmd } from './client/kmd'; export { default as IntDecoding } from './types/intDecoding'; export { default as Account } from './types/account'; -export { default as Indexer } from './client/v2/indexer/indexer'; +export { + default as Indexer, + SearchForApplicationBoxesResponse, +} from './client/v2/indexer/indexer'; export { BaseHTTPClient, BaseHTTPClientResponse, diff --git a/src/transaction.ts b/src/transaction.ts index e8965adeb..af4efd849 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -917,42 +917,6 @@ 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; } diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index c30f8d755..c58548e0b 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -139,7 +139,7 @@ module.exports = function getSteps(options) { case 'str': return makeUint8Array(Buffer.from(subArg[1])); case 'int': - return makeUint8Array([parseInt(subArg[1])]); + return makeUint8Array(algosdk.encodeUint64(parseInt(subArg[1], 10))); case 'addr': return algosdk.decodeAddress(subArg[1]).publicKey; case 'b64': @@ -1840,14 +1840,14 @@ module.exports = function getSteps(options) { 'we make a GetApplicationBoxByName call for applicationID {int} with encoded box name {string}', async function (index, boxName) { const box = splitAndProcessAppArgs(boxName)[0]; - await this.v2Client.getApplicationBoxByName(index).name(box).do(); + await this.v2Client.getApplicationBoxByName(index, box).doRaw(); } ); When( 'we make a GetApplicationBoxes call for applicationID {int} with max {int}', async function (index, limit) { - await this.v2Client.getApplicationBoxes(index).max(limit).do(); + await this.v2Client.getApplicationBoxes(index).max(limit).doRaw(); } ); @@ -2253,7 +2253,7 @@ module.exports = function getSteps(options) { .searchForApplicationBoxes(index) .limit(limit) .nextToken(token) - .do(); + .doRaw(); } ); @@ -2262,9 +2262,8 @@ module.exports = function getSteps(options) { async function (index, name) { const boxKey = splitAndProcessAppArgs(name)[0]; await this.indexerClient - .lookupApplicationBoxByIDandName(index) - .name(boxKey) - .do(); + .lookupApplicationBoxByIDandName(index, boxKey) + .doRaw(); } ); @@ -4451,13 +4450,14 @@ module.exports = function getSteps(options) { let resp = null; if (fromClient === 'algod') { resp = await this.v2Client - .getApplicationBoxByName(this.currentApplicationIndex) - .name(boxKey) + .getApplicationBoxByName(this.currentApplicationIndex, boxKey) .do(); } else if (fromClient === 'indexer') { resp = await this.indexerV2client - .lookupApplicationBoxByIDandName(this.currentApplicationIndex) - .name(boxKey) + .lookupApplicationBoxByIDandName( + this.currentApplicationIndex, + boxKey + ) .do(); } else { assert.fail(`expecting algod or indexer, got ${fromClient}`); @@ -4465,11 +4465,11 @@ module.exports = function getSteps(options) { const actualName = resp.name; const actualValue = resp.value; + assert.deepStrictEqual(Buffer.from(boxKey), Buffer.from(actualName)); assert.deepStrictEqual( - Buffer.from(boxKey), - Buffer.from(actualName, 'base64') + Buffer.from(boxValue, 'base64'), + Buffer.from(actualValue) ); - assert.deepStrictEqual(boxValue, actualValue); } catch (err) { if (errString !== '') { assert.deepStrictEqual( From 4026bf505b9c2b52f3adada24dbbb7b94b7a18cc Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 26 Sep 2022 09:33:49 -0700 Subject: [PATCH 11/15] Re-order box args to avoid breaking changes (#660) --- src/makeTxn.ts | 70 +++++++++++++++++------------------ tests/cucumber/steps/steps.js | 19 +++++++++- 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/src/makeTxn.ts b/src/makeTxn.ts index 9b468cf1a..18152f732 100644 --- a/src/makeTxn.ts +++ b/src/makeTxn.ts @@ -1100,11 +1100,11 @@ export function makeAssetTransferTxnWithSuggestedParamsFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions * @param extraPages - integer extra pages of memory to rent on creation of application + * @param boxes - Array of BoxReference, app ID and name of box to be accessed */ export function makeApplicationCreateTxn( from: AppCreateTxn['from'], @@ -1120,11 +1120,11 @@ export function makeApplicationCreateTxn( accounts?: AppCreateTxn['appAccounts'], foreignApps?: AppCreateTxn['appForeignApps'], foreignAssets?: AppCreateTxn['appForeignAssets'], - boxes?: AppCreateTxn['boxes'], note?: AppCreateTxn['note'], lease?: AppCreateTxn['lease'], rekeyTo?: AppCreateTxn['reKeyTo'], - extraPages?: AppCreateTxn['extraPages'] + extraPages?: AppCreateTxn['extraPages'], + boxes?: AppCreateTxn['boxes'] ) { const o: AppCreateTxn = { type: TransactionType.appl, @@ -1206,11 +1206,11 @@ export function makeApplicationCreateTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, - o.boxes, o.note, o.lease, o.rekeyTo, - o.extraPages + o.extraPages, + o.boxes ); } @@ -1232,10 +1232,10 @@ export function makeApplicationCreateTxnFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions + * @param boxes - Array of BoxReference, app ID and name of box to be accessed */ export function makeApplicationUpdateTxn( from: AppUpdateTxn['from'], @@ -1247,10 +1247,10 @@ export function makeApplicationUpdateTxn( accounts?: AppUpdateTxn['appAccounts'], foreignApps?: AppUpdateTxn['appForeignApps'], foreignAssets?: AppUpdateTxn['appForeignAssets'], - boxes?: AppUpdateTxn['boxes'], note?: AppUpdateTxn['note'], lease?: AppUpdateTxn['lease'], - rekeyTo?: AppUpdateTxn['reKeyTo'] + rekeyTo?: AppUpdateTxn['reKeyTo'], + boxes?: AppUpdateTxn['boxes'] ) { const o: AppUpdateTxn = { type: TransactionType.appl, @@ -1313,10 +1313,10 @@ export function makeApplicationUpdateTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, - o.boxes, o.note, o.lease, - o.rekeyTo + o.rekeyTo, + o.boxes ); } @@ -1336,10 +1336,10 @@ export function makeApplicationUpdateTxnFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions + * @param boxes - Array of BoxReference, app ID and name of box to be accessed */ export function makeApplicationDeleteTxn( from: AppDeleteTxn['from'], @@ -1349,10 +1349,10 @@ export function makeApplicationDeleteTxn( accounts?: AppDeleteTxn['appAccounts'], foreignApps?: AppDeleteTxn['appForeignApps'], foreignAssets?: AppDeleteTxn['appForeignAssets'], - boxes?: AppDeleteTxn['boxes'], note?: AppDeleteTxn['note'], lease?: AppDeleteTxn['lease'], - rekeyTo?: AppDeleteTxn['reKeyTo'] + rekeyTo?: AppDeleteTxn['reKeyTo'], + boxes?: AppDeleteTxn['boxes'] ) { const o: AppDeleteTxn = { type: TransactionType.appl, @@ -1407,10 +1407,10 @@ export function makeApplicationDeleteTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, - o.boxes, o.note, o.lease, - o.rekeyTo + o.rekeyTo, + o.boxes ); } @@ -1430,10 +1430,10 @@ export function makeApplicationDeleteTxnFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions + * @param boxes - Array of BoxReference, app ID and name of box to be accessed */ export function makeApplicationOptInTxn( from: AppOptInTxn['from'], @@ -1443,10 +1443,10 @@ export function makeApplicationOptInTxn( accounts?: AppOptInTxn['appAccounts'], foreignApps?: AppOptInTxn['appForeignApps'], foreignAssets?: AppOptInTxn['appForeignAssets'], - boxes?: AppOptInTxn['boxes'], note?: AppOptInTxn['note'], lease?: AppOptInTxn['lease'], - rekeyTo?: AppOptInTxn['reKeyTo'] + rekeyTo?: AppOptInTxn['reKeyTo'], + boxes?: AppOptInTxn['boxes'] ) { const o: AppOptInTxn = { type: TransactionType.appl, @@ -1501,10 +1501,10 @@ export function makeApplicationOptInTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, - o.boxes, o.note, o.lease, - o.rekeyTo + o.rekeyTo, + o.boxes ); } @@ -1524,10 +1524,10 @@ export function makeApplicationOptInTxnFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions + * @param boxes - Array of BoxReference, app ID and name of box to be accessed */ export function makeApplicationCloseOutTxn( from: AppCloseOutTxn['from'], @@ -1537,10 +1537,10 @@ export function makeApplicationCloseOutTxn( accounts?: AppCloseOutTxn['appAccounts'], foreignApps?: AppCloseOutTxn['appForeignApps'], foreignAssets?: AppCloseOutTxn['appForeignAssets'], - boxes?: AppCloseOutTxn['boxes'], note?: AppCloseOutTxn['note'], lease?: AppCloseOutTxn['lease'], - rekeyTo?: AppCloseOutTxn['reKeyTo'] + rekeyTo?: AppCloseOutTxn['reKeyTo'], + boxes?: AppCloseOutTxn['boxes'] ) { const o: AppCloseOutTxn = { type: TransactionType.appl, @@ -1595,10 +1595,10 @@ export function makeApplicationCloseOutTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, - o.boxes, o.note, o.lease, - o.rekeyTo + o.rekeyTo, + o.boxes ); } @@ -1618,10 +1618,10 @@ export function makeApplicationCloseOutTxnFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions + * @param boxes - Array of BoxReference, app ID and name of box to be accessed */ export function makeApplicationClearStateTxn( from: AppClearStateTxn['from'], @@ -1631,10 +1631,10 @@ export function makeApplicationClearStateTxn( accounts?: AppClearStateTxn['appAccounts'], foreignApps?: AppClearStateTxn['appForeignApps'], foreignAssets?: AppClearStateTxn['appForeignAssets'], - boxes?: AppClearStateTxn['boxes'], note?: AppClearStateTxn['note'], lease?: AppClearStateTxn['lease'], - rekeyTo?: AppClearStateTxn['reKeyTo'] + rekeyTo?: AppClearStateTxn['reKeyTo'], + boxes?: AppClearStateTxn['boxes'] ) { const o: AppClearStateTxn = { type: TransactionType.appl, @@ -1689,10 +1689,10 @@ export function makeApplicationClearStateTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, - o.boxes, o.note, o.lease, - o.rekeyTo + o.rekeyTo, + o.boxes ); } @@ -1712,10 +1712,10 @@ export function makeApplicationClearStateTxnFromObject( * @param accounts - Array of Address strings, any additional accounts to supply to the application * @param foreignApps - Array of int, any other apps used by the application, identified by index * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param boxes - Array of BoxReference, app ID and name of box to be accessed * @param note - Arbitrary data for sender to store * @param lease - Lease a transaction * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions + * @param boxes - Array of BoxReference, app ID and name of box to be accessed */ export function makeApplicationNoOpTxn( from: AppNoOpTxn['from'], @@ -1725,10 +1725,10 @@ export function makeApplicationNoOpTxn( accounts?: AppNoOpTxn['appAccounts'], foreignApps?: AppNoOpTxn['appForeignApps'], foreignAssets?: AppNoOpTxn['appForeignAssets'], - boxes?: AppNoOpTxn['boxes'], note?: AppNoOpTxn['note'], lease?: AppNoOpTxn['lease'], - rekeyTo?: AppNoOpTxn['reKeyTo'] + rekeyTo?: AppNoOpTxn['reKeyTo'], + boxes?: AppNoOpTxn['boxes'] ) { const o: AppNoOpTxn = { type: TransactionType.appl, @@ -1783,10 +1783,10 @@ export function makeApplicationNoOpTxnFromObject( o.accounts, o.foreignApps, o.foreignAssets, - o.boxes, o.note, o.lease, - o.rekeyTo + o.rekeyTo, + o.boxes ); } diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index c58548e0b..a96f1f088 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -3196,6 +3196,9 @@ module.exports = function getSteps(options) { appAccounts, foreignApps, foreignAssets, + undefined, + undefined, + undefined, boxes ); return; @@ -3214,11 +3217,11 @@ module.exports = function getSteps(options) { appAccounts, foreignApps, foreignAssets, - boxes, undefined, undefined, undefined, - extraPages + extraPages, + boxes ); return; case 'update': @@ -3232,6 +3235,9 @@ module.exports = function getSteps(options) { appAccounts, foreignApps, foreignAssets, + undefined, + undefined, + undefined, boxes ); return; @@ -3244,6 +3250,9 @@ module.exports = function getSteps(options) { appAccounts, foreignApps, foreignAssets, + undefined, + undefined, + undefined, boxes ); return; @@ -3256,6 +3265,9 @@ module.exports = function getSteps(options) { appAccounts, foreignApps, foreignAssets, + undefined, + undefined, + undefined, boxes ); return; @@ -3280,6 +3292,9 @@ module.exports = function getSteps(options) { appAccounts, foreignApps, foreignAssets, + undefined, + undefined, + undefined, boxes ); return; From 075d8d06a2020897a93d790822dd9b7fe81c270b Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 12 Oct 2022 10:56:16 -0700 Subject: [PATCH 12/15] Box storage merge develop (#668) --- .test-env | 2 + CHANGELOG.md | 12 ++++++ Makefile | 5 ++- README.md | 4 +- package.json | 2 +- src/client/algod.js | 21 ++++++++++ src/client/v2/algod/algod.ts | 18 ++++++++ src/client/v2/algod/getBlockHash.ts | 18 ++++++++ src/client/v2/indexer/lookupBlock.ts | 9 ++++ src/client/v2/indexer/searchAccounts.ts | 2 +- test-harness.sh | 56 ++++++++++++++++++++++++- tests/cucumber/steps/steps.js | 14 +++++++ tests/cucumber/unit.tags | 3 ++ 13 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 src/client/v2/algod/getBlockHash.ts diff --git a/.test-env b/.test-env index eff6690c0..9135343b7 100644 --- a/.test-env +++ b/.test-env @@ -3,6 +3,8 @@ SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing" SDK_TESTING_BRANCH="feature/box-storage" SDK_TESTING_HARNESS="test-harness" +INSTALL_ONLY=0 + VERBOSE_HARNESS=0 # WARNING: If set to 1, new features will be LOST when downloading the test harness. diff --git a/CHANGELOG.md b/CHANGELOG.md index a506677f8..e4114e3e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# v1.21.0 + +## What's Changed + +### Enhancements + +- Enhancement: Removing more unused steps by @tzaffi in https://github.com/algorand/js-algorand-sdk/pull/637 +- Enhancement: Add deprecation tag to algod v1 client by @algochoi in https://github.com/algorand/js-algorand-sdk/pull/642 +- enhancement: add unit test for ParticipationUpdates field by @shiqizng in https://github.com/algorand/js-algorand-sdk/pull/652 + +**Full Changelog**: https://github.com/algorand/js-algorand-sdk/compare/v1.20.0...v1.21.0 + # v1.20.0 ## What's Changed diff --git a/Makefile b/Makefile index 6f9bb461a..3fdc3afab 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,10 @@ 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 + ./test-harness.sh up + +harness-down: + ./test-harness.sh down docker-build: docker build -t js-sdk-testing -f tests/cucumber/docker/Dockerfile $(CURDIR) --build-arg TEST_BROWSER --build-arg CI=true diff --git a/README.md b/README.md index 31e26cc80..69e6e9cec 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Include a minified browser bundle directly in your HTML like so: ```html @@ -32,7 +32,7 @@ or ```html diff --git a/package.json b/package.json index 2803b38a3..0f4a53a46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "algosdk", - "version": "1.20.0", + "version": "1.21.0", "description": "The official JavaScript SDK for Algorand", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/src/client/algod.js b/src/client/algod.js index c1609454e..284870f4f 100644 --- a/src/client/algod.js +++ b/src/client/algod.js @@ -1,6 +1,7 @@ const { default: HTTPClient } = require('./client'); const { setSendTransactionHeaders } = require('./v2/algod/sendRawTransaction'); +/** @deprecated v1 algod APIs are deprecated, please use the v2 client */ function Algod( token = '', baseServer = 'http://r2.algorand.network', @@ -20,6 +21,7 @@ function Algod( * Takes an object and convert its note field to Buffer, if exist. * @param o * @returns {*} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ function noteb64ToNote(o) { if (!(o.noteb64 === undefined || o.noteb64 === null)) { @@ -33,6 +35,7 @@ function Algod( * status retrieves the StatusResponse from the running node * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.status = async (headerObj = {}) => { const res = await c.get('/v1/status', {}, headerObj); @@ -43,6 +46,7 @@ function Algod( * healthCheck returns an empty object iff the node is running * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.healthCheck = async (headerObj = {}) => { const res = await c.get('/health', {}, headerObj); @@ -58,6 +62,7 @@ function Algod( * @param roundNumber * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.statusAfterBlock = async (roundNumber, headerObj = {}) => { if (!Number.isInteger(roundNumber)) @@ -76,6 +81,7 @@ function Algod( * @param maxTxns - number * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.pendingTransactions = async (maxTxns, headerObj = {}) => { if (!Number.isInteger(maxTxns)) throw Error('maxTxns should be an integer'); @@ -101,6 +107,7 @@ function Algod( * versions retrieves the VersionResponse from the running node * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.versions = async (headerObj = {}) => { const res = await c.get('/versions', {}, headerObj); @@ -111,6 +118,7 @@ function Algod( * LedgerSupply gets the supply details for the specified node's Ledger * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.ledgerSupply = async (headerObj = {}) => { const res = await c.get('/v1/ledger/supply', {}, headerObj); @@ -125,6 +133,7 @@ function Algod( * @param maxTxns - number, optional * @param headers, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.transactionByAddress = async ( addr, @@ -161,6 +170,7 @@ function Algod( * @param maxTxns - number, optional * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.transactionByAddressAndDate = async ( addr, @@ -188,6 +198,7 @@ function Algod( * @param txid * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.transactionById = async (txid, headerObj = {}) => { const res = await c.get(`/v1/transaction/${txid}`, {}, headerObj); @@ -203,6 +214,7 @@ function Algod( * @param txid * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.transactionInformation = async (addr, txid, headerObj = {}) => { const res = await c.get( @@ -221,6 +233,7 @@ function Algod( * @param txid * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.pendingTransactionInformation = async (txid, headerObj = {}) => { const res = await c.get(`/v1/transactions/pending/${txid}`, {}, headerObj); @@ -235,6 +248,7 @@ function Algod( * @param addr - string * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.accountInformation = async (addr, headerObj = {}) => { const res = await c.get(`/v1/account/${addr}`, {}, headerObj); @@ -246,6 +260,7 @@ function Algod( * @param index - number * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.assetInformation = async (index, headerObj = {}) => { const res = await c.get(`/v1/asset/${index}`, {}, headerObj); @@ -256,6 +271,7 @@ function Algod( * suggestedFee gets the recommended transaction fee from the node * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.suggestedFee = async (headerObj = {}) => { const res = await c.get('/v1/transactions/fee', {}, headerObj); @@ -267,6 +283,7 @@ function Algod( * @param txn - Uin8Array * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.sendRawTransaction = async (txn, headerObj = {}) => { const txHeaders = setSendTransactionHeaders(headerObj); @@ -279,6 +296,7 @@ function Algod( * @param txn - Array of Uin8Array * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.sendRawTransactions = async (txns, headerObj = {}) => { const txHeaders = setSendTransactionHeaders(headerObj); @@ -297,6 +315,7 @@ function Algod( * getTransactionParams returns to common needed parameters for a new transaction * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.getTransactionParams = async (headerObj = {}) => { const res = await c.get('/v1/transactions/params', {}, headerObj); @@ -307,6 +326,7 @@ function Algod( * suggestParams returns to common needed parameters for a new transaction, in a format the transaction builder expects * @param headerObj, optional * @returns {Object} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.suggestParams = async (headerObj = {}) => { const result = await this.getTransactionParams(headerObj); @@ -325,6 +345,7 @@ function Algod( * @param roundNumber * @param headerObj, optional * @returns {Promise<*>} + * @deprecated v1 algod APIs are deprecated, please use the v2 client */ this.block = async (roundNumber, headerObj = {}) => { if (!Number.isInteger(roundNumber)) diff --git a/src/client/v2/algod/algod.ts b/src/client/v2/algod/algod.ts index bd647c9f7..3175a8e79 100644 --- a/src/client/v2/algod/algod.ts +++ b/src/client/v2/algod/algod.ts @@ -9,6 +9,7 @@ import Dryrun from './dryrun'; import Genesis from './genesis'; import GetAssetByID from './getAssetByID'; import GetApplicationByID from './getApplicationByID'; +import GetBlockHash from './getBlockHash'; import GetApplicationBoxByName from './getApplicationBoxByName'; import GetApplicationBoxes from './getApplicationBoxes'; import HealthCheck from './healthCheck'; @@ -207,6 +208,23 @@ export default class AlgodClient extends ServiceClient { return new Block(this.c, roundNumber); } + /** + * Get the block hash for the block on the given round. + * + * #### Example + * ```typescript + * const roundNumber = 18038133; + * const block = await algodClient.getBlockHash(roundNumber).do(); + * ``` + * + * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2blocksroundhash) + * @param roundNumber - The round number of the block to get. + * @category GET + */ + getBlockHash(roundNumber: number) { + return new GetBlockHash(this.c, this.intDecoding, roundNumber); + } + /** * Returns the transaction information for a specific pending transaction. * diff --git a/src/client/v2/algod/getBlockHash.ts b/src/client/v2/algod/getBlockHash.ts new file mode 100644 index 000000000..40d01b499 --- /dev/null +++ b/src/client/v2/algod/getBlockHash.ts @@ -0,0 +1,18 @@ +import JSONRequest from '../jsonrequest'; +import HTTPClient from '../../client'; +import IntDecoding from '../../../types/intDecoding'; + +export default class GetBlockHash extends JSONRequest { + round: number; + + constructor(c: HTTPClient, intDecoding: IntDecoding, roundNumber: number) { + super(c, intDecoding); + if (!Number.isInteger(roundNumber)) + throw Error('roundNumber should be an integer'); + this.round = roundNumber; + } + + path() { + return `/v2/blocks/${this.round}/hash`; + } +} diff --git a/src/client/v2/indexer/lookupBlock.ts b/src/client/v2/indexer/lookupBlock.ts index d722827cf..3b5030712 100644 --- a/src/client/v2/indexer/lookupBlock.ts +++ b/src/client/v2/indexer/lookupBlock.ts @@ -27,4 +27,13 @@ export default class LookupBlock extends JSONRequest { path() { return `/v2/blocks/${this.round}`; } + + /** + * Header only flag. When this is set to true, returned block does not contain the + * transactions. + */ + headerOnly(headerOnly: boolean) { + this.query['header-only'] = headerOnly; + return this; + } } diff --git a/src/client/v2/indexer/searchAccounts.ts b/src/client/v2/indexer/searchAccounts.ts index 31a62f02d..21b1df4b0 100644 --- a/src/client/v2/indexer/searchAccounts.ts +++ b/src/client/v2/indexer/searchAccounts.ts @@ -43,7 +43,7 @@ export default class SearchAccounts extends JSONRequest { * .do(); * ``` * @remarks - * If you are looking for accounts with the currency amount greater than 0, simply construct the query without `currencyGreaterThan` because it doesn't accept `-1`, and passing the `0` `currency-greater-than` value would exclude transactions with a 0 amount. + * If you are looking for accounts with the currency amount greater than 0, simply construct the query without `currencyGreaterThan` because it doesn't accept `-1`, and passing the `0` `currency-greater-than` value would exclude accounts with a 0 amount. * * @param greater * @category query diff --git a/test-harness.sh b/test-harness.sh index ad68eaf63..182039e1a 100755 --- a/test-harness.sh +++ b/test-harness.sh @@ -1,7 +1,47 @@ #!/usr/bin/env bash - set -euo pipefail +# test-harness.sh setup/start cucumber test environment. +# +# Configuration is managed with environment variables, the ones you +# are most likely to reconfigured are stored in '.test-env'. +# +# Variables: +# SDK_TESTING_URL - URL to algorand-sdk-testing, useful for forks. +# SDK_TESTING_BRANCH - branch to checkout, useful for new tests. +# SDK_TESTING_HARNESS - local directory that the algorand-sdk-testing repo is cloned into. +# VERBOSE_HARNESS - more output while the script runs. +# INSTALL_ONLY - installs feature files only, useful for unit tests. +# +# WARNING: If set to 1, new features will be LOST when downloading the test harness. +# REGARDLESS: modified features are ALWAYS overwritten. +# REMOVE_LOCAL_FEATURES - delete all local cucumber feature files before downloading these from github. +# +# 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 + +SHUTDOWN=0 +if [ $# -ne 0 ]; then + if [ $# -ne 1 ]; then + echo "this script accepts a single argument, which must be 'up' or 'down'." + exit 1 + fi + + case $1 in + 'up') + ;; # default. + 'down') + SHUTDOWN=1 + ;; + *) + echo "unknown parameter '$1'." + echo "this script accepts a single argument, which must be 'up' or 'down'." + exit 1 + ;; + esac +fi + START=$(date "+%s") THIS=$(basename "$0") @@ -23,10 +63,19 @@ if [ -d "$SDK_TESTING_HARNESS" ]; then ./scripts/down.sh popd rm -rf "$SDK_TESTING_HARNESS" + if [[ $SHUTDOWN == 1 ]]; then + echo "$THIS: network shutdown complete." + exit 0 + fi else echo "$THIS: directory $SDK_TESTING_HARNESS does not exist - NOOP" fi +if [[ $SHUTDOWN == 1 ]]; then + echo "$THIS: unable to shutdown network." + exit 1 +fi + git clone --depth 1 --single-branch --branch "$SDK_TESTING_BRANCH" "$SDK_TESTING_URL" "$SDK_TESTING_HARNESS" @@ -52,6 +101,11 @@ if [[ $VERBOSE_HARNESS == 1 ]]; then fi echo "$THIS: seconds it took to get to end of cloning and copying: $(($(date "+%s") - START))s" +if [[ $INSTALL_ONLY == 1 ]]; then + echo "$THIS: configured to install feature files only. Not starting test harness environment." + exit 0 +fi + ## Start test harness environment pushd "$SDK_TESTING_HARNESS" diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index a96f1f088..ef2bbb78c 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -2225,6 +2225,13 @@ module.exports = function getSteps(options) { } ); + When( + 'we make a Lookup Block call against round {int} and header {string}', + async function (int, string) { + await this.indexerClient.lookupBlock(int).headerOnly(string).do(); + } + ); + When( 'we make a Lookup Account by ID call against account {string} with round {int}', async function (account, round) { @@ -4649,6 +4656,13 @@ module.exports = function getSteps(options) { await this.v2Client.getStateProof(int).do(); }); + When( + 'we make a Lookup Block Hash call against round {int}', + async function (int) { + await this.v2Client.getBlockHash(int).do(); + } + ); + Given( 'a base64 encoded program bytes for heuristic sanity check {string}', async function (programByteStr) { diff --git a/tests/cucumber/unit.tags b/tests/cucumber/unit.tags index 0ff7ea3f5..31f29d33c 100644 --- a/tests/cucumber/unit.tags +++ b/tests/cucumber/unit.tags @@ -5,6 +5,7 @@ @unit.applications @unit.applications.boxes @unit.atomic_transaction_composer +@unit.blocksummary @unit.dryrun @unit.dryrun.trace.application @unit.feetest @@ -16,7 +17,9 @@ @unit.rekey @unit.responses @unit.responses.231 +@unit.responses.participationupdates @unit.responses.unlimited_assets +@unit.responses.blocksummary @unit.sourcemap @unit.stateproof.paths @unit.stateproof.responses From b527f9df2d05a9f3fd57115b38aad244f716f7d2 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 12 Oct 2022 14:56:13 -0700 Subject: [PATCH 13/15] Code generation improvements for boxes (#662) --- .../v2/algod/getApplicationBoxByName.ts | 6 +- src/client/v2/algod/getApplicationBoxes.ts | 17 +- src/client/v2/algod/models/types.ts | 34 +++ src/client/v2/indexer/indexer.ts | 6 +- .../lookupApplicationBoxByIDandName.ts | 8 +- src/client/v2/indexer/models/types.ts | 193 ++++++++++++++++++ .../v2/indexer/searchForApplicationBoxes.ts | 41 +--- src/main.ts | 5 +- 8 files changed, 238 insertions(+), 72 deletions(-) diff --git a/src/client/v2/algod/getApplicationBoxByName.ts b/src/client/v2/algod/getApplicationBoxByName.ts index 7848aa98a..29e832369 100644 --- a/src/client/v2/algod/getApplicationBoxByName.ts +++ b/src/client/v2/algod/getApplicationBoxByName.ts @@ -44,10 +44,6 @@ export default class GetApplicationBoxByName extends JSONRequest< // eslint-disable-next-line class-methods-use-this prepare(body: Record): Box { - if (body.name == null) - throw new Error(`Response does not contain "name" property: ${body}`); - if (body.value == null) - throw new Error(`Response does not contain "value" property: ${body}`); - return new Box(body.name, body.value); + return Box.from_obj_for_encoding(body); } } diff --git a/src/client/v2/algod/getApplicationBoxes.ts b/src/client/v2/algod/getApplicationBoxes.ts index 10d1e9253..213c9cca7 100644 --- a/src/client/v2/algod/getApplicationBoxes.ts +++ b/src/client/v2/algod/getApplicationBoxes.ts @@ -1,7 +1,7 @@ import JSONRequest from '../jsonrequest'; import HTTPClient from '../../client'; import IntDecoding from '../../../types/intDecoding'; -import { BoxesResponse, BoxDescriptor } from './models/types'; +import { BoxesResponse } from './models/types'; /** * Given an application ID, return all the box names associated with the app. @@ -56,19 +56,6 @@ export default class GetApplicationBoxes extends JSONRequest< // eslint-disable-next-line class-methods-use-this prepare(body: Record): BoxesResponse { - if (body.boxes == null || !Array.isArray(body.boxes)) - throw new Error( - `Response does not contain "boxes" array property: ${body}` - ); - - const boxes = (body.boxes as any[]).map((box, index) => { - if (box.name == null) - throw new Error( - `Response box at index ${index} does not contain "name" property: ${box}` - ); - return new BoxDescriptor(box.name); - }); - - return new BoxesResponse(boxes); + return BoxesResponse.from_obj_for_encoding(body); } } diff --git a/src/client/v2/algod/models/types.ts b/src/client/v2/algod/models/types.ts index bfb77cefc..41c5222b7 100644 --- a/src/client/v2/algod/models/types.ts +++ b/src/client/v2/algod/models/types.ts @@ -1435,6 +1435,17 @@ export class Box extends BaseModel { value: 'value', }; } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding(data: Record): Box { + /* eslint-disable dot-notation */ + if (typeof data['name'] === 'undefined') + throw new Error(`Response is missing required field 'name': ${data}`); + if (typeof data['value'] === 'undefined') + throw new Error(`Response is missing required field 'value': ${data}`); + return new Box(data['name'], data['value']); + /* eslint-enable dot-notation */ + } } /** @@ -1461,6 +1472,15 @@ export class BoxDescriptor extends BaseModel { name: 'name', }; } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding(data: Record): BoxDescriptor { + /* eslint-disable dot-notation */ + if (typeof data['name'] === 'undefined') + throw new Error(`Response is missing required field 'name': ${data}`); + return new BoxDescriptor(data['name']); + /* eslint-enable dot-notation */ + } } /** @@ -1481,6 +1501,19 @@ export class BoxesResponse extends BaseModel { boxes: 'boxes', }; } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding(data: Record): BoxesResponse { + /* eslint-disable dot-notation */ + if (!Array.isArray(data['boxes'])) + throw new Error( + `Response is missing required array field 'boxes': ${data}` + ); + return new BoxesResponse( + data['boxes'].map(BoxDescriptor.from_obj_for_encoding) + ); + /* eslint-enable dot-notation */ + } } export class BuildVersion extends BaseModel { @@ -2973,6 +3006,7 @@ export class StateProof extends BaseModel { typeof stateproof === 'string' ? new Uint8Array(Buffer.from(stateproof, 'base64')) : stateproof; + this.attribute_map = { message: 'Message', stateproof: 'StateProof', diff --git a/src/client/v2/indexer/indexer.ts b/src/client/v2/indexer/indexer.ts index 7fd053e24..b767343d6 100644 --- a/src/client/v2/indexer/indexer.ts +++ b/src/client/v2/indexer/indexer.ts @@ -18,17 +18,13 @@ import SearchAccounts from './searchAccounts'; import SearchForTransactions from './searchForTransactions'; import SearchForAssets from './searchForAssets'; import SearchForApplications from './searchForApplications'; -import SearchForApplicationBoxes, { - SearchForApplicationBoxesResponse, -} from './searchForApplicationBoxes'; +import SearchForApplicationBoxes from './searchForApplicationBoxes'; import { BaseHTTPClient } from '../../baseHTTPClient'; import { CustomTokenHeader, IndexerTokenHeader, } from '../../urlTokenBaseHTTPClient'; -export { SearchForApplicationBoxesResponse }; - /** * The Indexer provides a REST API interface of API calls to support searching the Algorand Blockchain. * diff --git a/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts b/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts index 80c069913..68b456aa8 100644 --- a/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts +++ b/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts @@ -1,7 +1,7 @@ import JSONRequest from '../jsonrequest'; import HTTPClient from '../../client'; import IntDecoding from '../../../types/intDecoding'; -import { Box } from '../algod/models/types'; +import { Box } from './models/types'; export default class LookupApplicationBoxByIDandName extends JSONRequest< Box, @@ -45,10 +45,6 @@ export default class LookupApplicationBoxByIDandName extends JSONRequest< // eslint-disable-next-line class-methods-use-this prepare(body: Record): Box { - if (body.name == null) - throw new Error(`Response does not contain "name" property: ${body}`); - if (body.value == null) - throw new Error(`Response does not contain "value" property: ${body}`); - return new Box(body.name, body.value); + return Box.from_obj_for_encoding(body); } } diff --git a/src/client/v2/indexer/models/types.ts b/src/client/v2/indexer/models/types.ts index 5c5948d39..7f5e417fb 100644 --- a/src/client/v2/indexer/models/types.ts +++ b/src/client/v2/indexer/models/types.ts @@ -64,6 +64,18 @@ export class Account extends BaseModel { */ public totalAssetsOptedIn: number | bigint; + /** + * For app-accounts only. The total number of bytes allocated for the keys and + * values of boxes which belong to the associated application. + */ + public totalBoxBytes: number | bigint; + + /** + * For app-accounts only. The total number of boxes which belong to the associated + * application. + */ + public totalBoxes: number | bigint; + /** * The count of all apps (AppParams objects) created by this account. */ @@ -173,6 +185,10 @@ export class Account extends BaseModel { * of application local data (AppLocalState objects) stored in this account. * @param totalAssetsOptedIn - The count of all assets that have been opted in, equivalent to the count of * AssetHolding objects held by this account. + * @param totalBoxBytes - For app-accounts only. The total number of bytes allocated for the keys and + * values of boxes which belong to the associated application. + * @param totalBoxes - For app-accounts only. The total number of boxes which belong to the associated + * application. * @param totalCreatedApps - The count of all apps (AppParams objects) created by this account. * @param totalCreatedAssets - The count of all assets (AssetParams objects) created by this account. * @param appsLocalState - (appl) applications local data stored in this account. @@ -214,6 +230,8 @@ export class Account extends BaseModel { status, totalAppsOptedIn, totalAssetsOptedIn, + totalBoxBytes, + totalBoxes, totalCreatedApps, totalCreatedAssets, appsLocalState, @@ -239,6 +257,8 @@ export class Account extends BaseModel { status: string; totalAppsOptedIn: number | bigint; totalAssetsOptedIn: number | bigint; + totalBoxBytes: number | bigint; + totalBoxes: number | bigint; totalCreatedApps: number | bigint; totalCreatedAssets: number | bigint; appsLocalState?: ApplicationLocalState[]; @@ -265,6 +285,8 @@ export class Account extends BaseModel { this.status = status; this.totalAppsOptedIn = totalAppsOptedIn; this.totalAssetsOptedIn = totalAssetsOptedIn; + this.totalBoxBytes = totalBoxBytes; + this.totalBoxes = totalBoxes; this.totalCreatedApps = totalCreatedApps; this.totalCreatedAssets = totalCreatedAssets; this.appsLocalState = appsLocalState; @@ -291,6 +313,8 @@ export class Account extends BaseModel { status: 'status', totalAppsOptedIn: 'total-apps-opted-in', totalAssetsOptedIn: 'total-assets-opted-in', + totalBoxBytes: 'total-box-bytes', + totalBoxes: 'total-boxes', totalCreatedApps: 'total-created-apps', totalCreatedAssets: 'total-created-assets', appsLocalState: 'apps-local-state', @@ -338,6 +362,14 @@ export class Account extends BaseModel { throw new Error( `Response is missing required field 'total-assets-opted-in': ${data}` ); + if (typeof data['total-box-bytes'] === 'undefined') + throw new Error( + `Response is missing required field 'total-box-bytes': ${data}` + ); + if (typeof data['total-boxes'] === 'undefined') + throw new Error( + `Response is missing required field 'total-boxes': ${data}` + ); if (typeof data['total-created-apps'] === 'undefined') throw new Error( `Response is missing required field 'total-created-apps': ${data}` @@ -356,6 +388,8 @@ export class Account extends BaseModel { status: data['status'], totalAppsOptedIn: data['total-apps-opted-in'], totalAssetsOptedIn: data['total-assets-opted-in'], + totalBoxBytes: data['total-box-bytes'], + totalBoxes: data['total-boxes'], totalCreatedApps: data['total-created-apps'], totalCreatedAssets: data['total-created-assets'], appsLocalState: @@ -2609,6 +2643,165 @@ export class BlockUpgradeVote extends BaseModel { } } +/** + * Box name and its content. + */ +export class Box extends BaseModel { + /** + * (name) box name, base64 encoded + */ + public name: Uint8Array; + + /** + * (value) box value, base64 encoded. + */ + public value: Uint8Array; + + /** + * Creates a new `Box` object. + * @param name - (name) box name, base64 encoded + * @param value - (value) box value, base64 encoded. + */ + constructor({ + name, + value, + }: { + name: string | Uint8Array; + value: string | Uint8Array; + }) { + super(); + this.name = + typeof name === 'string' + ? new Uint8Array(Buffer.from(name, 'base64')) + : name; + this.value = + typeof value === 'string' + ? new Uint8Array(Buffer.from(value, 'base64')) + : value; + + this.attribute_map = { + name: 'name', + value: 'value', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding(data: Record): Box { + /* eslint-disable dot-notation */ + if (typeof data['name'] === 'undefined') + throw new Error(`Response is missing required field 'name': ${data}`); + if (typeof data['value'] === 'undefined') + throw new Error(`Response is missing required field 'value': ${data}`); + return new Box({ + name: data['name'], + value: data['value'], + }); + /* eslint-enable dot-notation */ + } +} + +/** + * Box descriptor describes an app box without a value. + */ +export class BoxDescriptor extends BaseModel { + /** + * Base64 encoded box name + */ + public name: Uint8Array; + + /** + * Creates a new `BoxDescriptor` object. + * @param name - Base64 encoded box name + */ + constructor({ name }: { name: string | Uint8Array }) { + super(); + this.name = + typeof name === 'string' + ? new Uint8Array(Buffer.from(name, 'base64')) + : name; + + this.attribute_map = { + name: 'name', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding(data: Record): BoxDescriptor { + /* eslint-disable dot-notation */ + if (typeof data['name'] === 'undefined') + throw new Error(`Response is missing required field 'name': ${data}`); + return new BoxDescriptor({ + name: data['name'], + }); + /* eslint-enable dot-notation */ + } +} + +/** + * Box names of an application + */ +export class BoxesResponse extends BaseModel { + /** + * (appidx) application index. + */ + public applicationId: number | bigint; + + public boxes: BoxDescriptor[]; + + /** + * Used for pagination, when making another request provide this token with the + * next parameter. + */ + public nextToken?: string; + + /** + * Creates a new `BoxesResponse` object. + * @param applicationId - (appidx) application index. + * @param boxes - + * @param nextToken - Used for pagination, when making another request provide this token with the + * next parameter. + */ + constructor({ + applicationId, + boxes, + nextToken, + }: { + applicationId: number | bigint; + boxes: BoxDescriptor[]; + nextToken?: string; + }) { + super(); + this.applicationId = applicationId; + this.boxes = boxes; + this.nextToken = nextToken; + + this.attribute_map = { + applicationId: 'application-id', + boxes: 'boxes', + nextToken: 'next-token', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding(data: Record): BoxesResponse { + /* eslint-disable dot-notation */ + if (typeof data['application-id'] === 'undefined') + throw new Error( + `Response is missing required field 'application-id': ${data}` + ); + if (!Array.isArray(data['boxes'])) + throw new Error( + `Response is missing required array field 'boxes': ${data}` + ); + return new BoxesResponse({ + applicationId: data['application-id'], + boxes: data['boxes'].map(BoxDescriptor.from_obj_for_encoding), + nextToken: data['next-token'], + }); + /* eslint-enable dot-notation */ + } +} + /** * Response for errors */ diff --git a/src/client/v2/indexer/searchForApplicationBoxes.ts b/src/client/v2/indexer/searchForApplicationBoxes.ts index 6c34cb15e..b881de422 100644 --- a/src/client/v2/indexer/searchForApplicationBoxes.ts +++ b/src/client/v2/indexer/searchForApplicationBoxes.ts @@ -1,16 +1,10 @@ import JSONRequest from '../jsonrequest'; import HTTPClient from '../../client'; import IntDecoding from '../../../types/intDecoding'; -import { BoxDescriptor } from '../algod/models/types'; - -export interface SearchForApplicationBoxesResponse { - applicationId: number; - boxes: BoxDescriptor[]; - nextToken?: string; -} +import { BoxesResponse } from './models/types'; export default class SearchForApplicationBoxes extends JSONRequest< - SearchForApplicationBoxesResponse, + BoxesResponse, Record > { /** @@ -101,34 +95,7 @@ export default class SearchForApplicationBoxes extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): SearchForApplicationBoxesResponse { - if (typeof body['application-id'] !== 'number') { - throw new Error( - `Response does not contain "application-id" number property: ${body}` - ); - } - const applicationId: number = body['application-id']; - - if (body.boxes == null || !Array.isArray(body.boxes)) - throw new Error( - `Response does not contain "boxes" array property: ${body}` - ); - const boxes = (body.boxes as any[]).map((box, index) => { - if (box.name == null) - throw new Error( - `Response box at index ${index} does not contain "name" property: ${box}` - ); - return new BoxDescriptor(box.name); - }); - - const response: SearchForApplicationBoxesResponse = { - applicationId, - boxes, - }; - if (body['next-token'] != null) { - response.nextToken = body['next-token']; - } - - return response; + prepare(body: Record): BoxesResponse { + return BoxesResponse.from_obj_for_encoding(body); } } diff --git a/src/main.ts b/src/main.ts index a882391fb..4d80ec355 100644 --- a/src/main.ts +++ b/src/main.ts @@ -122,10 +122,7 @@ export { default as Algodv2 } from './client/v2/algod/algod'; export { default as Kmd } from './client/kmd'; export { default as IntDecoding } from './types/intDecoding'; export { default as Account } from './types/account'; -export { - default as Indexer, - SearchForApplicationBoxesResponse, -} from './client/v2/indexer/indexer'; +export { default as Indexer } from './client/v2/indexer/indexer'; export { BaseHTTPClient, BaseHTTPClientResponse, From ebbf77f64a54534ebff7ed0e29f32a6a0aa83ed0 Mon Sep 17 00:00:00 2001 From: michaeldiamant Date: Mon, 31 Oct 2022 12:53:18 -0400 Subject: [PATCH 14/15] Regenerate sources from upstream AVM branches --- src/client/v2/algod/models/types.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/client/v2/algod/models/types.ts b/src/client/v2/algod/models/types.ts index 41c5222b7..5e538012a 100644 --- a/src/client/v2/algod/models/types.ts +++ b/src/client/v2/algod/models/types.ts @@ -146,6 +146,17 @@ export class Account extends BaseModel { */ public sigType?: string; + /** + * (tbxb) The total number of bytes used by this account's app's box keys and + * values. + */ + public totalBoxBytes?: number | bigint; + + /** + * (tbx) The number of existing boxes created by this account's app. + */ + public totalBoxes?: number | bigint; + /** * Creates a new `Account` object. * @param address - the account public key @@ -193,6 +204,9 @@ export class Account extends BaseModel { * * sig * * msig * * lsig + * @param totalBoxBytes - (tbxb) The total number of bytes used by this account's app's box keys and + * values. + * @param totalBoxes - (tbx) The number of existing boxes created by this account's app. */ constructor({ address, @@ -217,6 +231,8 @@ export class Account extends BaseModel { participation, rewardBase, sigType, + totalBoxBytes, + totalBoxes, }: { address: string; amount: number | bigint; @@ -240,6 +256,8 @@ export class Account extends BaseModel { participation?: AccountParticipation; rewardBase?: number | bigint; sigType?: string; + totalBoxBytes?: number | bigint; + totalBoxes?: number | bigint; }) { super(); this.address = address; @@ -264,6 +282,8 @@ export class Account extends BaseModel { this.participation = participation; this.rewardBase = rewardBase; this.sigType = sigType; + this.totalBoxBytes = totalBoxBytes; + this.totalBoxes = totalBoxes; this.attribute_map = { address: 'address', @@ -288,6 +308,8 @@ export class Account extends BaseModel { participation: 'participation', rewardBase: 'reward-base', sigType: 'sig-type', + totalBoxBytes: 'total-box-bytes', + totalBoxes: 'total-boxes', }; } @@ -377,6 +399,8 @@ export class Account extends BaseModel { : undefined, rewardBase: data['reward-base'], sigType: data['sig-type'], + totalBoxBytes: data['total-box-bytes'], + totalBoxes: data['total-boxes'], }); /* eslint-enable dot-notation */ } From 1ad96cd5e01223549c1b69b9d37933fdb70a89e6 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Wed, 2 Nov 2022 07:45:46 -0400 Subject: [PATCH 15/15] Revert SDK_TESTING_BRANCH to master --- .test-env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.test-env b/.test-env index 9135343b7..df783a4fd 100644 --- a/.test-env +++ b/.test-env @@ -1,6 +1,6 @@ # Configs for testing repo download: SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing" -SDK_TESTING_BRANCH="feature/box-storage" +SDK_TESTING_BRANCH="master" SDK_TESTING_HARNESS="test-harness" INSTALL_ONLY=0