diff --git a/CHANGELOG.md b/CHANGELOG.md index 968d890cc..e6713e493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +# v1.24.0 + +## What's Changed + +### Bugfixes + +- Bug-Fix: encode ABI string with non-ASCII characters by @ahangsu in https://github.com/algorand/js-algorand-sdk/pull/700 + +### Enhancements + +- Tests: Migrate v1 algod dependencies to v2 in cucumber tests by @algochoi in https://github.com/algorand/js-algorand-sdk/pull/693 +- REST API: Add KV counts to NodeStatusResponse by @michaeldiamant in https://github.com/algorand/js-algorand-sdk/pull/696 +- Fix: createMultisigTransaction name in comments by @nullun in https://github.com/algorand/js-algorand-sdk/pull/694 +- Enhancement: allowing zero-length static array by @ahangsu in https://github.com/algorand/js-algorand-sdk/pull/698 +- ABI: Refactor ABI encoding test to round-trip by @michaeldiamant in https://github.com/algorand/js-algorand-sdk/pull/701 + +## New Contributors + +- @nullun made their first contribution in https://github.com/algorand/js-algorand-sdk/pull/694 + +**Full Changelog**: https://github.com/algorand/js-algorand-sdk/compare/v1.23.2...v1.24.0 + # v1.23.2 ## What's Changed diff --git a/README.md b/README.md index 9dbf8996c..2571126a0 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 fcca6b315..72870c902 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "algosdk", - "version": "1.23.2", + "version": "1.24.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "algosdk", - "version": "1.23.2", + "version": "1.24.0", "license": "MIT", "dependencies": { "algo-msgpack-with-bigint": "^2.1.1", diff --git a/package.json b/package.json index 7795dbf80..22b5815c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "algosdk", - "version": "1.23.2", + "version": "1.24.0", "description": "The official JavaScript SDK for Algorand", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/src/abi/abi_type.ts b/src/abi/abi_type.ts index 4c0d632ac..3ea89e67c 100644 --- a/src/abi/abi_type.ts +++ b/src/abi/abi_type.ts @@ -28,7 +28,7 @@ interface Segment { right: number; } -const staticArrayRegexp = /^([a-z\d[\](),]+)\[([1-9][\d]*)]$/; +const staticArrayRegexp = /^([a-z\d[\](),]+)\[(0|[1-9][\d]*)]$/; const ufixedRegexp = /^ufixed([1-9][\d]*)x([1-9][\d]*)$/; export type ABIValue = @@ -380,8 +380,13 @@ export class ABIStringType extends ABIType { throw new Error(`Cannot encode value as string: ${value}`); } const encodedBytes = Buffer.from(value); - const encodedLength = bigIntToBytes(value.length, LENGTH_ENCODE_BYTE_SIZE); - const mergedBytes = new Uint8Array(value.length + LENGTH_ENCODE_BYTE_SIZE); + const encodedLength = bigIntToBytes( + encodedBytes.length, + LENGTH_ENCODE_BYTE_SIZE + ); + const mergedBytes = new Uint8Array( + encodedBytes.length + LENGTH_ENCODE_BYTE_SIZE + ); mergedBytes.set(encodedLength); mergedBytes.set(encodedBytes, LENGTH_ENCODE_BYTE_SIZE); return mergedBytes; @@ -414,9 +419,9 @@ export class ABIArrayStaticType extends ABIType { constructor(argType: ABIType, arrayLength: number) { super(); - if (arrayLength < 1) { + if (arrayLength < 0) { throw new Error( - `static array must have a length greater than 0: ${arrayLength}` + `static array must have a non negative length: ${arrayLength}` ); } this.childType = argType; diff --git a/src/client/v2/algod/models/types.ts b/src/client/v2/algod/models/types.ts index 5e538012a..98fbe1540 100644 --- a/src/client/v2/algod/models/types.ts +++ b/src/client/v2/algod/models/types.ts @@ -2530,6 +2530,12 @@ export class NodeStatusResponse extends BaseModel { */ public catchpointProcessedAccounts?: number | bigint; + /** + * The number of key-values (KVs) from the current catchpoint that have been + * processed so far as part of the catchup + */ + public catchpointProcessedKvs?: number | bigint; + /** * The total number of accounts included in the current catchpoint */ @@ -2541,12 +2547,23 @@ export class NodeStatusResponse extends BaseModel { */ public catchpointTotalBlocks?: number | bigint; + /** + * The total number of key-values (KVs) included in the current catchpoint + */ + public catchpointTotalKvs?: number | bigint; + /** * The number of accounts from the current catchpoint that have been verified so * far as part of the catchup */ public catchpointVerifiedAccounts?: number | bigint; + /** + * The number of key-values (KVs) from the current catchpoint that have been + * verified so far as part of the catchup + */ + public catchpointVerifiedKvs?: number | bigint; + /** * The last catchpoint seen by the node */ @@ -2569,11 +2586,16 @@ export class NodeStatusResponse extends BaseModel { * catchup * @param catchpointProcessedAccounts - The number of accounts from the current catchpoint that have been processed so * far as part of the catchup + * @param catchpointProcessedKvs - The number of key-values (KVs) from the current catchpoint that have been + * processed so far as part of the catchup * @param catchpointTotalAccounts - The total number of accounts included in the current catchpoint * @param catchpointTotalBlocks - The total number of blocks that are required to complete the current catchpoint * catchup + * @param catchpointTotalKvs - The total number of key-values (KVs) included in the current catchpoint * @param catchpointVerifiedAccounts - The number of accounts from the current catchpoint that have been verified so * far as part of the catchup + * @param catchpointVerifiedKvs - The number of key-values (KVs) from the current catchpoint that have been + * verified so far as part of the catchup * @param lastCatchpoint - The last catchpoint seen by the node */ constructor({ @@ -2588,9 +2610,12 @@ export class NodeStatusResponse extends BaseModel { catchpoint, catchpointAcquiredBlocks, catchpointProcessedAccounts, + catchpointProcessedKvs, catchpointTotalAccounts, catchpointTotalBlocks, + catchpointTotalKvs, catchpointVerifiedAccounts, + catchpointVerifiedKvs, lastCatchpoint, }: { catchupTime: number | bigint; @@ -2604,9 +2629,12 @@ export class NodeStatusResponse extends BaseModel { catchpoint?: string; catchpointAcquiredBlocks?: number | bigint; catchpointProcessedAccounts?: number | bigint; + catchpointProcessedKvs?: number | bigint; catchpointTotalAccounts?: number | bigint; catchpointTotalBlocks?: number | bigint; + catchpointTotalKvs?: number | bigint; catchpointVerifiedAccounts?: number | bigint; + catchpointVerifiedKvs?: number | bigint; lastCatchpoint?: string; }) { super(); @@ -2621,9 +2649,12 @@ export class NodeStatusResponse extends BaseModel { this.catchpoint = catchpoint; this.catchpointAcquiredBlocks = catchpointAcquiredBlocks; this.catchpointProcessedAccounts = catchpointProcessedAccounts; + this.catchpointProcessedKvs = catchpointProcessedKvs; this.catchpointTotalAccounts = catchpointTotalAccounts; this.catchpointTotalBlocks = catchpointTotalBlocks; + this.catchpointTotalKvs = catchpointTotalKvs; this.catchpointVerifiedAccounts = catchpointVerifiedAccounts; + this.catchpointVerifiedKvs = catchpointVerifiedKvs; this.lastCatchpoint = lastCatchpoint; this.attribute_map = { @@ -2638,9 +2669,12 @@ export class NodeStatusResponse extends BaseModel { catchpoint: 'catchpoint', catchpointAcquiredBlocks: 'catchpoint-acquired-blocks', catchpointProcessedAccounts: 'catchpoint-processed-accounts', + catchpointProcessedKvs: 'catchpoint-processed-kvs', catchpointTotalAccounts: 'catchpoint-total-accounts', catchpointTotalBlocks: 'catchpoint-total-blocks', + catchpointTotalKvs: 'catchpoint-total-kvs', catchpointVerifiedAccounts: 'catchpoint-verified-accounts', + catchpointVerifiedKvs: 'catchpoint-verified-kvs', lastCatchpoint: 'last-catchpoint', }; } @@ -2692,9 +2726,12 @@ export class NodeStatusResponse extends BaseModel { catchpoint: data['catchpoint'], catchpointAcquiredBlocks: data['catchpoint-acquired-blocks'], catchpointProcessedAccounts: data['catchpoint-processed-accounts'], + catchpointProcessedKvs: data['catchpoint-processed-kvs'], catchpointTotalAccounts: data['catchpoint-total-accounts'], catchpointTotalBlocks: data['catchpoint-total-blocks'], + catchpointTotalKvs: data['catchpoint-total-kvs'], catchpointVerifiedAccounts: data['catchpoint-verified-accounts'], + catchpointVerifiedKvs: data['catchpoint-verified-kvs'], lastCatchpoint: data['last-catchpoint'], }); /* eslint-enable dot-notation */ diff --git a/src/multisig.ts b/src/multisig.ts index 297c9a7e8..8efd5797a 100644 --- a/src/multisig.ts +++ b/src/multisig.ts @@ -42,7 +42,7 @@ interface MultisigMetadataWithPks extends Omit { } /** - * createRawMultisigTransaction creates a raw, unsigned multisig transaction blob. + * createMultisigTransaction creates a raw, unsigned multisig transaction blob. * @param txn - the actual transaction. * @param version - multisig version * @param threshold - multisig threshold diff --git a/tests/10.ABI.ts b/tests/10.ABI.ts index ca90f6f3b..68447aa4b 100644 --- a/tests/10.ABI.ts +++ b/tests/10.ABI.ts @@ -10,6 +10,7 @@ import { ABIUfixedType, ABIUintType, ABIType, + ABIValue, } from '../src/abi/abi_type'; import { decodeAddress } from '../src/encoding/address'; @@ -125,7 +126,7 @@ describe('ABI type checking', () => { '[][][]', 'stuff[]', // static array - 'ufixed32x10[0]', + 'bool[01]', 'byte[10 ]', 'uint64[0x21]', // tuple @@ -218,191 +219,233 @@ describe('ABI type checking', () => { }); describe('ABI encoding', () => { - it('should encode the value correctly into bytes', () => { - const testCases = [ - [new ABIUintType(8).encode(BigInt(0)), new Uint8Array([0])], - [new ABIUintType(16).encode(BigInt(3)), new Uint8Array([0, 3])], - [ - new ABIUintType(64).encode(256), - new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]), - ], - [new ABIUfixedType(8, 30).encode(BigInt(255)), new Uint8Array([255])], - [new ABIUfixedType(32, 10).encode(33), new Uint8Array([0, 0, 0, 33])], - [ - new ABIAddressType().encode( - 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI' - ), - decodeAddress( - 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI' - ).publicKey, - ], - [new ABIByteType().encode(10), new Uint8Array([10])], - [new ABIByteType().encode(255), new Uint8Array([255])], - [new ABIBoolType().encode(true), new Uint8Array([128])], - [new ABIBoolType().encode(false), new Uint8Array([0])], - [ - new ABIStringType().encode('asdf'), - new Uint8Array([0, 4, 97, 115, 100, 102]), - ], - [ - new ABIArrayStaticType(new ABIBoolType(), 3).encode([ - true, - true, - false, - ]), - new Uint8Array([192]), - ], - [ - new ABIArrayStaticType(new ABIBoolType(), 8).encode([ - false, - true, - false, - false, - false, - false, - false, - false, - ]), - new Uint8Array([64]), - ], - [ - new ABIArrayStaticType(new ABIBoolType(), 8).encode([ - true, - true, - true, - true, - true, - true, - true, - true, - ]), - new Uint8Array([255]), - ], - [ - new ABIArrayStaticType(new ABIBoolType(), 9).encode([ - true, - false, - false, - true, - false, - false, - true, - false, - true, - ]), - new Uint8Array([146, 128]), - ], - [ - new ABIArrayStaticType(new ABIUintType(64), 3).encode([ - BigInt(1), - BigInt(2), - BigInt(3), - ]), - new Uint8Array([ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 2, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 3, - ]), - ], - [ - new ABIArrayDynamicType(new ABIBoolType()).encode([]), - new Uint8Array([0, 0]), - ], - [ - new ABIArrayDynamicType(new ABIBoolType()).encode([true, true, false]), - new Uint8Array([0, 3, 192]), - ], - [ - new ABIArrayDynamicType(new ABIBoolType()).encode([ - false, - true, - false, - false, - false, - false, - false, - false, - ]), - new Uint8Array([0, 8, 64]), - ], - [ - new ABIArrayDynamicType(new ABIBoolType()).encode([ - true, - false, - false, - true, - false, - false, - true, - false, - true, - ]), - new Uint8Array([0, 9, 146, 128]), - ], - [ABIType.from('()').encode([]), new Uint8Array([])], - // 2^6 + 2^5 = 64 + 32 = 96 - [ - ABIType.from('(bool,bool,bool)').encode([false, true, true]), - new Uint8Array([96]), - ], - [ - ABIType.from('(bool[3])').encode([[false, true, true]]), - new Uint8Array([96]), - ], - [ - ABIType.from('(bool[])').encode([[false, true, true]]), - new Uint8Array([0, 2, 0, 3, 96]), - ], - [ - ABIType.from('(bool[2],bool[])').encode([ - [true, true], - [true, true], - ]), - new Uint8Array([192, 0, 3, 0, 2, 192]), - ], - [ - ABIType.from('(bool[],bool[])').encode([[], []]), - new Uint8Array([0, 4, 0, 6, 0, 0, 0, 0]), - ], - [ - ABIType.from('(string,bool,bool,bool,bool,string)').encode([ - 'AB', - true, - false, - true, - false, - 'DE', - ]), - new Uint8Array([0, 5, 160, 0, 9, 0, 2, 65, 66, 0, 2, 68, 69]), - ], - ]; + type TestCase = { + abiType: ABIType; + input: T; + expectedEncoding: Uint8Array; + }; - for (const testCase of testCases) { - const actual = testCase[0]; - const expected = testCase[1]; - assert.deepStrictEqual(actual, expected); - } + function newTestCase(a: ABIType, b: T, c: Uint8Array): TestCase { + return { + abiType: a, + input: b, + expectedEncoding: c, + }; + } + + [ + newTestCase(new ABIUintType(8), BigInt(0), new Uint8Array([0])), + newTestCase(new ABIUintType(16), BigInt(3), new Uint8Array([0, 3])), + newTestCase( + new ABIUintType(64), + 256, + new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]) + ), + newTestCase(new ABIUfixedType(8, 30), BigInt(255), new Uint8Array([255])), + newTestCase(new ABIUfixedType(32, 10), 33, new Uint8Array([0, 0, 0, 33])), + newTestCase( + new ABIAddressType(), + 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI', + decodeAddress( + 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI' + ).publicKey + ), + newTestCase( + new ABIStringType(), + 'What’s new', + new Uint8Array([ + 0, + 12, + 87, + 104, + 97, + 116, + 226, + 128, + 153, + 115, + 32, + 110, + 101, + 119, + ]) + ), + newTestCase( + new ABIStringType(), + '😅🔨', + new Uint8Array([0, 8, 240, 159, 152, 133, 240, 159, 148, 168]) + ), + newTestCase(new ABIByteType(), 10, new Uint8Array([10])), + newTestCase(new ABIByteType(), 255, new Uint8Array([255])), + newTestCase(new ABIBoolType(), true, new Uint8Array([128])), + newTestCase(new ABIBoolType(), false, new Uint8Array([0])), + newTestCase( + new ABIStringType(), + 'asdf', + new Uint8Array([0, 4, 97, 115, 100, 102]) + ), + newTestCase( + new ABIArrayStaticType(new ABIBoolType(), 3), + [true, true, false], + new Uint8Array([192]) + ), + newTestCase( + new ABIArrayStaticType(new ABIBoolType(), 8), + [false, true, false, false, false, false, false, false], + new Uint8Array([64]) + ), + newTestCase( + new ABIArrayStaticType(new ABIBoolType(), 8), + [true, true, true, true, true, true, true, true], + new Uint8Array([255]) + ), + newTestCase( + new ABIArrayStaticType(new ABIBoolType(), 9), + [true, false, false, true, false, false, true, false, true], + new Uint8Array([146, 128]) + ), + newTestCase( + new ABIArrayStaticType(new ABIUintType(64), 3), + [BigInt(1), BigInt(2), 3], // Deliberately mix BigInt and int + new Uint8Array([ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3, + ]) + ), + newTestCase( + new ABIArrayDynamicType(new ABIBoolType()), + [], + new Uint8Array([0, 0]) + ), + newTestCase( + new ABIArrayDynamicType(new ABIBoolType()), + [true, true, false], + new Uint8Array([0, 3, 192]) + ), + newTestCase( + new ABIArrayDynamicType(new ABIBoolType()), + [false, true, false, false, false, false, false, false], + new Uint8Array([0, 8, 64]) + ), + newTestCase( + new ABIArrayDynamicType(new ABIBoolType()), + [true, false, false, true, false, false, true, false, true], + new Uint8Array([0, 9, 146, 128]) + ), + newTestCase(ABIType.from('()'), [], new Uint8Array([])), + // 2^6 + 2^5 = 64 + 32 = 96 + newTestCase( + ABIType.from('(bool,bool,bool)'), + [false, true, true], + new Uint8Array([96]) + ), + newTestCase( + ABIType.from('(bool[3])'), + [[false, true, true]], + new Uint8Array([96]) + ), + newTestCase( + ABIType.from('(bool[])'), + [[false, true, true]], + new Uint8Array([0, 2, 0, 3, 96]) + ), + newTestCase( + ABIType.from('(bool[2],bool[])'), + [ + [true, true], + [true, true], + ], + new Uint8Array([192, 0, 3, 0, 2, 192]) + ), + newTestCase( + ABIType.from('(bool[],bool[])'), + [[], []], + new Uint8Array([0, 4, 0, 6, 0, 0, 0, 0]) + ), + newTestCase( + ABIType.from('(string,bool,bool,bool,bool,string)'), + ['AB', true, false, true, false, 'DE'], + new Uint8Array([0, 5, 160, 0, 9, 0, 2, 65, 66, 0, 2, 68, 69]) + ), + newTestCase( + new ABITupleType([new ABIUintType(8), new ABIUintType(16)]), + [1, 2], + new Uint8Array([1, 0, 2]) + ), + ].forEach((testCase) => { + it(`should round-trip ${testCase.abiType}, ${testCase.input}`, () => { + const encoded = testCase.abiType.encode(testCase.input); + assert.deepStrictEqual(encoded, testCase.expectedEncoding); + const decoded = testCase.abiType.decode(encoded); + + // Converts any numeric type to BigInt for strict equality comparisons. + // The conversion is required because there's no type information + // available to convert a decoded BigInt back to its original number + // form. Converting from number to BigInt is always _safe_. + function numericAsBigInt(d: ABIValue): ABIValue { + if (typeof d === 'number') { + return BigInt(d); + } + if (d instanceof Array) { + return (d as ABIValue[]).map(numericAsBigInt); + } + return d; + } + + // Returns true when the provided ABIType decodes to BigInt. + function decodeReturnsBigInt(t: ABIType): boolean { + if (t instanceof ABIUintType || t instanceof ABIUfixedType) { + return true; + } + if (t instanceof ABITupleType) { + return t.childTypes.map(decodeReturnsBigInt).includes(true); + } + if (t instanceof ABIArrayStaticType) { + return decodeReturnsBigInt(t.childType); + } + if (t instanceof ABIArrayDynamicType) { + return decodeReturnsBigInt(t.childType); + } + return false; + } + + if (decodeReturnsBigInt(testCase.abiType)) { + // If casting to BigInt changes the test case input, then it implies + // the _unchanged_ test case input != `decoded`. + // + // The sanity check confirms that transforming the expected value is + // necessary. + if (testCase.input !== numericAsBigInt(testCase.input)) { + assert.notDeepStrictEqual(decoded, testCase.input); + } + + assert.deepStrictEqual(decoded, numericAsBigInt(testCase.input)); + } else { + assert.deepStrictEqual(decoded, testCase.input); + } + }); }); it('should fail for bad values during encoding', () => { @@ -431,156 +474,4 @@ describe('ABI encoding', () => { ]) ); }); - - it('should decode the value correctly into bytes', () => { - const testCases = [ - [new ABIUintType(8).decode(new Uint8Array([0])), BigInt(0)], - [new ABIUintType(16).decode(new Uint8Array([0, 3])), BigInt(3)], - [ - new ABIUintType(64).decode(new Uint8Array([1, 0, 0, 0, 0, 0, 0, 0])), - BigInt(2 ** 56), - ], - [new ABIUfixedType(8, 30).decode(new Uint8Array([255])), BigInt(255)], - [ - new ABIUfixedType(32, 10).decode(new Uint8Array([0, 0, 0, 33])), - BigInt(33), - ], - [ - new ABIAddressType().decode( - decodeAddress( - 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI' - ).publicKey - ), - 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI', - ], - [new ABIByteType().decode(new Uint8Array([10])), 10], - [new ABIByteType().decode(new Uint8Array([255])), 255], - [new ABIBoolType().decode(new Uint8Array([128])), true], - [new ABIBoolType().decode(new Uint8Array([0])), false], - [ - new ABIStringType().decode(new Uint8Array([0, 4, 97, 115, 100, 102])), - 'asdf', - ], - [ - new ABIArrayStaticType(new ABIBoolType(), 3).decode( - new Uint8Array([192]) - ), - [true, true, false], - ], - [ - new ABIArrayStaticType(new ABIBoolType(), 8).decode( - new Uint8Array([64]) - ), - [false, true, false, false, false, false, false, false], - ], - [ - new ABIArrayStaticType(new ABIBoolType(), 8).decode( - new Uint8Array([255]) - ), - [true, true, true, true, true, true, true, true], - ], - [ - new ABIArrayStaticType(new ABIBoolType(), 9).decode( - new Uint8Array([146, 128]) - ), - [true, false, false, true, false, false, true, false, true], - ], - [ - new ABIArrayStaticType(new ABIUintType(64), 3).decode( - new Uint8Array([ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 2, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 3, - ]) - ), - [BigInt(1), BigInt(2), BigInt(3)], - ], - [ - new ABIArrayDynamicType(new ABIBoolType()).decode( - new Uint8Array([0, 0]) - ), - [], - ], - [ - new ABIArrayDynamicType(new ABIBoolType()).decode( - new Uint8Array([0, 3, 192]) - ), - [true, true, false], - ], - [ - new ABIArrayDynamicType(new ABIBoolType()).decode( - new Uint8Array([0, 8, 64]) - ), - [false, true, false, false, false, false, false, false], - ], - [ - new ABIArrayDynamicType(new ABIBoolType()).decode( - new Uint8Array([0, 9, 146, 128]) - ), - [true, false, false, true, false, false, true, false, true], - ], - [ABIType.from('()').decode(new Uint8Array([])), []], - // // 2^6 + 2^5 = 64 + 32 = 96 - [ - ABIType.from('(bool,bool,bool)').decode(new Uint8Array([96])), - [false, true, true], - ], - [ - ABIType.from('(bool[3])').decode(new Uint8Array([96])), - [[false, true, true]], - ], - [ - ABIType.from('(bool[])').decode(new Uint8Array([0, 2, 0, 3, 96])), - [[false, true, true]], - ], - [ - ABIType.from('(bool[2],bool[])').decode( - new Uint8Array([192, 0, 3, 0, 2, 192]) - ), - [ - [true, true], - [true, true], - ], - ], - [ - ABIType.from('(bool[],bool[])').decode( - new Uint8Array([0, 4, 0, 6, 0, 0, 0, 0]) - ), - [[], []], - ], - [ - ABIType.from('(string,bool,bool,bool,bool,string)').decode( - new Uint8Array([0, 5, 160, 0, 9, 0, 2, 65, 66, 0, 2, 68, 69]) - ), - ['AB', true, false, true, false, 'DE'], - ], - ]; - - for (const testCase of testCases) { - const actual = testCase[0]; - const expected = testCase[1]; - assert.deepStrictEqual(actual, expected); - } - }); }); diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 8a4b14dcd..53d9613e7 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -120,17 +120,6 @@ module.exports = function getSteps(options) { // Dev Mode State const DEV_MODE_INITIAL_MICROALGOS = 10_000_000; - /* - * waitForAlgodInDevMode is a Dev mode helper method that waits for a transaction to resolve. - * Since Dev mode produces blocks on a per transaction basis, it's possible - * algod generates a block _before_ the corresponding SDK call to wait for a block. - * Without _any_ wait, it's possible the SDK looks for the transaction before algod completes processing. - * So, the method performs a local sleep to simulate waiting for a block. - */ - function waitForAlgodInDevMode() { - return new Promise((resolve) => setTimeout(resolve, 500)); - } - const { algod_token: algodToken, kmd_token: kmdToken } = options; // String parsing helper methods @@ -187,11 +176,6 @@ module.exports = function getSteps(options) { return boxRefArray; } - Given('an algod client', async function () { - this.acl = new algosdk.Algod(algodToken, 'http://localhost', 60000); - return this.acl; - }); - Given('a kmd client', function () { this.kcl = new algosdk.Kmd(kmdToken, 'http://localhost', 60001); return this.kcl; @@ -228,7 +212,7 @@ module.exports = function getSteps(options) { }); When('I get versions with algod', async function () { - this.versions = await this.acl.versions(); + this.versions = await this.v2Client.versionsCheck().do(); this.versions = this.versions.versions; return this.versions; }); @@ -237,47 +221,16 @@ module.exports = function getSteps(options) { assert.deepStrictEqual(true, this.versions.indexOf('v1') >= 0); }); + Then('v2 should be in the versions', function () { + assert.deepStrictEqual(true, this.versions.indexOf('v2') >= 0); + }); + When('I get versions with kmd', async function () { this.versions = await this.kcl.versions(); this.versions = this.versions.versions; return this.versions; }); - When('I get the status', async function () { - this.status = await this.acl.status(); - return this.status; - }); - - When('I get status after this block', async function () { - // Send a transaction to advance blocks in dev mode. - const sp = await this.acl.getTransactionParams(); - if (sp.firstRound === 0) sp.firstRound = 1; - const fundingTxnArgs = { - from: this.accounts[0], - to: this.accounts[0], - amount: 0, - fee: sp.fee, - firstRound: sp.lastRound + 1, - lastRound: sp.lastRound + 1000, - genesisHash: sp.genesishashb64, - genesisID: sp.genesisID, - }; - const stxKmd = await this.kcl.signTransaction( - this.handle, - this.wallet_pswd, - fundingTxnArgs - ); - await this.acl.sendRawTransaction(stxKmd); - - this.statusAfter = await this.acl.statusAfterBlock(this.status.lastRound); - return this.statusAfter; - }); - - Then('I can get the block info', async function () { - this.block = await this.acl.block(this.statusAfter.lastRound); - assert.deepStrictEqual(true, Number.isInteger(this.block.round)); - }); - Given( 'payment transaction parameters {int} {int} {int} {string} {string} {string} {int} {string} {string}', function (fee, fv, lv, gh, to, close, amt, gen, note) { @@ -426,16 +379,16 @@ module.exports = function getSteps(options) { this.rekey = await this.kcl.generateKey(this.handle); this.rekey = this.rekey.address; // Fund the rekey address with some Algos - const sp = await this.acl.getTransactionParams(); + const sp = await this.v2Client.getTransactionParams().do(); if (sp.firstRound === 0) sp.firstRound = 1; const fundingTxnArgs = { from: this.accounts[0], to: this.rekey, amount: DEV_MODE_INITIAL_MICROALGOS, fee: sp.fee, - firstRound: sp.lastRound + 1, - lastRound: sp.lastRound + 1000, - genesisHash: sp.genesishashb64, + firstRound: sp.firstRound, + lastRound: sp.lastRound, + genesisHash: sp.genesisHash, genesisID: sp.genesisID, }; @@ -444,7 +397,7 @@ module.exports = function getSteps(options) { this.wallet_pswd, fundingTxnArgs ); - await this.acl.sendRawTransaction(stxKmd); + await this.v2Client.sendRawTransaction(stxKmd).do(); return this.rekey; } ); @@ -504,15 +457,15 @@ module.exports = function getSteps(options) { 'default transaction with parameters {int} {string}', async function (amt, note) { [this.pk] = this.accounts; - const result = await this.acl.getTransactionParams(); + const result = await this.v2Client.getTransactionParams().do(); this.lastRound = result.lastRound; this.txn = { from: this.accounts[0], to: this.accounts[1], fee: result.fee, - firstRound: result.lastRound + 1, - lastRound: result.lastRound + 1000, - genesisHash: result.genesishashb64, + firstRound: result.firstRound, + lastRound: result.lastRound, + genesisHash: result.genesisHash, genesisID: result.genesisID, note: makeUint8Array(Buffer.from(note, 'base64')), amount: parseInt(amt), @@ -525,15 +478,15 @@ module.exports = function getSteps(options) { 'default transaction with parameters {int} {string} and rekeying key', async function (amt, note) { this.pk = this.rekey; - const result = await this.acl.getTransactionParams(); + const result = await this.v2Client.getTransactionParams().do(); this.lastRound = result.lastRound; this.txn = { from: this.rekey, to: this.accounts[1], fee: result.fee, - firstRound: result.lastRound + 1, - lastRound: result.lastRound + 1000, - genesisHash: result.genesishashb64, + firstRound: result.firstRound, + lastRound: result.lastRound, + genesisHash: result.genesisHash, genesisID: result.genesisID, note: makeUint8Array(Buffer.from(note, 'base64')), amount: parseInt(amt), @@ -546,7 +499,7 @@ module.exports = function getSteps(options) { 'default multisig transaction with parameters {int} {string}', async function (amt, note) { [this.pk] = this.accounts; - const result = await this.acl.getTransactionParams(); + const result = await this.v2Client.getTransactionParams().do(); this.msig = { version: 1, threshold: 1, @@ -557,9 +510,9 @@ module.exports = function getSteps(options) { from: algosdk.multisigAddress(this.msig), to: this.accounts[1], fee: result.fee, - firstRound: result.lastRound + 1, - lastRound: result.lastRound + 1000, - genesisHash: result.genesishashb64, + firstRound: result.firstRound, + lastRound: result.lastRound, + genesisHash: result.genesisHash, genesisID: result.genesisID, note: makeUint8Array(Buffer.from(note, 'base64')), amount: parseInt(amt), @@ -635,55 +588,14 @@ module.exports = function getSteps(options) { }); Then('the node should be healthy', async function () { - const health = await this.acl.healthCheck(); + const health = await this.v2Client.healthCheck().do(); assert.deepStrictEqual(health, makeObject({})); }); Then('I get the ledger supply', async function () { - return this.acl.ledgerSupply(); - }); - - Then('I get transactions by address and round', async function () { - const lastRound = await this.acl.status(); - const transactions = await this.acl.transactionByAddress( - this.accounts[0], - 1, - lastRound.lastRound - ); - assert.deepStrictEqual( - true, - Object.entries(transactions).length === 0 || - 'transactions' in transactions - ); + return this.v2Client.supply().do(); }); - Then('I get pending transactions', async function () { - const transactions = await this.acl.pendingTransactions(10); - assert.deepStrictEqual( - true, - Object.entries(transactions).length === 0 || - 'truncatedTxns' in transactions - ); - }); - - When('I get the suggested params', async function () { - this.params = await this.acl.getTransactionParams(); - return this.params; - }); - - When('I get the suggested fee', async function () { - this.fee = await this.acl.suggestedFee(); - this.fee = this.fee.fee; - return this.fee; - }); - - Then( - 'the fee in the suggested params should equal the suggested fee', - function () { - assert.deepStrictEqual(this.params.fee, this.fee); - } - ); - When('I create a bid', function () { let addr = algosdk.generateAccount(); this.sk = addr.sk; @@ -865,21 +777,23 @@ module.exports = function getSteps(options) { }); When('I send the transaction', async function () { - this.txid = await this.acl.sendRawTransaction(this.stx); - this.txid = this.txid.txId; + const txid = await this.v2Client.sendRawTransaction(this.stx).do(); + this.txid = txid.txId; + this.appTxid = txid; // Alias to use in waitForTransaction. return this.txid; }); When('I send the kmd-signed transaction', async function () { - this.txid = await this.acl.sendRawTransaction(this.stxKmd); - this.txid = this.txid.txId; + const txid = await this.v2Client.sendRawTransaction(this.stxKmd).do(); + this.txid = txid.txId; + this.appTxid = txid; // Alias to use in waitForTransaction. return this.txid; }); // eslint-disable-next-line consistent-return When('I send the multisig transaction', async function () { try { - this.txid = await this.acl.sendRawTransaction(this.stx); + this.txid = await this.v2Client.sendRawTransaction(this.stx).do(); this.err = false; return this.txid; } catch (e) { @@ -887,19 +801,6 @@ module.exports = function getSteps(options) { } }); - Then('the transaction should go through', async function () { - await waitForAlgodInDevMode(); - const info = await this.acl.pendingTransactionInformation(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 () { assert.deepStrictEqual(true, this.err); }); @@ -1006,12 +907,8 @@ module.exports = function getSteps(options) { // return this.txid // }) - Then('I get account information', async function () { - return this.acl.accountInformation(this.accounts[0]); - }); - Then('I can get account information', async function () { - await this.acl.accountInformation(this.pk); + await this.v2Client.accountInformation(this.pk).do(); return this.kcl.deleteKey(this.handle, this.wallet_pswd, this.pk); }); @@ -1034,12 +931,12 @@ module.exports = function getSteps(options) { const from = this.accounts[0]; this.pk = from; - const result = await this.acl.getTransactionParams(); + const result = await this.v2Client.getTransactionParams().do(); const suggestedParams = { fee: result.fee, - firstRound: result.lastRound + 1, - lastRound: result.lastRound + 1000, - genesisHash: result.genesishashb64, + firstRound: result.firstRound, + lastRound: result.lastRound, + genesisHash: result.genesisHash, genesisID: result.genesisID, }; this.lastRound = result.lastRound; @@ -1094,12 +991,12 @@ module.exports = function getSteps(options) { 'default asset creation transaction with total issuance {int}', async function (issuance) { [this.assetTestFixture.creator] = this.accounts; - this.params = await this.acl.getTransactionParams(); + this.params = await this.v2Client.getTransactionParams().do(); this.fee = this.params.fee; - this.fv = this.params.lastRound; - this.lv = this.fv + 1000; + this.fv = this.params.firstRound; + this.lv = this.params.lastRound; this.note = undefined; - this.gh = this.params.genesishashb64; + this.gh = this.params.genesisHash; const parsedIssuance = parseInt(issuance); const decimals = 0; const defaultFrozen = false; @@ -1160,12 +1057,12 @@ module.exports = function getSteps(options) { 'default-frozen asset creation transaction with total issuance {int}', async function (issuance) { [this.assetTestFixture.creator] = this.accounts; - this.params = await this.acl.getTransactionParams(); + this.params = await this.v2Client.getTransactionParams().do(); this.fee = this.params.fee; - this.fv = this.params.lastRound; - this.lv = this.fv + 1000; + this.fv = this.params.firstRound; + this.lv = this.params.lastRound; this.note = undefined; - this.gh = this.params.genesishashb64; + this.gh = this.params.genesisHash; const parsedIssuance = parseInt(issuance); const decimals = 0; const defaultFrozen = true; @@ -1235,22 +1132,22 @@ module.exports = function getSteps(options) { } When('I update the asset index', async function () { - const accountResponse = await this.acl.accountInformation( - this.assetTestFixture.creator - ); - const heldAssets = accountResponse.thisassettotal; - let keys = Object.keys(heldAssets).map((key) => parseInt(key)); - keys = keys.sort(sortKeysAscending); - const assetIndex = keys[keys.length - 1]; + const accountResponse = await this.v2Client + .accountInformation(this.assetTestFixture.creator) + .do(); + const heldAssets = accountResponse['created-assets']; + let assetIds = heldAssets.map((asset) => asset.index); + assetIds = assetIds.sort(sortKeysAscending); + const assetIndex = assetIds[assetIds.length - 1]; // this is stored as a string so it can be used as a key later. this.assetTestFixture.index = assetIndex.toString(); }); When('I get the asset info', async function () { - this.assetTestFixture.queriedParams = await this.acl.assetInformation( - this.assetTestFixture.index - ); + this.assetTestFixture.queriedParams = await this.v2Client + .getAssetByID(this.assetTestFixture.index) + .do(); }); Then('the asset info should match the expected asset info', function () { @@ -1258,9 +1155,9 @@ module.exports = function getSteps(options) { assert.strictEqual( true, this.assetTestFixture.expectedParams[key] === - this.assetTestFixture.queriedParams[key] || + this.assetTestFixture.queriedParams.params[key] || typeof this.assetTestFixture.expectedParams[key] === 'undefined' || - typeof this.assetTestFixture.queriedParams[key] === 'undefined' + typeof this.assetTestFixture.queriedParams.params[key] === 'undefined' ); }); }); @@ -1269,12 +1166,12 @@ module.exports = function getSteps(options) { 'I create a no-managers asset reconfigure transaction', async function () { [this.assetTestFixture.creator] = this.accounts; - this.params = await this.acl.getTransactionParams(); + this.params = await this.v2Client.getTransactionParams().do(); this.fee = this.params.fee; - this.fv = this.params.lastRound; - this.lv = this.fv + 1000; + this.fv = this.params.firstRound; + this.lv = this.params.lastRound; this.note = undefined; - this.gh = this.params.genesishashb64; + this.gh = this.params.genesisHash; // if we truly supplied no managers at all, it would be an asset destroy txn // so leave one key written const manager = this.assetTestFixture.creator; @@ -1311,12 +1208,12 @@ module.exports = function getSteps(options) { When('I create an asset destroy transaction', async function () { [this.assetTestFixture.creator] = this.accounts; - this.params = await this.acl.getTransactionParams(); + this.params = await this.v2Client.getTransactionParams().do(); this.fee = this.params.fee; - this.fv = this.params.lastRound; - this.lv = this.fv + 1000; + this.fv = this.params.firstRound; + this.lv = this.params.lastRound; this.note = undefined; - this.gh = this.params.genesishashb64; + this.gh = this.params.genesisHash; const genesisID = ''; const type = 'acfg'; @@ -1340,7 +1237,7 @@ module.exports = function getSteps(options) { Then('I should be unable to get the asset info', async function () { let failed = false; try { - await this.acl.assetInformation(this.assetTestFixture.index); + await this.v2Client.getAssetByID(this.assetTestFixture.index).do(); } catch (e) { failed = true; } @@ -1351,12 +1248,12 @@ module.exports = function getSteps(options) { 'I create a transaction for a second account, signalling asset acceptance', async function () { const accountToUse = this.accounts[1]; - this.params = await this.acl.getTransactionParams(); + this.params = await this.v2Client.getTransactionParams().do(); this.fee = this.params.fee; - this.fv = this.params.lastRound; - this.lv = this.fv + 1000; + this.fv = this.params.firstRound; + this.lv = this.params.lastRound; this.note = undefined; - this.gh = this.params.genesishashb64; + this.gh = this.params.genesisHash; const genesisID = ''; const type = 'axfer'; @@ -1383,12 +1280,12 @@ module.exports = function getSteps(options) { When( 'I create a transaction transferring {int} assets from creator to a second account', async function (amount) { - this.params = await this.acl.getTransactionParams(); + this.params = await this.v2Client.getTransactionParams().do(); this.fee = this.params.fee; - this.fv = this.params.lastRound; - this.lv = this.fv + 1000; + this.fv = this.params.firstRound; + this.lv = this.params.lastRound; this.note = undefined; - this.gh = this.params.genesishashb64; + this.gh = this.params.genesisHash; const genesisID = ''; const type = 'axfer'; @@ -1415,12 +1312,12 @@ module.exports = function getSteps(options) { When( 'I create a transaction transferring {int} assets from a second account to creator', async function (amount) { - this.params = await this.acl.getTransactionParams(); + this.params = await this.v2Client.getTransactionParams().do(); this.fee = this.params.fee; - this.fv = this.params.lastRound; - this.lv = this.fv + 1000; + this.fv = this.params.firstRound; + this.lv = this.params.lastRound; this.note = undefined; - this.gh = this.params.genesishashb64; + this.gh = this.params.genesisHash; const genesisID = ''; const type = 'axfer'; @@ -1447,18 +1344,21 @@ module.exports = function getSteps(options) { Then( 'the creator should have {int} assets remaining', async function (expectedTotal) { - const accountInformation = await this.acl.accountInformation( - this.assetTestFixture.creator - ); - const assetsHeld = accountInformation.assets[this.assetTestFixture.index]; - assert.deepStrictEqual(assetsHeld.amount, parseInt(expectedTotal)); + const accountInformation = await this.v2Client + .accountInformation(this.assetTestFixture.creator) + .do(); + for (const asset of accountInformation.assets) { + if (asset['asset-id'] === this.assetTestFixture.index) { + assert.deepStrictEqual(asset.amount, parseInt(expectedTotal)); + } + } } ); When('I send the bogus kmd-signed transaction', async function () { this.err = false; try { - await this.acl.sendRawTransaction(this.stxKmd); + await this.v2Client.sendRawTransaction(this.stxKmd).do(); } catch (e) { this.err = true; } @@ -1467,12 +1367,12 @@ module.exports = function getSteps(options) { When( 'I create an un-freeze transaction targeting the second account', async function () { - this.params = await this.acl.getTransactionParams(); + this.params = await this.v2Client.getTransactionParams().do(); this.fee = this.params.fee; - this.fv = this.params.lastRound; - this.lv = this.fv + 1000; + this.fv = this.params.firstRound; + this.lv = this.params.lastRound; this.note = undefined; - this.gh = this.params.genesishashb64; + this.gh = this.params.genesisHash; const freezer = this.assetTestFixture.creator; this.assetTestFixture.lastTxn = { @@ -1497,12 +1397,12 @@ module.exports = function getSteps(options) { When( 'I create a freeze transaction targeting the second account', async function () { - this.params = await this.acl.getTransactionParams(); + this.params = await this.v2Client.getTransactionParams().do(); this.fee = this.params.fee; - this.fv = this.params.lastRound; - this.lv = this.fv + 1000; + this.fv = this.params.firstRound; + this.lv = this.params.lastRound; this.note = undefined; - this.gh = this.params.genesishashb64; + this.gh = this.params.genesisHash; const freezer = this.assetTestFixture.creator; this.assetTestFixture.lastTxn = { @@ -1527,12 +1427,12 @@ module.exports = function getSteps(options) { When( 'I create a transaction revoking {int} assets from a second account to creator', async function (amount) { - this.params = await this.acl.getTransactionParams(); + this.params = await this.v2Client.getTransactionParams().do(); this.fee = this.params.fee; - this.fv = this.params.lastRound; - this.lv = this.fv + 1000; + this.fv = this.params.firstRound; + this.lv = this.params.lastRound; this.note = undefined; - this.gh = this.params.genesishashb64; + this.gh = this.params.genesisHash; const genesisID = ''; const type = 'axfer';